mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 15:14:23 +01:00
pay: Filter out routehints whose entrypoint is unknown
We would happily spin on attempts that are doomed to fail because we don't know the entrypoint. Next up: remove routehints whose entrypoints are known but unreachable.
This commit is contained in:
committed by
Rusty Russell
parent
ad7f59f7a1
commit
1e1d7b387c
@@ -2313,14 +2313,17 @@ static void trim_route(struct route_info **route, size_t n)
|
|||||||
|
|
||||||
/* Make sure routehints are reasonable length, and (since we assume we
|
/* Make sure routehints are reasonable length, and (since we assume we
|
||||||
* can append), not directly to us. Note: untrusted data! */
|
* can append), not directly to us. Note: untrusted data! */
|
||||||
static struct route_info **filter_routehints(struct routehints_data *d,
|
static struct route_info **filter_routehints(struct gossmap *map,
|
||||||
|
struct payment *p,
|
||||||
|
struct routehints_data *d,
|
||||||
struct node_id *myid,
|
struct node_id *myid,
|
||||||
struct route_info **hints)
|
struct route_info **hints)
|
||||||
{
|
{
|
||||||
|
const size_t max_hops = ROUTING_MAX_HOPS / 2;
|
||||||
char *mods = tal_strdup(tmpctx, "");
|
char *mods = tal_strdup(tmpctx, "");
|
||||||
for (size_t i = 0; i < tal_count(hints); i++) {
|
for (size_t i = 0; i < tal_count(hints); i++) {
|
||||||
|
struct gossmap_node *entrynode;
|
||||||
/* Trim any routehint > 10 hops */
|
/* Trim any routehint > 10 hops */
|
||||||
size_t max_hops = ROUTING_MAX_HOPS / 2;
|
|
||||||
if (tal_count(hints[i]) > max_hops) {
|
if (tal_count(hints[i]) > max_hops) {
|
||||||
tal_append_fmt(&mods,
|
tal_append_fmt(&mods,
|
||||||
"Trimmed routehint %zu (%zu hops) to %zu. ",
|
"Trimmed routehint %zu (%zu hops) to %zu. ",
|
||||||
@@ -2344,6 +2347,27 @@ static struct route_info **filter_routehints(struct routehints_data *d,
|
|||||||
tal_arr_remove(&hints, i);
|
tal_arr_remove(&hints, i);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If routehint entrypoint is unreachable there's no
|
||||||
|
* point in keeping it. */
|
||||||
|
entrynode = gossmap_find_node(map, &hints[i][0].pubkey);
|
||||||
|
if (entrynode == NULL) {
|
||||||
|
tal_append_fmt(&mods,
|
||||||
|
"Removed routehint %zu because "
|
||||||
|
"entrypoint %s is unknown. ",
|
||||||
|
i,
|
||||||
|
type_to_string(tmpctx, struct node_id,
|
||||||
|
&hints[i][0].pubkey));
|
||||||
|
plugin_log(p->plugin, LOG_DBG,
|
||||||
|
"Removed routehint %zu because "
|
||||||
|
"entrypoint %s is unknown. ",
|
||||||
|
i,
|
||||||
|
type_to_string(tmpctx, struct node_id,
|
||||||
|
&hints[i][0].pubkey));
|
||||||
|
tal_arr_remove(&hints, i);
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streq(mods, ""))
|
if (!streq(mods, ""))
|
||||||
@@ -2616,7 +2640,7 @@ static void routehint_step_cb(struct routehints_data *d, struct payment *p)
|
|||||||
{
|
{
|
||||||
struct route_hop hop;
|
struct route_hop hop;
|
||||||
const struct payment *root = payment_root(p);
|
const struct payment *root = payment_root(p);
|
||||||
|
struct gossmap *map;
|
||||||
if (p->step == PAYMENT_STEP_INITIALIZED) {
|
if (p->step == PAYMENT_STEP_INITIALIZED) {
|
||||||
if (root->routes == NULL)
|
if (root->routes == NULL)
|
||||||
return payment_continue(p);
|
return payment_continue(p);
|
||||||
@@ -2625,8 +2649,9 @@ static void routehint_step_cb(struct routehints_data *d, struct payment *p)
|
|||||||
* beginning, and every other payment will filter out the
|
* beginning, and every other payment will filter out the
|
||||||
* exluded ones on the fly. */
|
* exluded ones on the fly. */
|
||||||
if (p->parent == NULL) {
|
if (p->parent == NULL) {
|
||||||
d->routehints = filter_routehints(d, p->local_id,
|
map = get_gossmap(p->plugin);
|
||||||
p->routes);
|
d->routehints = filter_routehints(
|
||||||
|
map, p, d, p->local_id, p->routes);
|
||||||
/* filter_routehints modifies the array, but
|
/* filter_routehints modifies the array, but
|
||||||
* this could trigger a resize and the resize
|
* this could trigger a resize and the resize
|
||||||
* could trigger a realloc.
|
* could trigger a realloc.
|
||||||
|
|||||||
@@ -4172,3 +4172,51 @@ def test_self_pay(node_factory):
|
|||||||
|
|
||||||
with pytest.raises(RpcError):
|
with pytest.raises(RpcError):
|
||||||
l1.rpc.pay(inv)
|
l1.rpc.pay(inv)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(TEST_NETWORK != 'regtest', "Canned invoice is network specific")
|
||||||
|
def test_unreachable_routehint(node_factory, bitcoind):
|
||||||
|
"""Test that we discard routehints that we can't reach.
|
||||||
|
|
||||||
|
Reachability is tested by checking whether we can reach the
|
||||||
|
entrypoint of the routehint, i.e., the first node in the
|
||||||
|
routehint. The network we create is partitioned on purpose for
|
||||||
|
this: first we attempt with an unknown destination and an unknown
|
||||||
|
routehint entrypoint, later we make them known, but still
|
||||||
|
unreachable, by connecting them without a channel.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a partitioned network, first without connecting it, then
|
||||||
|
# connecting it without a channel so they can sync gossip. Notice
|
||||||
|
# that l4 is there only to trick the deadend heuristic.
|
||||||
|
l1, l2 = node_factory.line_graph(2, wait_for_announce=True)
|
||||||
|
l3, l4, l5 = node_factory.line_graph(3, wait_for_announce=True)
|
||||||
|
entrypoint = '0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199'
|
||||||
|
|
||||||
|
# Generate an invoice with exactly one routehint.
|
||||||
|
for i in range(100):
|
||||||
|
invoice = l5.rpc.invoice(10, 'attempt{}'.format(i), 'description')['bolt11']
|
||||||
|
decoded = l1.rpc.decodepay(invoice)
|
||||||
|
if 'routes' in decoded and len(decoded['routes']) == 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
assert('routes' in decoded and len(decoded['routes']) == 1)
|
||||||
|
|
||||||
|
with pytest.raises(RpcError, match=r'Destination [a-f0-9]{66} is not reachable'):
|
||||||
|
l1.rpc.pay(invoice)
|
||||||
|
|
||||||
|
assert(l1.daemon.is_in_log(
|
||||||
|
r"Removed routehint 0 because entrypoint {entrypoint} is unknown.".format(
|
||||||
|
entrypoint=entrypoint
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
# Now connect l2 to l3 to create a bridge, but without a
|
||||||
|
# channel. The entrypoint will become known, but still
|
||||||
|
# unreachable, resulting in a slightly different error message,
|
||||||
|
# but the routehint will still be removed.
|
||||||
|
l2.connect(l3)
|
||||||
|
wait_for(lambda: len(l1.rpc.listnodes(entrypoint)['nodes']) == 1)
|
||||||
|
|
||||||
|
# TODO(cdecker) investigate why dijkstra_distance tells us the entrypoint is reachable...
|
||||||
|
|||||||
Reference in New Issue
Block a user