Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- JSON API: `pay` and `decodepay` accept and ignore `lightning:` prefixes.
- pylightning: Allow either keyword arguments or positional arguments.
- JSON-RPC: messages are now separated by 2 consecutive newlines.
- JSON API: `pay` now tries a direct channel first if one exists.

### Deprecated

Expand Down
10 changes: 10 additions & 0 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,16 @@ struct channel *active_channel_by_id(struct lightningd *ld,
return peer_active_channel(peer);
}

struct channel *normal_channel_by_id(struct lightningd *ld,
const struct pubkey *id)
{
struct peer *peer = peer_by_id(ld, id);
if (!peer)
return NULL;

return peer_normal_channel(peer);
}

struct channel *channel_by_dbid(struct lightningd *ld, const u64 dbid)
{
struct peer *p;
Expand Down
3 changes: 3 additions & 0 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ struct channel *peer_normal_channel(struct peer *peer);
struct channel *active_channel_by_id(struct lightningd *ld,
const struct pubkey *id,
struct uncommitted_channel **uc);
/* Get CHANNELD_NORMAL channel for a peer, if any. */
struct channel *normal_channel_by_id(struct lightningd *ld,
const struct pubkey *id);

struct channel *channel_by_dbid(struct lightningd *ld, const u64 dbid);

Expand Down
82 changes: 72 additions & 10 deletions lightningd/payalgo.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <common/type_to_string.h>
#include <gossipd/gen_gossip_wire.h>
#include <gossipd/routing.h>
#include <lightningd/channel.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h>
Expand Down Expand Up @@ -119,6 +120,9 @@ struct pay {
* attempt to ensure no leaks across long pay attempts */
char *try_parent;

/* Whether we have tried a direct route or not. */
bool tried_direct_route;

/* Current route being attempted. */
struct route_hop *route;

Expand Down Expand Up @@ -403,6 +407,21 @@ static void json_pay_sendpay_resume(const struct sendpay_result *r,
}
}

static void json_pay_sendpay(struct pay *pay)
{
assert(pay->route);

++pay->sendpay_tries;
log_route(pay, pay->route);

pay->in_sendpay = true;
send_payment(pay->try_parent,
pay->cmd->ld, &pay->payment_hash, pay->route,
pay->msatoshi,
pay->description,
&json_pay_sendpay_resume, pay);
}

static void json_pay_getroute_reply(struct subd *gossip UNUSED,
const u8 *reply, const int *fds UNUSED,
struct pay *pay)
Expand Down Expand Up @@ -486,19 +505,10 @@ static void json_pay_getroute_reply(struct subd *gossip UNUSED,
return;
}

++pay->sendpay_tries;

log_route(pay, route);
assert(!pay->route);
pay->route = tal_dup_arr(pay, struct route_hop, route,
tal_count(route), 0);

pay->in_sendpay = true;
send_payment(pay->try_parent,
pay->cmd->ld, &pay->payment_hash, route,
pay->msatoshi,
pay->description,
&json_pay_sendpay_resume, pay);
json_pay_sendpay(pay);
}

/* Start a payment attempt. Return true if deferred,
Expand All @@ -512,6 +522,10 @@ static bool json_pay_try(struct pay *pay)
u64 maxoverpayment;
u64 overpayment;

struct channel *direct_channel = NULL;
struct route_hop *direct_route = NULL;
bool direct_channel_ok;

/* If too late anyway, fail now. */
if (time_after(now, pay->expiry)) {
struct json_stream *data
Expand Down Expand Up @@ -553,6 +567,52 @@ static bool json_pay_try(struct pay *pay)
} else
overpayment = 0;

/* Try a direct route first if possible. */
if (!pay->tried_direct_route) {
/* Only try direct route once. */
pay->tried_direct_route = true;

/* Find a direct channel. */
direct_channel = normal_channel_by_id(cmd->ld,
&pay->receiver_id);
/* Did we find one? */
direct_channel_ok = direct_channel && direct_channel->scid;
/* Check channel can serve this payment. */
if (direct_channel_ok) {
u64 reserve = direct_channel->channel_info.their_config.channel_reserve_satoshis * 1000;
if (direct_channel->our_msatoshi <= reserve)
/* Cannot spend. */
direct_channel_ok = false;
else if (direct_channel->our_msatoshi - reserve <
pay->msatoshi + overpayment)
/* Not enough spendable. */
direct_channel_ok = false;
}
if (direct_channel_ok) {
/* Generate a direct route
* with the direct channel. */
direct_route = tal_arr(pay,
struct route_hop,
1);
direct_route[0].channel_id = *direct_channel->scid;
direct_route[0].nodeid = pay->receiver_id;
direct_route[0].amount = pay->msatoshi
+ overpayment;
direct_route[0].delay = pay->min_final_cltv_expiry;

assert(!pay->route);
pay->route = direct_route;

/* Defer; this simplifies handling of
* command_still_pending.
*/
new_reltimer(&cmd->ld->timers, pay->try_parent,
time_from_sec(0),
&json_pay_sendpay, pay);
return true;
}
}

++pay->getroute_tries;

/* FIXME: use b11->routes */
Expand Down Expand Up @@ -677,6 +737,8 @@ static void json_pay(struct command *cmd,
* improve privacy somewhat. */
pay->fuzz = 0.75;
pay->try_parent = NULL;
/* Start not having tried direct route. */
pay->tried_direct_route = false;
/* Start with no route */
pay->route = NULL;
/* Start with no failures */
Expand Down
37 changes: 37 additions & 0 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -1119,3 +1119,40 @@ def test_pay_variants(node_factory):
b11 = 'LIGHTNING:' + l2.rpc.invoice(123000, 'test_pay_variants upper with prefix', 'description')['bolt11'].upper()
l1.rpc.decodepay(b11)
l1.rpc.pay(b11)


def test_pay_direct(node_factory, bitcoind):
"""Check that we prefer the direct route.
"""
l1, l2, l3 = node_factory.get_nodes(3)

# Direct channel
l1.rpc.connect(l3.info['id'], 'localhost', l3.port)
l1.fund_channel(l3, 10**7)
# Indirect route
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l1.fund_channel(l2, 10**7)
l2.rpc.connect(l3.info['id'], 'localhost', l3.port)
c3 = l2.fund_channel(l3, 10**7)

# Let channels lock in.
bitcoind.generate_block(5)

# Make sure l1 knows the l2->l3 channel.
l1.wait_channel_active(c3)

# Find out how much msatoshi l1 owns on l1->l2 channel.
l1l2msatreference = only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['msatoshi_to_us']

# Try multiple times to ensure that route randomization
# will not override our preference for direct route.
for i in range(8):
inv = l3.rpc.invoice(10000, 'pay{}'.format(i), 'desc')['bolt11']

l1.rpc.pay(inv)

# We should have gone the direct route, so
# l1->l2 channel msatoshi_to_us should not
# have changed.
l1l2msat = only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['msatoshi_to_us']
assert l1l2msat == l1l2msatreference