Skip to content

Commit caccd09

Browse files
committed
discovery: enforce non-zero timestamp in gossip messages
In this commit, we add validation for channel updates and node announcements to ensure that we reject gossip messages with zero timestamps at the discovery layer. From BOLT 7: "MUST set timestamp to greater than 0, AND to greater than any previously-sent channel_update for this short_channel_id." This validation is performed in the gossip handlers (handleNodeAnnouncement and handleChanUpdate) rather than at the wire protocol level. This approach ensures we can still decode messages from disk or embedded in onion errors while rejecting invalid gossip from peers. Remote peers sending zero-timestamp gossip will have their ban score incremented.
1 parent eea4840 commit caccd09

3 files changed

Lines changed: 119 additions & 0 deletions

File tree

discovery/gossiper.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2502,6 +2502,27 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context,
25022502
"node=%x, source=%x", nMsg.peer, timestamp, nodeAnn.NodeID,
25032503
nMsg.source.SerializeCompressed())
25042504

2505+
// Per BOLT 7, the timestamp MUST be greater than 0.
2506+
if nodeAnn.Timestamp == 0 {
2507+
err := fmt.Errorf("rejecting node announcement with zero "+
2508+
"timestamp for node %x", nodeAnn.NodeID)
2509+
2510+
// Only increase ban score for remote peers.
2511+
if nMsg.isRemote {
2512+
log.Warnf("Increasing ban score for peer=%v: %v",
2513+
nMsg.peer, err)
2514+
2515+
dcErr := d.handleBadPeer(nMsg.peer)
2516+
if dcErr != nil {
2517+
err = dcErr
2518+
}
2519+
}
2520+
2521+
nMsg.err <- err
2522+
2523+
return nil, false
2524+
}
2525+
25052526
// We'll quickly ask the router if it already has a newer update for
25062527
// this node so we can skip validating signatures if not required.
25072528
if d.cfg.Graph.IsStaleNode(ctx, nodeAnn.NodeID, timestamp) {
@@ -3056,6 +3077,27 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
30563077
// quickly reject it.
30573078
timestamp := time.Unix(int64(upd.Timestamp), 0)
30583079

3080+
// Per BOLT 7, the timestamp MUST be greater than 0.
3081+
if upd.Timestamp == 0 {
3082+
err := fmt.Errorf("rejecting channel update with zero "+
3083+
"timestamp for short_chan_id(%v)", shortChanID)
3084+
3085+
// Only increase ban score for remote peers.
3086+
if nMsg.isRemote {
3087+
log.Warnf("Increasing ban score for peer=%v: %v",
3088+
nMsg.peer, err)
3089+
3090+
dcErr := d.handleBadPeer(nMsg.peer)
3091+
if dcErr != nil {
3092+
err = dcErr
3093+
}
3094+
}
3095+
3096+
nMsg.err <- err
3097+
3098+
return nil, false
3099+
}
3100+
30593101
// Fetch the SCID we should be using to lock the channelMtx and make
30603102
// graph queries with.
30613103
graphScid, err := d.cfg.FindBaseByAlias(upd.ShortChannelID)

discovery/gossiper_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2930,6 +2930,78 @@ func TestExtraDataNodeAnnouncementValidation(t *testing.T) {
29302930
require.NoError(t, err, "unable to process announcement")
29312931
}
29322932

2933+
// TestZeroTimestampNodeAnnouncementRejection tests that a NodeAnnouncement with
2934+
// a zero timestamp is rejected per BOLT 7.
2935+
func TestZeroTimestampNodeAnnouncementRejection(t *testing.T) {
2936+
t.Parallel()
2937+
ctx := t.Context()
2938+
2939+
tCtx, err := createTestCtx(t, 0, false)
2940+
require.NoError(t, err, "can't create context")
2941+
2942+
remotePeer := &mockPeer{
2943+
remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{},
2944+
}
2945+
2946+
// Create a node announcement with a zero timestamp.
2947+
nodeAnn, err := createNodeAnnouncement(remoteKeyPriv1, 0)
2948+
require.NoError(t, err, "can't create node announcement")
2949+
2950+
// Processing the announcement should fail with a zero timestamp error.
2951+
select {
2952+
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
2953+
ctx, nodeAnn, remotePeer,
2954+
):
2955+
case <-time.After(2 * time.Second):
2956+
t.Fatal("did not process remote announcement")
2957+
}
2958+
require.Error(t, err)
2959+
require.Contains(t, err.Error(), "zero timestamp")
2960+
}
2961+
2962+
// TestZeroTimestampChannelUpdateRejection tests that a ChannelUpdate with a
2963+
// zero timestamp is rejected per BOLT 7.
2964+
func TestZeroTimestampChannelUpdateRejection(t *testing.T) {
2965+
t.Parallel()
2966+
ctx := t.Context()
2967+
2968+
tCtx, err := createTestCtx(t, 0, false)
2969+
require.NoError(t, err, "can't create context")
2970+
2971+
remotePeer := &mockPeer{
2972+
remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{},
2973+
}
2974+
2975+
// First, we need to process a channel announcement so that the channel
2976+
// update has a valid channel to refer to.
2977+
chanAnn, err := tCtx.createRemoteChannelAnnouncement(0)
2978+
require.NoError(t, err, "unable to create chan ann")
2979+
2980+
select {
2981+
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
2982+
ctx, chanAnn, remotePeer,
2983+
):
2984+
case <-time.After(2 * time.Second):
2985+
t.Fatal("did not process remote announcement")
2986+
}
2987+
require.NoError(t, err, "unable to process chan ann")
2988+
2989+
// Now create a channel update with a zero timestamp.
2990+
chanUpdAnn, err := createUpdateAnnouncement(0, 0, remoteKeyPriv1, 0)
2991+
require.NoError(t, err, "unable to create chan update")
2992+
2993+
// Processing the update should fail with a zero timestamp error.
2994+
select {
2995+
case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(
2996+
ctx, chanUpdAnn, remotePeer,
2997+
):
2998+
case <-time.After(2 * time.Second):
2999+
t.Fatal("did not process remote announcement")
3000+
}
3001+
require.Error(t, err)
3002+
require.Contains(t, err.Error(), "zero timestamp")
3003+
}
3004+
29333005
// assertBroadcast checks that num messages are being broadcasted from the
29343006
// gossiper. The broadcasted messages are returned.
29353007
func assertBroadcast(t *testing.T, ctx *testCtx, num int) []lnwire.Message {

docs/release-notes/release-notes-0.21.0.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@
112112
# Technical and Architectural Updates
113113
## BOLT Spec Updates
114114

115+
* [Enforce non-zero timestamps](https://github.com/lightningnetwork/lnd/pull/10469)
116+
for `channel_update` and `node_announcement` messages as required by BOLT 7.
117+
Gossip messages with zero timestamps are now rejected, and remote peers sending
118+
such invalid messages will have their ban score incremented.
119+
115120
## Testing
116121

117122
* [Added unit tests for TLV length validation across multiple packages](https://github.com/lightningnetwork/lnd/pull/10249).

0 commit comments

Comments
 (0)