channeldb: support adding partial LightningNodes to graph.

Adds a HaveNodeAnnouncement field to the LightningNode
struct, which is used to indicate if we have gotten
all the necessary information to fill the remaining
fields in the struct. If we haven't gotten a node
announcement for this specific node, then we only
know the pubkey, and can only fill that field in
the struct. Still, we should be able to add it to the
channel graph and use it for routes, as long as we
know about channels to this node.
This commit is contained in:
Johan T. Halseth 2017-07-14 21:25:02 +02:00 committed by Olaoluwa Osuntokun
parent 24db310aef
commit bd0465ee1d
2 changed files with 149 additions and 26 deletions

@ -328,9 +328,13 @@ func (c *ChannelGraph) SetSourceNode(node *LightningNode) error {
})
}
// AddLightningNode adds a new (unconnected) vertex/node to the graph database.
// When adding an edge, each node must be added before the edge can be
// inserted. Afterwards the edge information can then be updated.
// AddLightningNode adds a vertex/node to the graph database. If the node is not
// in the database from before, this will add a new, unconnected one to the
// graph. If it is present from before, this will update that node's
// information. Note that this method is expected to only be called to update
// an already present node from a node annoucement, or to insert a node found
// in a channel update.
//
// TODO(roasbeef): also need sig of announcement
func (c *ChannelGraph) AddLightningNode(node *LightningNode) error {
return c.db.Update(func(tx *bolt.Tx) error {
@ -850,6 +854,15 @@ func (c *ChannelGraph) UpdateEdgePolicy(edge *ChannelEdgePolicy) error {
// from it. As the graph is directed, a node will also have an incoming edge
// attached to it for each outgoing edge.
type LightningNode struct {
// PubKey is the node's long-term identity public key. This key will be
// used to authenticated any advertisements/updates sent by the node.
PubKey *btcec.PublicKey
// HaveNodeAnnouncement indicates whether we received a node annoucement
// for this particular node. If true, the remaining fields will be set,
// if false only the PubKey is known for this node.
HaveNodeAnnouncement bool
// LastUpdate is the last time the vertex information for this node has
// been updated.
LastUpdate time.Time
@ -857,10 +870,6 @@ type LightningNode struct {
// Address is the TCP address this node is reachable over.
Addresses []net.Addr
// PubKey is the node's long-term identity public key. This key will be
// used to authenticated any advertisements/updates sent by the node.
PubKey *btcec.PublicKey
// Color is the selected color for the node.
Color color.RGBA
@ -1384,11 +1393,12 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, node *L
nodePub := node.PubKey.SerializeCompressed()
if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil {
return err
// If the node has the update time set, write it, else write 0.
updateUnix := uint64(0)
if node.LastUpdate.Unix() > 0 {
updateUnix = uint64(node.LastUpdate.Unix())
}
updateUnix := uint64(node.LastUpdate.Unix())
byteOrder.PutUint64(scratch[:8], updateUnix)
if _, err := b.Write(scratch[:8]); err != nil {
return err
@ -1398,6 +1408,24 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, node *L
return err
}
// If we got a node announcement for this node, we will have the rest of
// the data available. If not we don't have more data to write.
if !node.HaveNodeAnnouncement {
// Write HaveNodeAnnouncement=0.
byteOrder.PutUint16(scratch[:2], 0)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
// Write HaveNodeAnnouncement=1.
byteOrder.PutUint16(scratch[:2], 1)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.R); err != nil {
return err
}
@ -1456,7 +1484,12 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket, node *L
return err
}
if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
func fetchLightningNode(nodeBucket *bolt.Bucket,
@ -1492,6 +1525,25 @@ func deserializeLightningNode(r io.Reader) (*LightningNode, error) {
return nil, err
}
if _, err := r.Read(scratch[:2]); err != nil {
return nil, err
}
hasNodeAnn := byteOrder.Uint16(scratch[:2])
if hasNodeAnn == 1 {
node.HaveNodeAnnouncement = true
} else {
node.HaveNodeAnnouncement = false
}
// The rest of the data is optional, and will only be there if we got a node
// announcement for this node.
if !node.HaveNodeAnnouncement {
return node, nil
}
// We did get a node announcement for this node, so we'll have the rest
// of the data available.
if err := binary.Read(r, byteOrder, &node.Color.R); err != nil {
return nil, err
}

@ -51,14 +51,15 @@ func createTestVertex(db *DB) (*LightningNode, error) {
pub := priv.PubKey().SerializeCompressed()
return &LightningNode{
AuthSig: testSig,
LastUpdate: time.Unix(updateTime, 0),
PubKey: priv.PubKey(),
Color: color.RGBA{1, 2, 3, 0},
Alias: "kek" + string(pub[:]),
Features: testFeatures,
Addresses: testAddrs,
db: db,
HaveNodeAnnouncement: true,
AuthSig: testSig,
LastUpdate: time.Unix(updateTime, 0),
PubKey: priv.PubKey(),
Color: color.RGBA{1, 2, 3, 0},
Alias: "kek" + string(pub[:]),
Features: testFeatures,
Addresses: testAddrs,
db: db,
}, nil
}
@ -77,14 +78,15 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// graph, so we'll create a test vertex to start with.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{
AuthSig: testSig,
LastUpdate: time.Unix(1232342, 0),
PubKey: testPub,
Color: color.RGBA{1, 2, 3, 0},
Alias: "kek",
Features: testFeatures,
Addresses: testAddrs,
db: db,
HaveNodeAnnouncement: true,
AuthSig: testSig,
LastUpdate: time.Unix(1232342, 0),
PubKey: testPub,
Color: color.RGBA{1, 2, 3, 0},
Alias: "kek",
Features: testFeatures,
Addresses: testAddrs,
db: db,
}
// First, insert the node into the graph DB. This should succeed
@ -125,6 +127,71 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
}
}
// TestPartialNode checks that we can add and retrieve a LightningNode where
// where only the pubkey is known to the database.
func TestPartialNode(t *testing.T) {
t.Parallel()
db, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
t.Fatalf("unable to make test database: %v", err)
}
graph := db.ChannelGraph()
// We want to be able to insert nodes into the graph that only has the
// PubKey set.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{
PubKey: testPub,
HaveNodeAnnouncement: false,
}
if err := graph.AddLightningNode(node); err != nil {
t.Fatalf("unable to add node: %v", err)
}
// Next, fetch the node from the database to ensure everything was
// serialized properly.
dbNode, err := graph.FetchLightningNode(testPub)
if err != nil {
t.Fatalf("unable to locate node: %v", err)
}
if _, exists, err := graph.HasLightningNode(testPub); err != nil {
t.Fatalf("unable to query for node: %v", err)
} else if !exists {
t.Fatalf("node should be found but wasn't")
}
// The two nodes should match exactly! (with default values for
// LastUpdate and db set to satisfy compareNodes())
node = &LightningNode{
PubKey: testPub,
HaveNodeAnnouncement: false,
LastUpdate: time.Unix(0, 0),
db: db,
}
if err := compareNodes(node, dbNode); err != nil {
t.Fatalf("nodes don't match: %v", err)
}
// Next, delete the node from the graph, this should purge all data
// related to the node.
if err := graph.DeleteLightningNode(testPub); err != nil {
t.Fatalf("unable to delete node; %v", err)
}
// Finally, attempt to fetch the node again. This should fail as the
// node should've been deleted from the database.
_, err = graph.FetchLightningNode(testPub)
if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!")
}
}
func TestAliasLookup(t *testing.T) {
t.Parallel()
@ -929,6 +996,10 @@ func compareNodes(a, b *LightningNode) error {
return fmt.Errorf("db doesn't match: expected %#v, \n "+
"got %#v", a.db, b.db)
}
if !reflect.DeepEqual(a.HaveNodeAnnouncement, b.HaveNodeAnnouncement) {
return fmt.Errorf("HaveNodeAnnouncement doesn't match: expected %#v, \n "+
"got %#v", a.HaveNodeAnnouncement, b.HaveNodeAnnouncement)
}
return nil
}