getroute: allow array of channels to exclude.

The pay plugin will use this, rather than the current "suppress for 90 second" hacks.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2019-01-15 14:35:27 +10:30 committed by Christian Decker
parent 599ec5efbe
commit 3d016e7249
3 changed files with 107 additions and 4 deletions

View File

@ -147,13 +147,13 @@ class LightningRpc(UnixDomainSocketRpc):
}
return self.call("listnodes", payload)
def getroute(self, peer_id, msatoshi, riskfactor, cltv=9, fromid=None, fuzzpercent=None, seed=None):
def getroute(self, peer_id, msatoshi, riskfactor, cltv=9, fromid=None, fuzzpercent=None, seed=None, exclude=[]):
"""
Show route to {id} for {msatoshi}, using {riskfactor} and optional
{cltv} (default 9). If specified search from {fromid} otherwise use
this node as source. Randomize the route with up to {fuzzpercent}
(0.0 -> 100.0, default 5.0) using {seed} as an arbitrary-size string
seed.
seed. {exclude} is an optional array of scids[xDirection] to exclude.
"""
payload = {
"id": peer_id,
@ -162,7 +162,8 @@ class LightningRpc(UnixDomainSocketRpc):
"cltv": cltv,
"fromid": fromid,
"fuzzpercent": fuzzpercent,
"seed": seed
"seed": seed,
"exclude": exclude
}
return self.call("getroute", payload)

View File

@ -292,6 +292,27 @@ static void json_getroute_reply(struct subd *gossip UNUSED, const u8 *reply, con
was_pending(command_success(cmd, response));
}
static bool json_to_short_channel_id_with_dir(const char *buffer,
const jsmntok_t *tok,
struct short_channel_id *scid,
bool *dir)
{
/* Ends in /0 or /1 */
if (tok->end - tok->start < 2)
return false;
if (buffer[tok->end - 2] != '/')
return false;
if (buffer[tok->end - 1] == '0')
*dir = false;
else if (buffer[tok->end - 1] == '1')
*dir = true;
else
return false;
return short_channel_id_from_str(buffer + tok->start,
tok->end - tok->start - 2, scid);
}
static struct command_result *json_getroute(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
@ -300,9 +321,13 @@ static struct command_result *json_getroute(struct command *cmd,
struct lightningd *ld = cmd->ld;
struct pubkey *destination;
struct pubkey *source;
const jsmntok_t *excludetok;
u64 *msatoshi;
unsigned *cltv;
double *riskfactor;
struct short_channel_id *excluded;
bool *excluded_dir;
/* Higher fuzz means that some high-fee paths can be discounted
* for an even larger value, increasing the scope for route
* randomization (the higher-fee paths become more likely to
@ -317,16 +342,43 @@ static struct command_result *json_getroute(struct command *cmd,
p_opt_def("cltv", param_number, &cltv, 9),
p_opt_def("fromid", param_pubkey, &source, ld->id),
p_opt_def("fuzzpercent", param_percent, &fuzz, 5.0),
p_opt("exclude", param_array, &excludetok),
NULL))
return command_param_failed();
/* Convert from percentage */
*fuzz = *fuzz / 100.0;
if (excludetok) {
const jsmntok_t *t, *end = json_next(excludetok);
size_t i;
excluded = tal_arr(cmd, struct short_channel_id,
excludetok->size);
excluded_dir = tal_arr(cmd, bool, excludetok->size);
for (i = 0, t = excludetok + 1;
t < end;
t = json_next(t), i++) {
if (!json_to_short_channel_id_with_dir(buffer, t,
&excluded[i],
&excluded_dir[i])) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"%.*s is not a valid"
" id+direction",
t->end - t->start,
buffer + t->start);
}
}
} else {
excluded = NULL;
excluded_dir = NULL;
}
u8 *req = towire_gossip_getroute_request(cmd, source, destination,
*msatoshi, *riskfactor * 1000,
*cltv, fuzz,
NULL, NULL);
excluded, excluded_dir);
subd_req(ld->gossip, ld->gossip, req, -1, 0, json_getroute_reply, cmd);
return command_still_pending(cmd);
}

View File

@ -1,9 +1,11 @@
from fixtures import * # noqa: F401,F403
from lightning import RpcError
from utils import wait_for, TIMEOUT, only_one
import json
import logging
import os
import pytest
import struct
import subprocess
import time
@ -941,3 +943,51 @@ def test_gossip_notices_close(node_factory, bitcoind):
l1.start()
assert(l1.rpc.listchannels()['channels'] == [])
assert(l1.rpc.listnodes()['nodes'] == [])
def test_getroute_exclude(node_factory, bitcoind):
"""Test getroute's exclude argument"""
l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True)
# This should work
route = l1.rpc.getroute(l4.info['id'], 1, 1)['route']
# l1 id is > l2 id, so 1 means l1->l2
chan_l1l2 = route[0]['channel'] + '/1'
chan_l2l1 = route[0]['channel'] + '/0'
# This should not
with pytest.raises(RpcError):
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l1l2])
# Blocking the wrong way should be fine.
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l2l1])
# Now, create an alternate (better) route.
l2.rpc.connect(l4.info['id'], 'localhost', l4.port)
scid = l2.fund_channel(l4, 1000000, wait_for_active=False)
bitcoind.generate_block(5)
# We don't wait above, because we care about it hitting l1.
l1.daemon.wait_for_logs([r'update for channel {}\(0\) now ACTIVE'
.format(scid),
r'update for channel {}\(1\) now ACTIVE'
.format(scid)])
# l3 id is > l2 id, so 1 means l3->l2
# chan_l3l2 = route[1]['channel'] + '/1'
chan_l2l3 = route[1]['channel'] + '/0'
# l4 is > l2
# chan_l4l2 = scid + '/1'
chan_l2l4 = scid + '/0'
# This works
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l2l3])
# This works
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l2l4])
# This doesn't
with pytest.raises(RpcError):
l1.rpc.getroute(l4.info['id'], 1, 1, exclude=[chan_l2l3, chan_l2l4])