diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 641f4c9ed..88fd401aa 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -1613,13 +1613,26 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( return nil } - // Node announcement was successfully proceeded and know it - // might be broadcast to other connected nodes. - announcements = append(announcements, networkMsg{ - peer: nMsg.peer, - source: nMsg.source, - msg: msg, - }) + // In order to ensure we don't leak unadvertised nodes, we'll + // make a quick check to ensure this node intends to publicly + // advertise itself to the network. + isPublic, err := d.cfg.Router.IsPublicNode(node.PubKeyBytes) + if err != nil { + log.Errorf("Unable to determine if node %x is "+ + "advertised: %v", node.PubKeyBytes, err) + nMsg.err <- err + return nil + } + + // If it does, we'll add their announcement to our batch so that + // it can be broadcast to the rest of our peers. + if isPublic { + announcements = append(announcements, networkMsg{ + peer: nMsg.peer, + source: nMsg.source, + msg: msg, + }) + } nMsg.err <- nil // TODO(roasbeef): get rid of the above diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 560125259..56d6bdfd7 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -1,23 +1,19 @@ package discovery import ( + "bytes" "encoding/hex" "fmt" + "io/ioutil" + "math/big" + prand "math/rand" "net" + "os" "reflect" "sync" - - prand "math/rand" - "testing" - - "math/big" - "time" - "io/ioutil" - "os" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -213,6 +209,22 @@ func (r *mockGraphSource) IsStaleNode(nodePub routing.Vertex, timestamp time.Tim return false } +// IsPublicNode determines whether the given vertex is seen as a public node in +// the graph from the graph's source node's point of view. +func (r *mockGraphSource) IsPublicNode(node routing.Vertex) (bool, error) { + for _, info := range r.infos { + if !bytes.Equal(node[:], info.NodeKey1Bytes[:]) && + !bytes.Equal(node[:], info.NodeKey2Bytes[:]) { + continue + } + + if info.AuthProof != nil { + return true, nil + } + } + return false, nil +} + // IsKnownEdge returns true if the graph source already knows of the passed // channel ID. func (r *mockGraphSource) IsKnownEdge(chanID lnwire.ShortChannelID) bool { @@ -441,10 +453,9 @@ func createUpdateAnnouncement(blockHeight uint32, flags lnwire.ChanUpdateFlag, return a, nil } -func createRemoteChannelAnnouncement(blockHeight uint32, - extraBytes ...[]byte) (*lnwire.ChannelAnnouncement, error) { +func createAnnouncementWithoutProof(blockHeight uint32, + extraBytes ...[]byte) *lnwire.ChannelAnnouncement { - var err error a := &lnwire.ChannelAnnouncement{ ShortChannelID: lnwire.ShortChannelID{ BlockHeight: blockHeight, @@ -461,6 +472,14 @@ func createRemoteChannelAnnouncement(blockHeight uint32, a.ExtraOpaqueData = extraBytes[0] } + return a +} + +func createRemoteChannelAnnouncement(blockHeight uint32, + extraBytes ...[]byte) (*lnwire.ChannelAnnouncement, error) { + + a := createAnnouncementWithoutProof(blockHeight, extraBytes...) + pub := nodeKeyPriv1.PubKey() signer := mockSigner{nodeKeyPriv1} sig, err := SignAnnouncement(&signer, pub, a) @@ -1966,6 +1985,115 @@ func TestDeDuplicatedAnnouncements(t *testing.T) { } } +// TestForwardPrivateNodeAnnouncement ensures that we do not forward node +// announcements for nodes who do not intend to publicly advertise themselves. +func TestForwardPrivateNodeAnnouncement(t *testing.T) { + t.Parallel() + + const ( + startingHeight = 100 + timestamp = 123456 + ) + + ctx, cleanup, err := createTestCtx(startingHeight) + if err != nil { + t.Fatalf("can't create context: %v", err) + } + defer cleanup() + + // We'll start off by processing a channel announcement without a proof + // (i.e., an unadvertised channel), followed by a node announcement for + // this same channel announcement. + chanAnn := createAnnouncementWithoutProof(startingHeight - 2) + pubKey := nodeKeyPriv1.PubKey() + + select { + case err := <-ctx.gossiper.ProcessLocalAnnouncement(chanAnn, pubKey): + if err != nil { + t.Fatalf("unable to process local announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatalf("local announcement not processed") + } + + // The gossiper should not broadcast the announcement due to it not + // having its announcement signatures. + select { + case <-ctx.broadcastedMessage: + t.Fatal("gossiper should not have broadcast channel announcement") + case <-time.After(2 * trickleDelay): + } + + nodeAnn, err := createNodeAnnouncement(nodeKeyPriv1, timestamp) + if err != nil { + t.Fatalf("unable to create node announcement: %v", err) + } + + select { + case err := <-ctx.gossiper.ProcessLocalAnnouncement(nodeAnn, pubKey): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } + + // The gossiper should also not broadcast the node announcement due to + // it not being part of any advertised channels. + select { + case <-ctx.broadcastedMessage: + t.Fatal("gossiper should not have broadcast node announcement") + case <-time.After(2 * trickleDelay): + } + + // Now, we'll attempt to forward the NodeAnnouncement for the same node + // by opening a public channel on the network. We'll create a + // ChannelAnnouncement and hand it off to the gossiper in order to + // process it. + remoteChanAnn, err := createRemoteChannelAnnouncement(startingHeight - 1) + if err != nil { + t.Fatalf("unable to create remote channel announcement: %v", err) + } + peer := &mockPeer{pubKey, nil, nil} + + select { + case err := <-ctx.gossiper.ProcessRemoteAnnouncement(remoteChanAnn, peer): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } + + select { + case <-ctx.broadcastedMessage: + case <-time.After(2 * trickleDelay): + t.Fatal("gossiper should have broadcast the channel announcement") + } + + // We'll recreate the NodeAnnouncement with an updated timestamp to + // prevent a stale update. The NodeAnnouncement should now be forwarded. + nodeAnn, err = createNodeAnnouncement(nodeKeyPriv1, timestamp+1) + if err != nil { + t.Fatalf("unable to create node announcement: %v", err) + } + + select { + case err := <-ctx.gossiper.ProcessRemoteAnnouncement(nodeAnn, peer): + if err != nil { + t.Fatalf("unable to process remote announcement: %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("remote announcement not processed") + } + + select { + case <-ctx.broadcastedMessage: + case <-time.After(2 * trickleDelay): + t.Fatal("gossiper should have broadcast the node announcement") + } +} + // TestReceiveRemoteChannelUpdateFirst tests that if we receive a // ChannelUpdate from the remote before we have processed our // own ChannelAnnouncement, it will be reprocessed later, after