Skip to content

Commit

Permalink
Merge da56aa3 into 9cba3e8
Browse files Browse the repository at this point in the history
  • Loading branch information
wpaulino committed Oct 25, 2018
2 parents 9cba3e8 + da56aa3 commit 4ca9943
Show file tree
Hide file tree
Showing 10 changed files with 617 additions and 92 deletions.
16 changes: 16 additions & 0 deletions chan_series.go
Expand Up @@ -89,6 +89,22 @@ func (c *chanSeries) UpdatesInHorizon(chain chainhash.Hash,
return nil, err
}
for _, nodeAnn := range nodeAnnsInHorizon {
// Ensure we only forward nodes that are publicly advertised to
// prevent leaking information about nodes.
isNodePublic, err := c.graph.IsPublicNode(nodeAnn.PubKeyBytes)
if err != nil {
srvrLog.Errorf("Unable to determine if node %x is "+
"advertised: %v", nodeAnn.PubKeyBytes, err)
continue
}

if !isNodePublic {
srvrLog.Tracef("Skipping forwarding announcement for "+
"node %x due to being unadvertised",
nodeAnn.PubKeyBytes)
continue
}

nodeUpdate, err := nodeAnn.NodeAnnouncement(true)
if err != nil {
return nil, err
Expand Down
71 changes: 71 additions & 0 deletions channeldb/graph.go
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"image/color"
"io"
Expand Down Expand Up @@ -1804,6 +1805,47 @@ func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement,
return nodeAnn, nil
}

// isPublic determines whether the node is seen as public within the graph from
// the source node's point of view. An existing database transaction can also be
// specified.
func (l *LightningNode) isPublic(tx *bolt.Tx, sourcePubKey []byte) (bool, error) {
// In order to determine whether this node is publicly advertised within
// the graph, we'll need to look at all of its edges and check whether
// they extend to any other node than the source node. errDone will be
// used to terminate the check early.
nodeIsPublic := false
errDone := errors.New("done")
err := l.ForEachChannel(tx, func(_ *bolt.Tx, info *ChannelEdgeInfo,
_, _ *ChannelEdgePolicy) error {

// If this edge doesn't extend to the source node, we'll
// terminate our search as we can now conclude that the node is
// publicly advertised within the graph due to the local node
// knowing of the current edge.
if !bytes.Equal(info.NodeKey1Bytes[:], sourcePubKey) &&
!bytes.Equal(info.NodeKey2Bytes[:], sourcePubKey) {

nodeIsPublic = true
return errDone
}

// Since the edge _does_ extend to the source node, we'll also
// need to ensure that this is a public edge.
if info.AuthProof != nil {
nodeIsPublic = true
return errDone
}

// Otherwise, we'll continue our search.
return nil
})
if err != nil && err != errDone {
return false, err
}

return nodeIsPublic, nil
}

// FetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned.
Expand Down Expand Up @@ -2566,6 +2608,35 @@ func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (*ChannelEdgeInfo, *
return edgeInfo, policy1, policy2, nil
}

// IsPublicNode is a helper method that determines whether the node with the
// given public key is seen as a public node in the graph from the graph's
// source node's point of view.
func (c *ChannelGraph) IsPublicNode(pubKey [33]byte) (bool, error) {
var nodeIsPublic bool
err := c.db.View(func(tx *bolt.Tx) error {
nodes := tx.Bucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
ourPubKey := nodes.Get(sourceKey)
if ourPubKey == nil {
return ErrSourceNodeNotSet
}
node, err := fetchLightningNode(nodes, pubKey[:])
if err != nil {
return err
}

nodeIsPublic, err = node.isPublic(tx, ourPubKey)
return err
})
if err != nil {
return false, err
}

return nodeIsPublic, nil
}

// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys.
func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
Expand Down
156 changes: 156 additions & 0 deletions channeldb/graph_test.go
Expand Up @@ -2438,6 +2438,162 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) {
}
}

// TestNodeIsPublic ensures that we properly detect nodes that are seen as
// public within the network graph.
func TestNodeIsPublic(t *testing.T) {
t.Parallel()

// We'll start off the test by creating a small network of 3
// participants with the following graph:
//
// Alice <-> Bob <-> Carol
//
// We'll need to create a separate database and channel graph for each
// participant to replicate real-world scenarios (private edges being in
// some graphs but not others, etc.).
aliceDB, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
t.Fatalf("unable to make test database: %v", err)
}
aliceNode, err := createTestVertex(aliceDB)
if err != nil {
t.Fatalf("unable to create test node: %v", err)
}
aliceGraph := aliceDB.ChannelGraph()
if err := aliceGraph.SetSourceNode(aliceNode); err != nil {
t.Fatalf("unable to set source node: %v", err)
}

bobDB, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
t.Fatalf("unable to make test database: %v", err)
}
bobNode, err := createTestVertex(bobDB)
if err != nil {
t.Fatalf("unable to create test node: %v", err)
}
bobGraph := bobDB.ChannelGraph()
if err := bobGraph.SetSourceNode(bobNode); err != nil {
t.Fatalf("unable to set source node: %v", err)
}

carolDB, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
t.Fatalf("unable to make test database: %v", err)
}
carolNode, err := createTestVertex(carolDB)
if err != nil {
t.Fatalf("unable to create test node: %v", err)
}
carolGraph := carolDB.ChannelGraph()
if err := carolGraph.SetSourceNode(carolNode); err != nil {
t.Fatalf("unable to set source node: %v", err)
}

aliceBobEdge, _ := createEdge(10, 0, 0, 0, aliceNode, bobNode)
bobCarolEdge, _ := createEdge(10, 1, 0, 1, bobNode, carolNode)

// After creating all of our nodes and edges, we'll add them to each
// participant's graph.
nodes := []*LightningNode{aliceNode, bobNode, carolNode}
edges := []*ChannelEdgeInfo{&aliceBobEdge, &bobCarolEdge}
dbs := []*DB{aliceDB, bobDB, carolDB}
graphs := []*ChannelGraph{aliceGraph, bobGraph, carolGraph}
for i, graph := range graphs {
for _, node := range nodes {
node.db = dbs[i]
if err := graph.AddLightningNode(node); err != nil {
t.Fatalf("unable to add node: %v", err)
}
}
for _, edge := range edges {
edge.db = dbs[i]
if err := graph.AddChannelEdge(edge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
}
}

// checkNodes is a helper closure that will be used to assert that the
// given nodes are seen as public/private within the given graphs.
checkNodes := func(nodes []*LightningNode, graphs []*ChannelGraph,
public bool) {

t.Helper()

for _, node := range nodes {
for _, graph := range graphs {
isPublic, err := graph.IsPublicNode(node.PubKeyBytes)
if err != nil {
t.Fatalf("unable to determine if pivot "+
"is public: %v", err)
}

switch {
case isPublic && !public:
t.Fatalf("expected %x to be private",
node.PubKeyBytes)
case !isPublic && public:
t.Fatalf("expected %x to be public",
node.PubKeyBytes)
}
}
}
}

// Due to the way the edges were set up above, we'll make sure each node
// can correctly determine that every other node is public.
checkNodes(nodes, graphs, true)

// Now, we'll remove the edge between Alice and Bob from everyone's
// graph. This will make Alice be seen as a private node as it no longer
// has any advertised edges.
for _, graph := range graphs {
err := graph.DeleteChannelEdge(&aliceBobEdge.ChannelPoint)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}
}
checkNodes(
[]*LightningNode{aliceNode},
[]*ChannelGraph{bobGraph, carolGraph},
false,
)

// We'll also make the edge between Bob and Carol private. Within Bob's
// and Carol's graph, the edge will exist, but it will not have a proof
// that allows it to be advertised. Within Alice's graph, we'll
// completely remove the edge as it is not possible for her to know of
// it without it being advertised.
for i, graph := range graphs {
err := graph.DeleteChannelEdge(&bobCarolEdge.ChannelPoint)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}

if graph == aliceGraph {
continue
}

bobCarolEdge.AuthProof = nil
bobCarolEdge.db = dbs[i]
if err := graph.AddChannelEdge(&bobCarolEdge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
}

// With the modifications above, Bob should now be seen as a private
// node from both Alice's and Carol's perspective.
checkNodes(
[]*LightningNode{bobNode},
[]*ChannelGraph{aliceGraph, carolGraph},
false,
)
}

// compareNodes is used to compare two LightningNodes while excluding the
// Features struct, which cannot be compared as the semantics for reserializing
// the featuresMap have not been defined.
Expand Down
27 changes: 20 additions & 7 deletions discovery/gossiper.go
Expand Up @@ -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
Expand Down

0 comments on commit 4ca9943

Please sign in to comment.