diff --git a/plugins/pay.c b/plugins/pay.c index 1c38350c8..1064a9fb7 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -4,6 +4,7 @@ #include #include #include +#include #include /* Public key of this node. */ @@ -43,6 +44,9 @@ struct pay_command { /* Channels which have failed us. */ const char **excludes; + + /* Any routehints to use. */ + struct route_info **routehints; }; static struct command_result *start_pay_attempt(struct command *cmd, @@ -123,6 +127,73 @@ static struct command_result *sendpay_done(struct command *cmd, pc->payment_hash); } +/* Calculate how many millisatoshi we need at the start of this route + * to get msatoshi to the end. */ +static u64 route_msatoshi(u64 msatoshi, + const struct route_info *route, size_t num_route) +{ + for (ssize_t i = num_route - 1; i >= 0; i--) { + u64 fee; + + fee = route[i].fee_base_msat; + fee += (route[i].fee_proportional_millionths * msatoshi) / 1000000; + msatoshi += fee; + } + return msatoshi; +} + +/* Calculate cltv we need at the start of this route to get cltv at the end. */ +static u32 route_cltv(u32 cltv, + const struct route_info *route, size_t num_route) +{ + for (size_t i = 0; i < num_route; i++) + cltv += route[i].cltv_expiry_delta; + return cltv; +} + +/* The pubkey to use is the destination of this routehint. */ +static const char *route_pubkey(const tal_t *ctx, + const struct pay_command *pc, + const struct route_info *routehint, + size_t n) +{ + if (n == tal_count(routehint)) + return pc->dest; + return type_to_string(ctx, struct pubkey, &routehint[n].pubkey); +} + +static const char *join_routehint(const tal_t *ctx, + const char *buf, + const jsmntok_t *route, + const struct pay_command *pc, + const struct route_info *routehint) +{ + char *ret; + + /* Truncate closing ] from route */ + ret = tal_strndup(ctx, buf + route->start, route->end - route->start - 1); + for (size_t i = 0; i < tal_count(routehint); i++) { + tal_append_fmt(&ret, ", {" + " 'id': '%s'," + " 'channel': '%s'," + " 'msatoshi': %"PRIu64"," + " 'delay': %u }", + /* pubkey of *destination* */ + route_pubkey(tmpctx, pc, routehint, i + 1), + type_to_string(tmpctx, struct short_channel_id, + &routehint[i].short_channel_id), + /* amount to be received by *destination* */ + route_msatoshi(pc->msatoshi, routehint + i + 1, + tal_count(routehint) - i - 1), + /* cltv for *destination* */ + route_cltv(pc->final_cltv, routehint + i + 1, + tal_count(routehint) - i - 1)); + } + /* Put ] back */ + tal_append_fmt(&ret, "]"); + return ret; +} + static struct command_result *getroute_done(struct command *cmd, const char *buf, const jsmntok_t *result, @@ -165,7 +236,11 @@ static struct command_result *getroute_done(struct command *cmd, "Route wanted delay of %u blocks", delay); } - attempt.route = json_strdup(pc->attempts, buf, result); + if (tal_count(pc->routehints)) + attempt.route = join_routehint(pc->attempts, buf, t, + pc, pc->routehints[0]); + else + attempt.route = json_strdup(pc->attempts, buf, t); tal_arr_expand(&pc->attempts, attempt); if (pc->desc) @@ -174,8 +249,8 @@ static struct command_result *getroute_done(struct command *cmd, json_desc = ""; return send_outreq(cmd, "sendpay", sendpay_done, forward_error, pc, - "'route': %.*s, 'payment_hash': '%s'%s", - t->end - t->start, buf + t->start, + "'route': %s, 'payment_hash': '%s'%s", + attempt.route, pc->payment_hash, json_desc); } @@ -184,6 +259,10 @@ static struct command_result *start_pay_attempt(struct command *cmd, struct pay_command *pc) { char *exclude; + u64 amount; + const char *dest; + size_t max_hops = ROUTING_MAX_HOPS; + u32 cltv; if (tal_count(pc->excludes) != 0) { exclude = tal_strdup(tmpctx, ",'exclude': ["); @@ -196,14 +275,32 @@ static struct command_result *start_pay_attempt(struct command *cmd, } else exclude = ""; + /* If we have a routehint, try that first; we need to do extra + * checks that it meets our criteria though. */ + if (tal_count(pc->routehints)) { + amount = route_msatoshi(pc->msatoshi, + pc->routehints[0], + tal_count(pc->routehints[0])); + dest = type_to_string(tmpctx, struct pubkey, + &pc->routehints[0][0].pubkey); + max_hops -= tal_count(pc->routehints[0]); + cltv = route_cltv(pc->final_cltv, + pc->routehints[0], + tal_count(pc->routehints[0])); + } else { + amount = pc->msatoshi; + dest = pc->dest; + cltv = pc->final_cltv; + } + /* OK, ask for route to destination */ return send_outreq(cmd, "getroute", getroute_done, forward_error, pc, "'id': '%s'," "'msatoshi': %"PRIu64"," "'cltv': %u," + "'maxhops': %zu," "'riskfactor': %f%s", - pc->dest, pc->msatoshi, pc->final_cltv, pc->riskfactor, - exclude); + dest, amount, cltv, max_hops, pc->riskfactor, exclude); } /* gossipd doesn't know much about the current state of channels; here we @@ -261,6 +358,40 @@ static struct command_result *listpeers_done(struct command *cmd, return start_pay_attempt(cmd, pc); } +/* Trim route to this length by taking from the *front* of route + * (end points to destination, so we need that bit!) */ +static void trim_route(struct route_info **route, size_t n) +{ + size_t remove = tal_count(*route) - n; + memmove(*route, *route + remove, sizeof(**route) * n); + tal_resize(route, n); +} + +/* Make sure routehints are reasonable length, and (since we assume we + * can append), not directly to us. Note: untrusted data! */ +static struct route_info **filter_routehints(struct pay_command *pc, + struct route_info **hints) +{ + for (size_t i = 0; i < tal_count(hints); i++) { + /* Trim any routehint > 10 hops */ + size_t max_hops = ROUTING_MAX_HOPS / 2; + if (tal_count(hints[i]) > max_hops) + trim_route(&hints[i], max_hops); + + /* If we are first hop, trim. */ + if (tal_count(hints[i]) > 0 + && pubkey_eq(&hints[i][0].pubkey, &my_id)) + trim_route(&hints[i], tal_count(hints[i])-1); + + /* If route is empty, remove altogether. */ + if (tal_count(hints[i]) == 0) { + tal_arr_remove(&hints, i); + i--; + } + } + return tal_steal(pc, hints); +} + static struct command_result *handle_pay(struct command *cmd, const char *buf, const jsmntok_t *params) @@ -315,6 +446,7 @@ static struct command_result *handle_pay(struct command *cmd, pc->msatoshi = *msatoshi; } + pc->routehints = filter_routehints(pc, b11->routes); pc->maxfeepercent = *maxfeepercent; pc->maxdelay = *maxdelay; pc->exemptfee = *exemptfee; diff --git a/tests/test_pay.py b/tests/test_pay.py index 8c1aef9f3..fabc3d698 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -1202,7 +1202,7 @@ def test_pay_retry(node_factory, bitcoind): l1.rpc.pay(l5.rpc.invoice(10**8, 'test_retry2', 'test_retry2')['bolt11']) -@pytest.mark.xfail(strict=True) +@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 otherwise gossip takes 5 minutes!") def test_pay_routeboost(node_factory, bitcoind): """Make sure we can use routeboost information. """ # l1->l2->l3--private-->l4