From b0815a1fd6bb2e9c2ad450ddf9fef0875b9437a0 Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Thu, 25 Apr 2024 13:47:21 +0100 Subject: [PATCH 1/9] channeldb: add persist nodeannounment config in db In this commit, we save nodeannouncement config in the database so that we will be able to retrieve the config later and use them on retart Signed-off-by: Abdullahi Yunus --- channeldb/error.go | 10 +++ channeldb/node.go | 175 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 channeldb/node.go diff --git a/channeldb/error.go b/channeldb/error.go index 859af974648..d9411cb6916 100644 --- a/channeldb/error.go +++ b/channeldb/error.go @@ -108,6 +108,16 @@ var ( // channel with a channel point that is already present in the // database. ErrChanAlreadyExists = fmt.Errorf("channel already exists") + + // ErrNodeAnnBucketNotFound is returned when the nodeannouncement + // bucket hasn't been created yet. + ErrNodeAnnBucketNotFound = fmt.Errorf("no node announcement bucket " + + "exist") + + // ErrNodeAnnNotFound is returned when we're unable to find the target + // node announcement. + ErrNodeAnnNotFound = fmt.Errorf("node announcement with target " + + "identity not found") ) // ErrTooManyExtraOpaqueBytes creates an error which should be returned if the diff --git a/channeldb/node.go b/channeldb/node.go new file mode 100644 index 00000000000..9d20601a161 --- /dev/null +++ b/channeldb/node.go @@ -0,0 +1,175 @@ +package channeldb + +import ( + "bytes" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + // nodeAnnouncementBucket stores announcement config pertaining to node. + // This bucket allows one to query for persisted node announcement + // config and use it when starting orrestarting a node + nodeAnnouncementBucket = []byte("nab") +) + +type NodeAnnouncement struct { + // Alias indicate the human readable name that a node operator can + // assign to their node for better readability and easier identification + Alias string + + // Color represent the hexadecimal value that node operators can assign + // to their nodes. It's represented as a hex string. + Color string + + // IdentityPub is the node's current identity public key. Any + // channel/topology related information received by this node MUST be + // signed by this public key. + IdentityPub *btcec.PublicKey + + db *NodeAnnouncementDB +} + +type NodeAnnouncementDB struct { + backend kvdb.Backend +} + +func NewNodeAnnouncement(db *NodeAnnouncementDB, identityPub *btcec.PublicKey, alias, color string) *NodeAnnouncement { + + return &NodeAnnouncement{ + Alias: alias, + IdentityPub: identityPub, + Color: color, + db: db, + } +} + +// FetchNodeAnnouncement attempts to lookup the data for NodeAnnouncement based +// on a target identity public key. If a particular NodeAnnouncement for the +// passed identity public key cannot be found, then returns ErrNodeAnnNotFound +func (l *NodeAnnouncementDB) FetchNodeAnnouncement(identity *btcec.PublicKey) (*NodeAnnouncement, error) { + var nodeAnnouncement *NodeAnnouncement + err := kvdb.View(l.backend, func(tx kvdb.RTx) error { + nodeAnn, err := fetchNodeAnnouncement(tx, identity) + if err != nil { + return err + } + + nodeAnnouncement = nodeAnn + return nil + }, func() { + nodeAnnouncement = nil + }) + + return nodeAnnouncement, err +} + +func fetchNodeAnnouncement(tx kvdb.RTx, targetPub *btcec.PublicKey) (*NodeAnnouncement, error) { + // First fetch the bucket for storing node announcement, bailing out + // early if it hasn't been created yet. + nodeAnnBucket := tx.ReadBucket(nodeAnnouncementBucket) + if nodeAnnBucket == nil { + return nil, ErrNodeAnnBucketNotFound + } + + // If a node announcement for that particular public key cannot be + // located, then exit early with ErrNodeAnnNotFound + pubkey := targetPub.SerializeCompressed() + nodeAnnBytes := nodeAnnBucket.Get(pubkey) + if nodeAnnBytes == nil { + return nil, ErrNodeAnnNotFound + } + + // FInally, decode and allocate a fresh NodeAnnouncement object to be + // returned to the caller + nodeAnnReader := bytes.NewReader(nodeAnnBytes) + return deserializeNodeAnnouncement(nodeAnnReader) + +} + +func (n *NodeAnnouncement) PutNodeAnnouncement(pubkey *btcec.PublicKey, alias, color string) error { + nodeAnn := NewNodeAnnouncement(n.db, pubkey, alias, color) + + return kvdb.Update(n.db.backend, func(tx kvdb.RwTx) error { + nodeAnnBucket := tx.ReadWriteBucket(nodeAnnouncementBucket) + if nodeAnnBucket == nil { + _, err := tx.CreateTopLevelBucket(nodeAnnouncementBucket) + if err != nil { + return err + } + + nodeAnnBucket = tx.ReadWriteBucket(nodeAnnouncementBucket) + if nodeAnnBucket == nil { + return ErrNodeAnnBucketNotFound + } + } + + return putNodeAnnouncement(nodeAnnBucket, nodeAnn) + }, func() {}) +} + +func putNodeAnnouncement(nodeAnnBucket kvdb.RwBucket, n *NodeAnnouncement) error { + // First serialize the NodeAnnouncement into raw-bytes encoding. + var b bytes.Buffer + if err := serializeNodeAnnouncement(&b, n); err != nil { + return err + } + + // Finally insert the link-node into the node metadata bucket keyed + // according to the its pubkey serialized in compressed form. + nodePub := n.IdentityPub.SerializeCompressed() + return nodeAnnBucket.Put(nodePub, b.Bytes()) +} + +func serializeNodeAnnouncement(w io.Writer, n *NodeAnnouncement) error { + // Serialize Alias + if _, err := w.Write([]byte(n.Alias + "\x00")); err != nil { + return err + } + + // Serialize Color + if _, err := w.Write([]byte(n.Color + "\x00")); err != nil { + return err + } + + // Serialize IdentityPub + serializedID := n.IdentityPub.SerializeCompressed() + if _, err := w.Write(serializedID); err != nil { + return err + } + + return nil +} + +func deserializeNodeAnnouncement(r io.Reader) (*NodeAnnouncement, error) { + var err error + nodeAnn := &NodeAnnouncement{} + + // Read Alias + aliasBuf := make([]byte, 32) + if _, err := io.ReadFull(r, aliasBuf); err != nil { + return nil, err + } + nodeAnn.Alias = string(bytes.TrimRight(aliasBuf, "\x00")) + + // Read Color + colorBuf := make([]byte, 8) + if _, err := io.ReadFull(r, colorBuf); err != nil { + return nil, err + } + nodeAnn.Color = string(bytes.TrimRight(colorBuf, "\x00")) + + var pub [33]byte + if _, err := io.ReadFull(r, pub[:]); err != nil { + return nil, err + } + nodeAnn.IdentityPub, err = btcec.ParsePubKey(pub[:]) + if err != nil { + return nil, err + } + + return nodeAnn, err + +} From d7719d880acdcfd236a998518960307c7c0f220f Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Tue, 30 Apr 2024 23:58:05 +0100 Subject: [PATCH 2/9] channeldb: check for persisted node announcement alias In this commit, we check for nodeannouncement alias in the db before creating a disk representation of a node. If there none, we default to first 10 characters of our pubkey. --- channeldb/db.go | 9 +++++++++ server.go | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/channeldb/db.go b/channeldb/db.go index 93bb239bb65..099b3fe2a16 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -447,6 +447,7 @@ var dbTopLevelBuckets = [][]byte{ outpointBucket, chanIDBucket, historicalChannelBucket, + nodeAnnouncementBucket, } // Wipe completely deletes all saved state within all used buckets within the @@ -519,6 +520,9 @@ type ChannelStateDB struct { // linkNodeDB separates all DB operations on LinkNodes. linkNodeDB *LinkNodeDB + // nodeAnnouncementDB seperates all DB operations on NodeAnnouncements. + nodeAnnouncementDb *NodeAnnouncementDB + // parent holds a pointer to the "main" channeldb.DB object. This is // only used for testing and should never be used in production code. // For testing use the ChannelStateDB.GetParentDB() function to retrieve @@ -542,6 +546,11 @@ func (c *ChannelStateDB) LinkNodeDB() *LinkNodeDB { return c.linkNodeDB } +// nodeAnnouncementDB returns the current instance of the nodeannouncement database. +func (c *ChannelStateDB) NodeAnnouncemenDB() *NodeAnnouncementDB { + return c.nodeAnnouncementDb +} + // FetchOpenChannels starts a new database transaction and returns all stored // currently active/open channels associated with the target nodeID. In the case // that no active channels are known to have been created with this node, then a diff --git a/server.go b/server.go index 2979880a291..e44b1e21825 100644 --- a/server.go +++ b/server.go @@ -816,6 +816,13 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // If no alias is provided, default to first 10 characters of public // key. alias := cfg.Alias + if alias == "" { + pubkey := nodeKeyDesc.PubKey + persistedAlias, err := s.miscDB.ChannelStateDB().NodeAnnouncemenDB().FetchNodeAnnouncement(pubkey) + if err == nil && persistedAlias.Alias != "" { + alias = persistedAlias.Alias + } + } if alias == "" { alias = hex.EncodeToString(serializedPubKey[:10]) } From 815e06989ac7760f8ace7494ada827128a044894 Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Sun, 26 May 2024 06:04:11 +0100 Subject: [PATCH 3/9] channeldb+lnd: check for persisted alias in disk In this commit, we check for alias that are persisted in disk and use them when the node is restarting. --- channeldb/db.go | 14 ++++----- channeldb/node.go | 72 +++++++++++++++++------------------------------ server.go | 22 +++++++++------ 3 files changed, 45 insertions(+), 63 deletions(-) diff --git a/channeldb/db.go b/channeldb/db.go index 099b3fe2a16..2b340c7204a 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -286,6 +286,12 @@ var ( number: 31, migration: migration31.DeleteLastPublishedTxTLB, }, + { + // Create a top level bucket which holds information + // about our node announcement. + number: 32, + migration: mig.CreateTLB(nodeAnnouncementBucket), + }, } // optionalVersions stores all optional migrations that are applied @@ -520,9 +526,6 @@ type ChannelStateDB struct { // linkNodeDB separates all DB operations on LinkNodes. linkNodeDB *LinkNodeDB - // nodeAnnouncementDB seperates all DB operations on NodeAnnouncements. - nodeAnnouncementDb *NodeAnnouncementDB - // parent holds a pointer to the "main" channeldb.DB object. This is // only used for testing and should never be used in production code. // For testing use the ChannelStateDB.GetParentDB() function to retrieve @@ -546,11 +549,6 @@ func (c *ChannelStateDB) LinkNodeDB() *LinkNodeDB { return c.linkNodeDB } -// nodeAnnouncementDB returns the current instance of the nodeannouncement database. -func (c *ChannelStateDB) NodeAnnouncemenDB() *NodeAnnouncementDB { - return c.nodeAnnouncementDb -} - // FetchOpenChannels starts a new database transaction and returns all stored // currently active/open channels associated with the target nodeID. In the case // that no active channels are known to have been created with this node, then a diff --git a/channeldb/node.go b/channeldb/node.go index 9d20601a161..fd066f0a5a6 100644 --- a/channeldb/node.go +++ b/channeldb/node.go @@ -28,30 +28,14 @@ type NodeAnnouncement struct { // channel/topology related information received by this node MUST be // signed by this public key. IdentityPub *btcec.PublicKey - - db *NodeAnnouncementDB -} - -type NodeAnnouncementDB struct { - backend kvdb.Backend -} - -func NewNodeAnnouncement(db *NodeAnnouncementDB, identityPub *btcec.PublicKey, alias, color string) *NodeAnnouncement { - - return &NodeAnnouncement{ - Alias: alias, - IdentityPub: identityPub, - Color: color, - db: db, - } } // FetchNodeAnnouncement attempts to lookup the data for NodeAnnouncement based // on a target identity public key. If a particular NodeAnnouncement for the // passed identity public key cannot be found, then returns ErrNodeAnnNotFound -func (l *NodeAnnouncementDB) FetchNodeAnnouncement(identity *btcec.PublicKey) (*NodeAnnouncement, error) { +func (d *DB) FetchNodeAnnouncement(identity *btcec.PublicKey) (*NodeAnnouncement, error) { var nodeAnnouncement *NodeAnnouncement - err := kvdb.View(l.backend, func(tx kvdb.RTx) error { + err := kvdb.View(d, func(tx kvdb.RTx) error { nodeAnn, err := fetchNodeAnnouncement(tx, identity) if err != nil { return err @@ -89,38 +73,34 @@ func fetchNodeAnnouncement(tx kvdb.RTx, targetPub *btcec.PublicKey) (*NodeAnnoun } -func (n *NodeAnnouncement) PutNodeAnnouncement(pubkey *btcec.PublicKey, alias, color string) error { - nodeAnn := NewNodeAnnouncement(n.db, pubkey, alias, color) - - return kvdb.Update(n.db.backend, func(tx kvdb.RwTx) error { - nodeAnnBucket := tx.ReadWriteBucket(nodeAnnouncementBucket) - if nodeAnnBucket == nil { - _, err := tx.CreateTopLevelBucket(nodeAnnouncementBucket) - if err != nil { - return err - } - - nodeAnnBucket = tx.ReadWriteBucket(nodeAnnouncementBucket) - if nodeAnnBucket == nil { - return ErrNodeAnnBucketNotFound - } +func (d *DB) PutNodeAnnouncement(pubkey *btcec.PublicKey, alias, color string) error { + nodeAnn := &NodeAnnouncement{ + Alias: alias, + IdentityPub: pubkey, + Color: color, + } + + return kvdb.Update(d, func(tx kvdb.RwTx) error { + nodeAnnouncements := tx.ReadWriteBucket(nodeAnnouncementBucket) + + nodeAnnBucket, err := nodeAnnouncements.CreateBucketIfNotExists(pubkey.SerializeCompressed()) + if err != nil { + return err } - return putNodeAnnouncement(nodeAnnBucket, nodeAnn) - }, func() {}) -} + var b bytes.Buffer + if err := serializeNodeAnnouncement(&b, nodeAnn); err != nil { + return err + } -func putNodeAnnouncement(nodeAnnBucket kvdb.RwBucket, n *NodeAnnouncement) error { - // First serialize the NodeAnnouncement into raw-bytes encoding. - var b bytes.Buffer - if err := serializeNodeAnnouncement(&b, n); err != nil { - return err - } + err = nodeAnnBucket.Put(pubkey.SerializeCompressed(), b.Bytes()) + if err != nil { + return err + } - // Finally insert the link-node into the node metadata bucket keyed - // according to the its pubkey serialized in compressed form. - nodePub := n.IdentityPub.SerializeCompressed() - return nodeAnnBucket.Put(nodePub, b.Bytes()) + return nil + + }, func() {}) } func serializeNodeAnnouncement(w io.Writer, n *NodeAnnouncement) error { diff --git a/server.go b/server.go index e44b1e21825..5cfff09f190 100644 --- a/server.go +++ b/server.go @@ -806,23 +806,27 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // configuration so we can send it out as a sort of heart beat within // the network. // - // We'll start by parsing the node color from configuration. + // We'll start by getting the node color & alias from disk. + pubkey := nodeKeyDesc.PubKey + persistedConfig, _ := s.miscDB.FetchNodeAnnouncement(pubkey) + // if err != nil { + // return nil, err + // } + // We'll then be parsing the node color from configuration. color, err := lncfg.ParseHexColor(cfg.Color) if err != nil { srvrLog.Errorf("unable to parse color: %v\n", err) return nil, err } - // If no alias is provided, default to first 10 characters of public - // key. + // If no alias is provided, check if one is stored in the database and + // use that. alias := cfg.Alias - if alias == "" { - pubkey := nodeKeyDesc.PubKey - persistedAlias, err := s.miscDB.ChannelStateDB().NodeAnnouncemenDB().FetchNodeAnnouncement(pubkey) - if err == nil && persistedAlias.Alias != "" { - alias = persistedAlias.Alias - } + if alias == "" && persistedConfig != nil { + alias = persistedConfig.Alias } + // If no alias is provided, default to first 10 characters of public + // key. if alias == "" { alias = hex.EncodeToString(serializedPubKey[:10]) } From 75ffcafae460494bc7efadea82044c31cad490ec Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Fri, 31 May 2024 09:10:53 +0100 Subject: [PATCH 4/9] channeldb: remove migration for nodeannouncemnet bucket --- channeldb/db.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/channeldb/db.go b/channeldb/db.go index 2b340c7204a..aebc31a7362 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -286,12 +286,6 @@ var ( number: 31, migration: migration31.DeleteLastPublishedTxTLB, }, - { - // Create a top level bucket which holds information - // about our node announcement. - number: 32, - migration: mig.CreateTLB(nodeAnnouncementBucket), - }, } // optionalVersions stores all optional migrations that are applied From 03e2ac6b9a0bdc3e73c190a92c2e362de0988455 Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Sun, 2 Jun 2024 22:44:00 +0100 Subject: [PATCH 5/9] channeldb+peersrpc+lnd: save node announcement config whenever updated In this commit, we ensure new configurations are saved whenever node announcement is updated. --- channeldb/node.go | 92 +++++++++++++++++++-------------- lnrpc/peersrpc/config_active.go | 3 ++ lnrpc/peersrpc/peers_server.go | 5 ++ subrpcserver_config.go | 4 ++ 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/channeldb/node.go b/channeldb/node.go index fd066f0a5a6..45b2fadbd27 100644 --- a/channeldb/node.go +++ b/channeldb/node.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "image/color" "io" "github.com/btcsuite/btcd/btcec/v2" @@ -15,19 +16,28 @@ var ( nodeAnnouncementBucket = []byte("nab") ) +// NodeAlias is a hex encoded UTF-8 string that may be displayed as an +// alternative to the node's ID. Notice that aliases are not unique and may be +// freely chosen by the node operators. +type NodeAlias [32]byte + +// String returns a utf8 string representation of the alias bytes. +func (n NodeAlias) String() string { + // Trim trailing zero-bytes for presentation + return string(bytes.Trim(n[:], "\x00")) +} + type NodeAnnouncement struct { - // Alias indicate the human readable name that a node operator can - // assign to their node for better readability and easier identification - Alias string + // Alias is used to customize node's appearance in maps and + // graphs + Alias NodeAlias // Color represent the hexadecimal value that node operators can assign // to their nodes. It's represented as a hex string. - Color string + Color color.RGBA - // IdentityPub is the node's current identity public key. Any - // channel/topology related information received by this node MUST be - // signed by this public key. - IdentityPub *btcec.PublicKey + // NodeID is a public key which is used as node identification. + NodeID [33]byte } // FetchNodeAnnouncement attempts to lookup the data for NodeAnnouncement based @@ -35,6 +45,7 @@ type NodeAnnouncement struct { // passed identity public key cannot be found, then returns ErrNodeAnnNotFound func (d *DB) FetchNodeAnnouncement(identity *btcec.PublicKey) (*NodeAnnouncement, error) { var nodeAnnouncement *NodeAnnouncement + err := kvdb.View(d, func(tx kvdb.RTx) error { nodeAnn, err := fetchNodeAnnouncement(tx, identity) if err != nil { @@ -73,50 +84,56 @@ func fetchNodeAnnouncement(tx kvdb.RTx, targetPub *btcec.PublicKey) (*NodeAnnoun } -func (d *DB) PutNodeAnnouncement(pubkey *btcec.PublicKey, alias, color string) error { +func (d *DB) PutNodeAnnouncement(pubkey [33]byte, alias [32]byte, color color.RGBA) error { nodeAnn := &NodeAnnouncement{ - Alias: alias, - IdentityPub: pubkey, - Color: color, + Alias: alias, + Color: color, + NodeID: pubkey, } return kvdb.Update(d, func(tx kvdb.RwTx) error { - nodeAnnouncements := tx.ReadWriteBucket(nodeAnnouncementBucket) - - nodeAnnBucket, err := nodeAnnouncements.CreateBucketIfNotExists(pubkey.SerializeCompressed()) - if err != nil { - return err + nodeAnnBucket := tx.ReadWriteBucket(nodeAnnouncementBucket) + if nodeAnnBucket == nil { + return ErrNodeAnnBucketNotFound } - var b bytes.Buffer - if err := serializeNodeAnnouncement(&b, nodeAnn); err != nil { - return err - } + return putNodeAnnouncement(nodeAnnBucket, nodeAnn) - err = nodeAnnBucket.Put(pubkey.SerializeCompressed(), b.Bytes()) - if err != nil { - return err - } + }, func() {}) +} - return nil +func putNodeAnnouncement(nodeAnnBucket kvdb.RwBucket, n *NodeAnnouncement) error { + var b bytes.Buffer + if err := serializeNodeAnnouncement(&b, n); err != nil { + return err + } - }, func() {}) + nodePub := n.NodeID[:] + return nodeAnnBucket.Put(nodePub, b.Bytes()) } func serializeNodeAnnouncement(w io.Writer, n *NodeAnnouncement) error { // Serialize Alias - if _, err := w.Write([]byte(n.Alias + "\x00")); err != nil { + if _, err := w.Write([]byte(n.Alias[:])); err != nil { return err } // Serialize Color - if _, err := w.Write([]byte(n.Color + "\x00")); err != nil { + // Write R + if _, err := w.Write([]byte{n.Color.R}); err != nil { + return err + } + // Write G + if _, err := w.Write([]byte{n.Color.G}); err != nil { + return err + } + // Write B + if _, err := w.Write([]byte{n.Color.B}); err != nil { return err } // Serialize IdentityPub - serializedID := n.IdentityPub.SerializeCompressed() - if _, err := w.Write(serializedID); err != nil { + if _, err := w.Write(n.NodeID[:]); err != nil { return err } @@ -132,23 +149,22 @@ func deserializeNodeAnnouncement(r io.Reader) (*NodeAnnouncement, error) { if _, err := io.ReadFull(r, aliasBuf); err != nil { return nil, err } - nodeAnn.Alias = string(bytes.TrimRight(aliasBuf, "\x00")) + nodeAnn.Alias = [32]byte(aliasBuf) // Read Color - colorBuf := make([]byte, 8) + // colorBuf contains R, G, B, A (alpha), but the color.RGBA type only + // expects R, G, B, so we need to slice it. + colorBuf := make([]byte, 3) if _, err := io.ReadFull(r, colorBuf); err != nil { return nil, err } - nodeAnn.Color = string(bytes.TrimRight(colorBuf, "\x00")) + nodeAnn.Color = color.RGBA{colorBuf[0], colorBuf[1], colorBuf[2], 0} var pub [33]byte if _, err := io.ReadFull(r, pub[:]); err != nil { return nil, err } - nodeAnn.IdentityPub, err = btcec.ParsePubKey(pub[:]) - if err != nil { - return nil, err - } + nodeAnn.NodeID = pub return nodeAnn, err diff --git a/lnrpc/peersrpc/config_active.go b/lnrpc/peersrpc/config_active.go index 4a2f028e454..f93d1ab70a9 100644 --- a/lnrpc/peersrpc/config_active.go +++ b/lnrpc/peersrpc/config_active.go @@ -6,6 +6,7 @@ package peersrpc import ( "net" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" ) @@ -29,4 +30,6 @@ type Config struct { // vector should be provided. UpdateNodeAnnouncement func(features *lnwire.RawFeatureVector, mods ...netann.NodeAnnModifier) error + + ChanStateDB *channeldb.ChannelStateDB } diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go index 14e039f615d..b6c8d5a340a 100644 --- a/lnrpc/peersrpc/peers_server.go +++ b/lnrpc/peersrpc/peers_server.go @@ -398,5 +398,10 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context, return nil, err } + nodeAnnouncement := s.cfg.GetNodeAnnouncement() + s.cfg.ChanStateDB.GetParentDB().PutNodeAnnouncement( + nodeAnnouncement.NodeID, nodeAnnouncement.Alias, + nodeAnnouncement.RGBColor) + return resp, nil } diff --git a/subrpcserver_config.go b/subrpcserver_config.go index 6687f71a7d4..00c507a4b36 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -332,6 +332,10 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(updateNodeAnnouncement), ) + subCfgValue.FieldByName("ChanStateDB").Set( + reflect.ValueOf(chanStateDB), + ) + default: return fmt.Errorf("unknown field: %v, %T", fieldName, cfg) From 31820dcd086bf7157f79eec909eb7cb2775f771c Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Sun, 2 Jun 2024 23:46:42 +0100 Subject: [PATCH 6/9] lnd: determine source for alias and color Here we try to determine the alias and color source for our node when starting. The hierarchy is config set in lnd.conf take precedence over persisted config and finally the default. --- server.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index 5cfff09f190..85d3b5bb587 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "encoding/hex" "fmt" + "image/color" "math/big" prand "math/rand" "net" @@ -806,26 +807,35 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // configuration so we can send it out as a sort of heart beat within // the network. // - // We'll start by getting the node color & alias from disk. + // We'll start by getting the node announcement from disk. pubkey := nodeKeyDesc.PubKey - persistedConfig, _ := s.miscDB.FetchNodeAnnouncement(pubkey) - // if err != nil { - // return nil, err - // } - // We'll then be parsing the node color from configuration. - color, err := lncfg.ParseHexColor(cfg.Color) + persistedConfig, err := s.miscDB.FetchNodeAnnouncement(pubkey) if err != nil { - srvrLog.Errorf("unable to parse color: %v\n", err) - return nil, err + srvrLog.Errorf("unable to fetch node announcement: %v\n", err) + } + + var color color.RGBA + + // Determine the source of the color. #3399FF is the default + if cfg.Color == "#3399FF" && persistedConfig != nil { + color = persistedConfig.Color + } else { + var err error + color, err = lncfg.ParseHexColor(cfg.Color) + if err != nil { + srvrLog.Errorf("unable to parse color: %v\n", err) + return nil, err + } } // If no alias is provided, check if one is stored in the database and // use that. alias := cfg.Alias if alias == "" && persistedConfig != nil { - alias = persistedConfig.Alias + alias = persistedConfig.Alias.String() } - // If no alias is provided, default to first 10 characters of public + + // If still alias is empty, default to first 10 characters of public // key. if alias == "" { alias = hex.EncodeToString(serializedPubKey[:10]) From 9365f95240029fa5d538c27b6e2c3927a838b103 Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Tue, 4 Jun 2024 14:42:21 +0100 Subject: [PATCH 7/9] test: test persisting and retrieving node announcement --- channeldb/node.go | 13 +++++++++++ channeldb/node_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 channeldb/node_test.go diff --git a/channeldb/node.go b/channeldb/node.go index 45b2fadbd27..8959088170e 100644 --- a/channeldb/node.go +++ b/channeldb/node.go @@ -40,6 +40,19 @@ type NodeAnnouncement struct { NodeID [33]byte } +// Sync performs a full database sync which writes the current up-to-date data +// within the struct to the database. +func (n *NodeAnnouncement) Sync(db *DB) error { + return kvdb.Update(db, func(tx kvdb.RwTx) error { + nodeAnnBucket := tx.ReadWriteBucket(nodeAnnouncementBucket) + if nodeAnnBucket == nil { + return ErrNodeAnnBucketNotFound + } + + return putNodeAnnouncement(nodeAnnBucket, n) + }, func() {}) +} + // FetchNodeAnnouncement attempts to lookup the data for NodeAnnouncement based // on a target identity public key. If a particular NodeAnnouncement for the // passed identity public key cannot be found, then returns ErrNodeAnnNotFound diff --git a/channeldb/node_test.go b/channeldb/node_test.go new file mode 100644 index 00000000000..02b65bed695 --- /dev/null +++ b/channeldb/node_test.go @@ -0,0 +1,52 @@ +package channeldb + +import ( + "image/color" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +func TestNodeAnnouncementEncodeDecode(t *testing.T) { + t.Parallel() + + fullDB, err := MakeTestDB(t) + require.NoError(t, err, "unable to make test database") + + // We'll start by creating initial data to use for populating our test + // node announcement instance + var alias [32]byte + copy(alias[:], []byte("alice")) + color := color.RGBA{255, 255, 255, 0} + _, pub := btcec.PrivKeyFromBytes(key[:]) + + nodeAnn := &NodeAnnouncement{ + Alias: alias, + Color: color, + NodeID: [33]byte(pub.SerializeCompressed()), + } + if err := nodeAnn.Sync(fullDB); err != nil { + t.Fatalf("unable to sync node announcement: %v", err) + } + + // Fetch the current node announcement from the database, it should + // match the one we just persisted + persistedNodeAnn, err := fullDB.FetchNodeAnnouncement(pub) + require.NoError(t, err, "unable to fetch node announcement") + if nodeAnn.Alias != persistedNodeAnn.Alias { + t.Fatalf("node aliases don't match: expected %v, got %v", + nodeAnn.Alias.String(), persistedNodeAnn.Alias.String()) + } + + if nodeAnn.Color != persistedNodeAnn.Color { + t.Fatalf("node colors don't match: expected %v, got %v", + nodeAnn.Color, persistedNodeAnn.Color) + } + + if nodeAnn.NodeID != persistedNodeAnn.NodeID { + t.Fatalf("node nodeIds don't match: expected %v, got %v", + nodeAnn.NodeID, persistedNodeAnn.NodeID) + } + +} From 371c12e71ee049efd66f49a176113456ee3eeec1 Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Fri, 7 Jun 2024 12:26:07 +0100 Subject: [PATCH 8/9] channeldb+peersrpc+lnd: save features and addresses to disk In this commit, we save the features and addresses assosciated with a node in the database, and use them when the node restarts. --- channeldb/node.go | 48 ++++++++++++++++++++++++++++++---- lnrpc/peersrpc/peers_server.go | 2 +- server.go | 45 ++++++++++++++++++++----------- 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/channeldb/node.go b/channeldb/node.go index 8959088170e..e63a4f458aa 100644 --- a/channeldb/node.go +++ b/channeldb/node.go @@ -4,9 +4,11 @@ import ( "bytes" "image/color" "io" + "net" "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/lnwire" ) var ( @@ -38,6 +40,13 @@ type NodeAnnouncement struct { // NodeID is a public key which is used as node identification. NodeID [33]byte + + // Address includes two specification fields: 'ipv6' and 'port' on + // which the node is accepting incoming connections. + Addresses []net.Addr + + // Features is the list of protocol features this node supports. + Features *lnwire.RawFeatureVector } // Sync performs a full database sync which writes the current up-to-date data @@ -97,11 +106,14 @@ func fetchNodeAnnouncement(tx kvdb.RTx, targetPub *btcec.PublicKey) (*NodeAnnoun } -func (d *DB) PutNodeAnnouncement(pubkey [33]byte, alias [32]byte, color color.RGBA) error { +func (d *DB) PutNodeAnnouncement(pubkey [33]byte, alias [32]byte, color color.RGBA, + addresses []net.Addr, features *lnwire.RawFeatureVector) error { nodeAnn := &NodeAnnouncement{ - Alias: alias, - Color: color, - NodeID: pubkey, + Alias: alias, + Color: color, + NodeID: pubkey, + Addresses: addresses, + Features: features, } return kvdb.Update(d, func(tx kvdb.RwTx) error { @@ -145,11 +157,29 @@ func serializeNodeAnnouncement(w io.Writer, n *NodeAnnouncement) error { return err } - // Serialize IdentityPub + // Serialize NodeID if _, err := w.Write(n.NodeID[:]); err != nil { return err } + // Serialize Addresses + var addrBuffer bytes.Buffer + if err := lnwire.WriteNetAddrs(&addrBuffer, n.Addresses); err != nil { + return err + } + if _, err := w.Write(addrBuffer.Bytes()); err != nil { + return err + } + + // Serialize Features + var featsBuffer bytes.Buffer + if err := lnwire.WriteRawFeatureVector(&featsBuffer, n.Features); err != nil { + return err + } + if _, err := w.Write(featsBuffer.Bytes()); err != nil { + return err + } + return nil } @@ -179,6 +209,14 @@ func deserializeNodeAnnouncement(r io.Reader) (*NodeAnnouncement, error) { } nodeAnn.NodeID = pub + if err := lnwire.ReadElement(r, &nodeAnn.Addresses); err != nil { + return nil, err + } + + if err := lnwire.ReadElement(r, &nodeAnn.Features); err != nil { + return nil, err + } + return nodeAnn, err } diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go index b6c8d5a340a..569ca7efd04 100644 --- a/lnrpc/peersrpc/peers_server.go +++ b/lnrpc/peersrpc/peers_server.go @@ -401,7 +401,7 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context, nodeAnnouncement := s.cfg.GetNodeAnnouncement() s.cfg.ChanStateDB.GetParentDB().PutNodeAnnouncement( nodeAnnouncement.NodeID, nodeAnnouncement.Alias, - nodeAnnouncement.RGBColor) + nodeAnnouncement.RGBColor, nodeAnnouncement.Addresses, nodeAnnouncement.Features) return resp, nil } diff --git a/server.go b/server.go index 85d3b5bb587..fb3868ed98d 100644 --- a/server.go +++ b/server.go @@ -796,8 +796,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return nil, err } + // Fetch the persisted node announcement from disk. This announcement + // contains information about our node, and will be used to construct + // our initial node. + pubkey := nodeKeyDesc.PubKey + persistedConfig, err := s.miscDB.FetchNodeAnnouncement(pubkey) + if err != nil { + srvrLog.Errorf("unable to fetch node announcement: %v\n", err) + } + selfAddrs := make([]net.Addr, 0, len(externalIPs)) selfAddrs = append(selfAddrs, externalIPs...) + selfAddrs = append(selfAddrs, persistedConfig.Addresses...) // As the graph can be obtained at anytime from the network, we won't // replicate it, and instead it'll only be stored locally. @@ -806,17 +816,10 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // We'll now reconstruct a node announcement based on our current // configuration so we can send it out as a sort of heart beat within // the network. - // - // We'll start by getting the node announcement from disk. - pubkey := nodeKeyDesc.PubKey - persistedConfig, err := s.miscDB.FetchNodeAnnouncement(pubkey) - if err != nil { - srvrLog.Errorf("unable to fetch node announcement: %v\n", err) - } - var color color.RGBA - - // Determine the source of the color. #3399FF is the default + // Determine the source of the color. If the user-provided color is + // #3399FF (default) and there is a persisted node announcement, use the color + // from that. Otherwise, parse the user-provided color. if cfg.Color == "#3399FF" && persistedConfig != nil { color = persistedConfig.Color } else { @@ -829,14 +832,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, } // If no alias is provided, check if one is stored in the database and - // use that. + // use that. If still no alias is provided, default to the first 10 + // characters of the public key. alias := cfg.Alias if alias == "" && persistedConfig != nil { alias = persistedConfig.Alias.String() } - - // If still alias is empty, default to first 10 characters of public - // key. if alias == "" { alias = hex.EncodeToString(serializedPubKey[:10]) } @@ -844,12 +845,26 @@ func newServer(cfg *Config, listenAddrs []net.Addr, if err != nil { return nil, err } + + // + // If there is no persisted configuration, we will use the set of features + // provided by the feature manager. Otherwise, we will use the features + // stored in the persisted configuration. + var features *lnwire.FeatureVector + if persistedConfig != nil { + // Extract the features from the persisted configuration. + features = lnwire.NewFeatureVector(persistedConfig.Features, lnwire.Features) + } else { + // Get the set of features from the feature manager. + features = s.featureMgr.Get(feature.SetNodeAnn) + } + selfNode := &channeldb.LightningNode{ HaveNodeAnnouncement: true, LastUpdate: time.Now(), Addresses: selfAddrs, Alias: nodeAlias.String(), - Features: s.featureMgr.Get(feature.SetNodeAnn), + Features: features, Color: color, } copy(selfNode.PubKeyBytes[:], nodeKeyDesc.PubKey.SerializeCompressed()) From 5899f80000527c3968c27f62a40516f46fdb758a Mon Sep 17 00:00:00 2001 From: Abdullahi Yunus Date: Fri, 7 Jun 2024 14:35:12 +0100 Subject: [PATCH 9/9] channeldb: add test to compare local and persisted node announcement addresses --- channeldb/node_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/channeldb/node_test.go b/channeldb/node_test.go index 02b65bed695..8c2197abe4b 100644 --- a/channeldb/node_test.go +++ b/channeldb/node_test.go @@ -2,9 +2,12 @@ package channeldb import ( "image/color" + "net" + "reflect" "testing" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -20,11 +23,15 @@ func TestNodeAnnouncementEncodeDecode(t *testing.T) { copy(alias[:], []byte("alice")) color := color.RGBA{255, 255, 255, 0} _, pub := btcec.PrivKeyFromBytes(key[:]) + address := []net.Addr{testAddr} + features := lnwire.RawFeatureVector{} nodeAnn := &NodeAnnouncement{ - Alias: alias, - Color: color, - NodeID: [33]byte(pub.SerializeCompressed()), + Alias: alias, + Color: color, + NodeID: [33]byte(pub.SerializeCompressed()), + Addresses: address, + Features: &features, } if err := nodeAnn.Sync(fullDB); err != nil { t.Fatalf("unable to sync node announcement: %v", err) @@ -49,4 +56,10 @@ func TestNodeAnnouncementEncodeDecode(t *testing.T) { nodeAnn.NodeID, persistedNodeAnn.NodeID) } + // Verify that the addresses of the node announcements are the same. + if !reflect.DeepEqual(nodeAnn.Addresses, persistedNodeAnn.Addresses) { + t.Fatalf("node addresses don't match: expected %v, got %v", + nodeAnn.Addresses, persistedNodeAnn.Addresses) + } + }