diff --git a/gossipd/routing.c b/gossipd/routing.c index ca9da0ddf276..2b9dde5961c0 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -368,6 +368,7 @@ static struct node *new_node(struct routing_state *rstate, n->id = *id; memset(n->chans.arr, 0, sizeof(n->chans.arr)); broadcastable_init(&n->bcast); + broadcastable_init(&n->rgraph); n->tokens = TOKEN_MAX; node_map_add(rstate->nodes, n); tal_add_destructor2(n, destroy_node, rstate); @@ -521,6 +522,7 @@ static void init_half_chan(struct routing_state *rstate, struct half_chan *c = &chan->half[channel_idx]; broadcastable_init(&c->bcast); + broadcastable_init(&c->rgraph); c->tokens = TOKEN_MAX; } @@ -1340,7 +1342,7 @@ bool routing_add_channel_update(struct routing_state *rstate, return false; } - if (timestamp <= hc->bcast.timestamp) { + if (timestamp <= hc->rgraph.timestamp) { SUPERVERBOSE("Ignoring outdated update."); /* Ignoring != failing */ return true; @@ -1377,15 +1379,34 @@ bool routing_add_channel_update(struct routing_state *rstate, } else { spam = false; } - - chan->half[direction].bcast.timestamp = timestamp; - - /* Safe even if was never added, but if it's a private channel it - * would be a WIRE_GOSSIP_STORE_PRIVATE_UPDATE. */ - gossip_store_delete(rstate->gs, &hc->bcast, - is_chan_public(chan) - ? WIRE_CHANNEL_UPDATE - : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); + /* Routing graph always uses the latest message. */ + hc->rgraph.timestamp = timestamp; + if (spam) { + /* Remove the prior spam update if it exists. */ + if (hc->rgraph.index != hc->bcast.index) { + gossip_store_delete(rstate->gs, &hc->rgraph, + is_chan_public(chan) + ? WIRE_CHANNEL_UPDATE + : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); + } + } else { + /* Safe to broadcast */ + hc->bcast.timestamp = timestamp; + /* Remove prior spam update if one exists. */ + if (hc->rgraph.index != hc->bcast.index) { + /* Safe even if was never added, but if it's a + * private channel it would be a + * WIRE_GOSSIP_STORE_PRIVATE_UPDATE. */ + gossip_store_delete(rstate->gs, &hc->rgraph, + is_chan_public(chan) + ? WIRE_CHANNEL_UPDATE + : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); + } + gossip_store_delete(rstate->gs, &hc->bcast, + is_chan_public(chan) + ? WIRE_CHANNEL_UPDATE + : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); + } /* BOLT #7: * - MUST consider the `timestamp` of the `channel_announcement` to be @@ -1407,23 +1428,31 @@ bool routing_add_channel_update(struct routing_state *rstate, hc->bcast.index = gossip_store_add_private_update(rstate->gs, update); - } else + /* No need to separately track spam for private + * channels. */ + hc->rgraph.index = hc->bcast.index; + } else { hc->bcast.index = index; + hc->rgraph.index = index; + } return true; } /* If we're loading from store, this means we don't re-add to store. */ - if (index) - hc->bcast.index = index; - else { - hc->bcast.index - = gossip_store_add(rstate->gs, update, - hc->bcast.timestamp, + if (index) { + if (!spam) + hc->bcast.index = index; + hc->rgraph.index = index; + } else { + hc->rgraph.index + = gossip_store_add(rstate->gs, update, timestamp, local_direction(rstate, chan, NULL), spam, NULL); if (hc->bcast.timestamp > rstate->last_timestamp && hc->bcast.timestamp < time_now().ts.tv_sec) rstate->last_timestamp = hc->bcast.timestamp; + if (!spam) + hc->bcast.index = hc->rgraph.index; peer_supplied_good_gossip(peer, 1); } @@ -1653,7 +1682,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, return false; } - if (node->bcast.timestamp >= timestamp) { + if (node->rgraph.timestamp >= timestamp) { SUPERVERBOSE("Ignoring node announcement, it's outdated."); /* OK unless we're loading from store */ return index == 0; @@ -1691,26 +1720,41 @@ bool routing_add_node_announcement(struct routing_state *rstate, spam = false; } - /* Harmless if it was never added */ - gossip_store_delete(rstate->gs, - &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - - node->bcast.timestamp = timestamp; - if (node->bcast.timestamp > rstate->last_timestamp - && node->bcast.timestamp < time_now().ts.tv_sec) - rstate->last_timestamp = node->bcast.timestamp; + /* Routing graph always references the latest message. */ + node->rgraph.timestamp = timestamp; + if (!spam) { + node->bcast.timestamp = timestamp; + /* remove prior spam update if one exists */ + if (node->rgraph.index != node->bcast.index) { + /* Harmless if it was never added */ + gossip_store_delete(rstate->gs, &node->rgraph, + WIRE_NODE_ANNOUNCEMENT); + } + gossip_store_delete(rstate->gs, &node->bcast, + WIRE_NODE_ANNOUNCEMENT); + /* Remove prior spam update. */ + } else if (node->rgraph.index != node->bcast.index) { + gossip_store_delete(rstate->gs, &node->rgraph, + WIRE_NODE_ANNOUNCEMENT); + } - if (index) - node->bcast.index = index; - else { - node->bcast.index - = gossip_store_add(rstate->gs, msg, - node->bcast.timestamp, + /* Don't add to the store if it was loaded from the store. */ + if (index) { + node->rgraph.index = index; + if (!spam) + node->bcast.index = index; + } else { + node->rgraph.index + = gossip_store_add(rstate->gs, msg, timestamp, node_id_eq(&node_id, &rstate->local_id), - spam, - NULL); + spam, NULL); + if (node->bcast.timestamp > rstate->last_timestamp + && node->bcast.timestamp < time_now().ts.tv_sec) + rstate->last_timestamp = node->bcast.timestamp; + if (!spam) + node->bcast.index = node->rgraph.index; + peer_supplied_good_gossip(peer, 1); } diff --git a/gossipd/routing.h b/gossipd/routing.h index 1641284bb667..b58161d85019 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -20,9 +20,13 @@ struct peer; struct routing_state; struct half_chan { - /* Timestamp and index into store file */ + /* Timestamp and index into store file - safe to broadcast */ struct broadcastable bcast; + /* Most recent gossip for the routing graph - may be rate-limited and + * non-broadcastable. If there is no spam, rgraph == bcast. */ + struct broadcastable rgraph; + /* Token bucket */ u8 tokens; }; @@ -105,6 +109,10 @@ struct node { /* Timestamp and index into store file */ struct broadcastable bcast; + /* Possibly spam flagged. Nonbroadcastable, but used for routing graph. + * If there is no current spam, rgraph == bcast. */ + struct broadcastable rgraph; + /* Token bucket */ u8 tokens; diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 6cd24c435d57..56b91c90a740 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1863,7 +1863,8 @@ def channel_fees(node): '0102c479b7684b9db496b844f6925f4ffd8a27c5840a020d1b537623c1545dcd8e195776381bbf51213e541a853a4a49a0faf84316e7ccca5e7074901a96bbabe04e06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400201010006000000000000000000000014000003eb000000003b023380', # timestamp=1568096259, fee_proportional_millionths=1004 '01024b866012d995d3d7aec7b7218a283de2d03492dbfa21e71dd546ec2e36c3d4200453420aa02f476f99c73fe1e223ea192f5fa544b70a8319f2a216f1513d503d06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400301010006000000000000000000000014000003ec000000003b023380', - # update 5 marks you as a nasty spammer, but we listen to you anyway now! fee_proportional_millionths=1005 + # update 5 marks you as a nasty spammer, but the routing graph is + # updated with this even though the gossip is not broadcast. '01025b5b5a0daed874ab02bd3356d38190ff46bbaf5f10db5067da70f3ca203480ca78059e6621c6143f3da4e454d0adda6d01a9980ed48e71ccd0c613af73570a7106226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400401010006000000000000000000000014000003ed000000003b023380' ], timeout=TIMEOUT @@ -1887,6 +1888,7 @@ def channel_fees(node): check=True, timeout=TIMEOUT, stdout=subprocess.PIPE).stdout.decode('utf8') + assert("fee_proportional_millionths=1004" in decoded) # Used in routing graph, but not passed to gossip peers. assert("fee_proportional_millionths=1005" not in decoded)