plugins/pay: implement routeboost hints, naive version.

We sanitize the routes: firstly, we assume appending so eliminate the
first hop if the route points straight to us.  Secondly, eliminate empty
hints.  Thirdly, trim overlong hints.

Then we just use the first route hint.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2019-01-15 20:34:01 +10:30 committed by Christian Decker
parent b787200752
commit 2b72ebb3b6
2 changed files with 138 additions and 6 deletions

View File

@ -4,6 +4,7 @@
#include <ccan/time/time.h>
#include <common/bolt11.h>
#include <common/type_to_string.h>
#include <gossipd/gossip_constants.h>
#include <plugins/libplugin.h>
/* 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;

View File

@ -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