Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

channeldb: add persist nodeannounment config in db #8690

1 change: 1 addition & 0 deletions channeldb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ var dbTopLevelBuckets = [][]byte{
outpointBucket,
chanIDBucket,
historicalChannelBucket,
nodeAnnouncementBucket,
}

// Wipe completely deletes all saved state within all used buckets within the
Expand Down
10 changes: 10 additions & 0 deletions channeldb/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
222 changes: 222 additions & 0 deletions channeldb/node.go
Abdulkbk marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package channeldb

import (
"bytes"
"image/color"
"io"
"net"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)

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")
)

// 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 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 color.RGBA

// 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
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already persist our node announcement in the bucket storing all the collected node announcement. Can we not just grab our latest one from there?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know where the collected node announcements are stored. Are you referring to here where all nodes within a channel graph are stored and their node announcements (if they have any)?.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes - we store our node announcement there too right? if so, then i dont think we need a whole new bucket for it. Let me know if my assumption is incorrect

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ellemouton I explored the code related to graph DB, and it turns out you're right. We're indeed storing our node along with the node announcement, and we can easily retrieve the stored values from this method when lnd starts.

Since this is the case, I will close this PR and open a new one to replace it. Does that make sense?

// 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
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 {
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 (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,
Addresses: addresses,
Features: features,
}

return kvdb.Update(d, func(tx kvdb.RwTx) error {
nodeAnnBucket := tx.ReadWriteBucket(nodeAnnouncementBucket)
if nodeAnnBucket == nil {
return ErrNodeAnnBucketNotFound
}

return putNodeAnnouncement(nodeAnnBucket, nodeAnn)

}, func() {})
}

func putNodeAnnouncement(nodeAnnBucket kvdb.RwBucket, n *NodeAnnouncement) error {
var b bytes.Buffer
if err := serializeNodeAnnouncement(&b, n); err != nil {
return err
}

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[:])); err != nil {
return err
}

// Serialize Color
// 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 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
}

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 = [32]byte(aliasBuf)

// Read Color
// 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 = 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.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

}
65 changes: 65 additions & 0 deletions channeldb/node_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package channeldb

import (
"image/color"
"net"
"reflect"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
"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[:])
address := []net.Addr{testAddr}
features := lnwire.RawFeatureVector{}

nodeAnn := &NodeAnnouncement{
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)
}

// 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)
}

// 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)
}

}
3 changes: 3 additions & 0 deletions lnrpc/peersrpc/config_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package peersrpc
import (
"net"

"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
)
Expand All @@ -29,4 +30,6 @@ type Config struct {
// vector should be provided.
UpdateNodeAnnouncement func(features *lnwire.RawFeatureVector,
mods ...netann.NodeAnnModifier) error

ChanStateDB *channeldb.ChannelStateDB
}
5 changes: 5 additions & 0 deletions lnrpc/peersrpc/peers_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, nodeAnnouncement.Addresses, nodeAnnouncement.Features)

return resp, nil
}