diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index d9ec63044..3ab0c4fc3 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -417,20 +417,20 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, case errors.Is(err, graphdb.ErrGraphNodeNotFound): fallthrough case errors.Is(err, graphdb.ErrGraphNotFound): - graphNode := &models.Node{ - Addresses: []net.Addr{&net.TCPAddr{ - IP: bytes.Repeat( - []byte("a"), 16, - ), - }}, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ), - AuthSigBytes: testSig.Serialize(), - } - copy( - graphNode.PubKeyBytes[:], - pub.SerializeCompressed(), + //nolint:ll + graphNode := models.NewV1Node( + route.NewVertex(pub), + &models.NodeV1Fields{ + Addresses: []net.Addr{&net.TCPAddr{ + IP: bytes.Repeat( + []byte("a"), 16, + ), + }}, + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ).RawFeatureVector, + AuthSigBytes: testSig.Serialize(), + }, ) err := d.db.AddNode( context.Background(), graphNode, @@ -449,18 +449,18 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, if err != nil { return nil, err } - dbNode := &models.Node{ - Addresses: []net.Addr{ - &net.TCPAddr{ + + dbNode := models.NewV1Node( + route.NewVertex(nodeKey), &models.NodeV1Fields{ + Addresses: []net.Addr{&net.TCPAddr{ IP: bytes.Repeat([]byte("a"), 16), - }, + }}, + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ).RawFeatureVector, + AuthSigBytes: testSig.Serialize(), }, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ), - AuthSigBytes: testSig.Serialize(), - } - copy(dbNode.PubKeyBytes[:], nodeKey.SerializeCompressed()) + ) if err := d.db.AddNode( context.Background(), dbNode, ); err != nil { @@ -549,18 +549,19 @@ func (d *testDBGraph) addRandNode() (*btcec.PublicKey, error) { if err != nil { return nil, err } - dbNode := &models.Node{ - Addresses: []net.Addr{ - &net.TCPAddr{ - IP: bytes.Repeat([]byte("a"), 16), + dbNode := models.NewV1Node( + route.NewVertex(nodeKey), &models.NodeV1Fields{ + Addresses: []net.Addr{ + &net.TCPAddr{ + IP: bytes.Repeat([]byte("a"), 16), + }, }, + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ).RawFeatureVector, + AuthSigBytes: testSig.Serialize(), }, - Features: lnwire.NewFeatureVector( - nil, lnwire.Features, - ), - AuthSigBytes: testSig.Serialize(), - } - copy(dbNode.PubKeyBytes[:], nodeKey.SerializeCompressed()) + ) err = d.db.AddNode(context.Background(), dbNode) if err != nil { return nil, err diff --git a/channeldb/db_test.go b/channeldb/db_test.go index 50a94574e..e2e9a1970 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -19,6 +19,7 @@ import ( "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/shachain" "github.com/stretchr/testify/require" ) @@ -811,15 +812,17 @@ func createNode(priv *btcec.PrivateKey) *models.Node { updateTime := rand.Int63() pub := priv.PubKey().SerializeCompressed() - n := &models.Node{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: time.Unix(updateTime, 0), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub), - Features: testFeatures, - Addresses: testAddrs, - } - copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) + n := models.NewV1Node( + route.NewVertex(priv.PubKey()), + &models.NodeV1Fields{ + AuthSigBytes: testSig.Serialize(), + LastUpdate: time.Unix(updateTime, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + string(pub), + Features: testFeatures.RawFeatureVector, + Addresses: testAddrs, + }, + ) return n } diff --git a/graph/builder_test.go b/graph/builder_test.go index 6e362fb82..72f271985 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -97,15 +97,16 @@ func TestIgnoreNodeAnnouncement(t *testing.T) { ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath) pub := priv1.PubKey() - node := &models.Node{ - LastUpdate: time.Unix(123, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(node.PubKeyBytes[:], pub.SerializeCompressed()) + node := models.NewV1Node( + route.NewVertex(pub), &models.NodeV1Fields{ + Addresses: testAddrs, + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + LastUpdate: time.Unix(123, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + }, + ) err := ctx.builder.AddNode(t.Context(), node) if !IsError(err, ErrIgnored) { @@ -1083,15 +1084,16 @@ func TestIsStaleNode(t *testing.T) { // With the node stub in the database, we'll add the fully node // announcement to the database. - n1 := &models.Node{ - LastUpdate: updateTimeStamp, - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) + n1 := models.NewV1Node( + route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{ + LastUpdate: updateTimeStamp, + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + }, + ) if err := ctx.builder.AddNode(t.Context(), n1); err != nil { t.Fatalf("could not add node: %v", err) } @@ -1399,14 +1401,16 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( return nil, err } - dbNode := &models.Node{ + pubKey, err := route.NewVertexFromBytes(pubBytes) + require.NoError(t, err) + + dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{ AuthSigBytes: testSig.Serialize(), LastUpdate: testTime, Addresses: testAddrs, Alias: node.Alias, - Features: testFeatures, - } - copy(dbNode.PubKeyBytes[:], pubBytes) + Features: testFeatures.RawFeatureVector, + }) // We require all aliases within the graph to be unique for our // tests. @@ -1784,15 +1788,15 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, features = lnwire.EmptyFeatureVector() } - dbNode := &models.Node{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: alias, - Features: features, - } - - copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) + dbNode := models.NewV1Node( + route.NewVertex(pubKey), &models.NodeV1Fields{ + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: alias, + Features: features.RawFeatureVector, + }, + ) privKeyMap[alias] = privKey diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 2e1235172..e4a8c7fae 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -70,18 +70,18 @@ var ( ) func createNode(priv *btcec.PrivateKey) *models.Node { - pub := priv.PubKey().SerializeCompressed() - n := &models.Node{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: nextUpdateTime(), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + hex.EncodeToString(pub), - Features: testFeatures, - Addresses: testAddrs, - } - copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) + pubKey := route.NewVertex(priv.PubKey()) - return n + return models.NewV1Node( + pubKey, &models.NodeV1Fields{ + LastUpdate: nextUpdateTime(), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + hex.EncodeToString(pubKey[:]), + Addresses: testAddrs, + Features: testFeatures.RawFeatureVector, + AuthSigBytes: testSig.Serialize(), + }, + ) } func createTestVertex(t testing.TB) *models.Node { @@ -105,16 +105,18 @@ func TestNodeInsertionAndDeletion(t *testing.T) { timeStamp := int64(1232342) nodeWithAddrs := func(addrs []net.Addr) *models.Node { timeStamp++ - return &models.Node{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: time.Unix(timeStamp, 0), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek", - Features: testFeatures, - Addresses: addrs, - ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, - PubKeyBytes: testPub, - } + + return models.NewV1Node( + testPub, &models.NodeV1Fields{ + AuthSigBytes: testSig.Serialize(), + LastUpdate: time.Unix(timeStamp, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek", + Features: testFeatures.RawFeatureVector, + Addresses: addrs, + ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, + }, + ) } // First, insert the node into the graph DB. This should succeed @@ -313,11 +315,7 @@ func TestPartialNode(t *testing.T) { // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode1 := &models.Node{ - LastUpdate: time.Unix(0, 0), - PubKeyBytes: pubKey1, - Features: lnwire.EmptyFeatureVector(), - } + expectedNode1 := models.NewV1ShellNode(pubKey1) compareNodes(t, expectedNode1, dbNode1) _, exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) @@ -326,11 +324,7 @@ func TestPartialNode(t *testing.T) { // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode2 := &models.Node{ - LastUpdate: time.Unix(0, 0), - PubKeyBytes: pubKey2, - Features: lnwire.EmptyFeatureVector(), - } + expectedNode2 := models.NewV1ShellNode(pubKey2) compareNodes(t, expectedNode2, dbNode2) // Next, delete the node from the graph, this should purge all data @@ -365,7 +359,7 @@ func TestAliasLookup(t *testing.T) { require.NoError(t, err, "unable to generate pubkey") dbAlias, err := graph.LookupAlias(ctx, nodePub) require.NoError(t, err, "unable to find alias") - require.Equal(t, testNode.Alias, dbAlias) + require.Equal(t, testNode.Alias.UnwrapOr(""), dbAlias) // Ensure that looking up a non-existent alias results in an error. node := createTestVertex(t) @@ -1600,7 +1594,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, node := createTestVertex(t) nodes[i] = node - nodeIndex[node.Alias] = struct{}{} + nodeIndex[node.Alias.UnwrapOr("")] = struct{}{} } // Add each of the nodes into the graph, they should be inserted @@ -1612,7 +1606,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, // Iterate over each node as returned by the graph, if all nodes are // reached, then the map created above should be empty. err := graph.ForEachNode(ctx, func(n *models.Node) error { - delete(nodeIndex, n.Alias) + delete(nodeIndex, n.Alias.UnwrapOr("")) return nil }, func() {}) require.NoError(t, err) @@ -2289,7 +2283,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { require.Len(t, resp, len(queryCase.resp)) for i := 0; i < len(resp); i++ { - compareNodes(t, &queryCase.resp[i], &resp[i]) + compareNodes(t, &queryCase.resp[i], resp[i]) } } } @@ -2487,7 +2481,7 @@ func TestNodeUpdatesInHorizonEarlyTermination(t *testing.T) { ) // Collect only up to stopAt nodes, breaking afterwards. - var collected []models.Node + var collected []*models.Node count := 0 for node := range iter { if count >= stopAt { @@ -3824,7 +3818,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) { t.Fatalf("should have 1 nodes instead have: %v", len(nodesInHorizon)) } - compareNodes(t, node1, &nodesInHorizon[0]) + compareNodes(t, node1, nodesInHorizon[0]) // We'll now delete the node from the graph, this should result in it // being removed from the update index as well. diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go index 25eb6f5b1..2d4da91f5 100644 --- a/graph/db/interfaces.go +++ b/graph/db/interfaces.go @@ -111,7 +111,7 @@ type V1Store interface { //nolint:interfacebloat // by two nodes to quickly determine if they have the same set of up to // date node announcements. NodeUpdatesInHorizon(startTime, endTime time.Time, - opts ...IteratorOption) iter.Seq2[models.Node, error] + opts ...IteratorOption) iter.Seq2[*models.Node, error] // FetchNode attempts to look up a target node by its identity // public key. If the node isn't found in the database, then diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 99c95842f..ae4136e9c 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "errors" "fmt" + "image/color" "io" "iter" "math" @@ -848,7 +849,7 @@ func forEachNode(db kvdb.Backend, // Execute the callback, the transaction will abort if // this returns an error. - return cb(tx, &node) + return cb(tx, node) }) } @@ -945,12 +946,7 @@ func sourceNodeWithTx(nodes kvdb.RBucket) (*models.Node, error) { // With the pubKey of the source node retrieved, we're able to // fetch the full node information. - node, err := fetchLightningNode(nodes, selfPub) - if err != nil { - return nil, err - } - - return &node, nil + return fetchLightningNode(nodes, selfPub) } // SetSourceNode sets the source node within the graph database. The source @@ -1205,10 +1201,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, _, node1Err := fetchLightningNode(nodes, edge.NodeKey1Bytes[:]) switch { case errors.Is(node1Err, ErrGraphNodeNotFound): - node1Shell := models.Node{ - PubKeyBytes: edge.NodeKey1Bytes, - } - err := addLightningNode(tx, &node1Shell) + err := addLightningNode( + tx, models.NewV1ShellNode(edge.NodeKey1Bytes), + ) if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x: %w", edge.NodeKey1Bytes, err) @@ -1220,10 +1215,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, _, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:]) switch { case errors.Is(node2Err, ErrGraphNodeNotFound): - node2Shell := models.Node{ - PubKeyBytes: edge.NodeKey2Bytes, - } - err := addLightningNode(tx, &node2Shell) + err := addLightningNode( + tx, models.NewV1ShellNode(edge.NodeKey2Bytes), + ) if err != nil { return fmt.Errorf("unable to create shell node "+ "for: %x: %w", edge.NodeKey2Bytes, err) @@ -2247,8 +2241,8 @@ func (c *KVStore) fetchNextChanUpdateBatch( Info: &edgeInfo, Policy1: edge1, Policy2: edge2, - Node1: &node1, - Node2: &node2, + Node1: node1, + Node2: node2, } state.edgesSeen[chanIDInt] = struct{}{} @@ -2394,10 +2388,10 @@ func newNodeUpdatesIterator(batchSize int, startTime, endTime time.Time, // fetchNextNodeBatch fetches the next batch of node announcements using the // iterator state. func (c *KVStore) fetchNextNodeBatch( - state *nodeUpdatesIterator) ([]models.Node, bool, error) { + state *nodeUpdatesIterator) ([]*models.Node, bool, error) { var ( - nodeBatch []models.Node + nodeBatch []*models.Node hasMore bool ) @@ -2536,14 +2530,14 @@ func (c *KVStore) fetchNextNodeBatch( // update timestamp within the passed range. func (c *KVStore) NodeUpdatesInHorizon(startTime, endTime time.Time, - opts ...IteratorOption) iter.Seq2[models.Node, error] { + opts ...IteratorOption) iter.Seq2[*models.Node, error] { cfg := defaultIteratorConfig() for _, opt := range opts { opt(cfg) } - return func(yield func(models.Node, error) bool) { + return func(yield func(*models.Node, error) bool) { // Initialize iterator state. state := newNodeUpdatesIterator( cfg.nodeUpdateIterBatchSize, @@ -2557,7 +2551,7 @@ func (c *KVStore) NodeUpdatesInHorizon(startTime, log.Errorf("unable to read node updates in "+ "horizon: %v", err) - yield(models.Node{}, err) + yield(&models.Node{}, err) return } @@ -2945,8 +2939,8 @@ func (c *KVStore) fetchChanInfos(tx kvdb.RTx, chanIDs []uint64) ( Info: &edgeInfo, Policy1: edge1, Policy2: edge2, - Node1: &node1, - Node2: &node2, + Node1: node1, + Node2: node2, }) } @@ -3411,7 +3405,7 @@ func (c *KVStore) fetchLightningNode(tx kvdb.RTx, return err } - node = &n + node = n return nil } @@ -3694,7 +3688,7 @@ func (c *KVStore) fetchOtherNode(tx kvdb.RTx, return err } - targetNode = &node + targetNode = node return nil } @@ -4383,17 +4377,20 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, return err } - if err := binary.Write(&b, byteOrder, node.Color.R); err != nil { + nodeColor := node.Color.UnwrapOr(color.RGBA{}) + + if err := binary.Write(&b, byteOrder, nodeColor.R); err != nil { return err } - if err := binary.Write(&b, byteOrder, node.Color.G); err != nil { + if err := binary.Write(&b, byteOrder, nodeColor.G); err != nil { return err } - if err := binary.Write(&b, byteOrder, node.Color.B); err != nil { + if err := binary.Write(&b, byteOrder, nodeColor.B); err != nil { return err } - if err := wire.WriteVarString(&b, 0, node.Alias); err != nil { + err = wire.WriteVarString(&b, 0, node.Alias.UnwrapOr("")) + if err != nil { return err } @@ -4432,7 +4429,8 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, return err } - if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil { + err = aliasBucket.Put(nodePub, []byte(node.Alias.UnwrapOr(""))) + if err != nil { return err } @@ -4466,11 +4464,11 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, } func fetchLightningNode(nodeBucket kvdb.RBucket, - nodePub []byte) (models.Node, error) { + nodePub []byte) (*models.Node, error) { nodeBytes := nodeBucket.Get(nodePub) if nodeBytes == nil { - return models.Node{}, ErrGraphNodeNotFound + return nil, ErrGraphNodeNotFound } nodeReader := bytes.NewReader(nodeBytes) @@ -4533,30 +4531,29 @@ func deserializeLightningNodeCacheable(r io.Reader) (route.Vertex, return pubKey, features, nil } -func deserializeLightningNode(r io.Reader) (models.Node, error) { +func deserializeLightningNode(r io.Reader) (*models.Node, error) { var ( - node models.Node scratch [8]byte err error + pubKey [33]byte ) - // Always populate a feature vector, even if we don't have a node - // announcement and short circuit below. - node.Features = lnwire.EmptyFeatureVector() - if _, err := r.Read(scratch[:]); err != nil { - return models.Node{}, err + return nil, err } unix := int64(byteOrder.Uint64(scratch[:])) - node.LastUpdate = time.Unix(unix, 0) + lastUpdate := time.Unix(unix, 0) - if _, err := io.ReadFull(r, node.PubKeyBytes[:]); err != nil { - return models.Node{}, err + if _, err := io.ReadFull(r, pubKey[:]); err != nil { + return nil, err } + node := models.NewV1ShellNode(pubKey) + node.LastUpdate = lastUpdate + if _, err := r.Read(scratch[:2]); err != nil { - return models.Node{}, err + return nil, err } hasNodeAnn := byteOrder.Uint16(scratch[:2]) @@ -4568,28 +4565,31 @@ func deserializeLightningNode(r io.Reader) (models.Node, error) { // 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 models.Node{}, err + var nodeColor color.RGBA + if err := binary.Read(r, byteOrder, &nodeColor.R); err != nil { + return nil, err } - if err := binary.Read(r, byteOrder, &node.Color.G); err != nil { - return models.Node{}, err + if err := binary.Read(r, byteOrder, &nodeColor.G); err != nil { + return nil, err } - if err := binary.Read(r, byteOrder, &node.Color.B); err != nil { - return models.Node{}, err + if err := binary.Read(r, byteOrder, &nodeColor.B); err != nil { + return nil, err } + node.Color = fn.Some(nodeColor) - node.Alias, err = wire.ReadVarString(r, 0) + alias, err := wire.ReadVarString(r, 0) if err != nil { - return models.Node{}, err + return nil, err } + node.Alias = fn.Some(alias) err = node.Features.Decode(r) if err != nil { - return models.Node{}, err + return nil, err } if _, err := r.Read(scratch[:2]); err != nil { - return models.Node{}, err + return nil, err } numAddresses := int(byteOrder.Uint16(scratch[:2])) @@ -4597,7 +4597,7 @@ func deserializeLightningNode(r io.Reader) (models.Node, error) { for i := 0; i < numAddresses; i++ { address, err := DeserializeAddr(r) if err != nil { - return models.Node{}, err + return nil, err } addresses = append(addresses, address) } @@ -4605,7 +4605,7 @@ func deserializeLightningNode(r io.Reader) (models.Node, error) { node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") if err != nil { - return models.Node{}, err + return nil, err } // We'll try and see if there are any opaque bytes left, if not, then @@ -4617,7 +4617,7 @@ func deserializeLightningNode(r io.Reader) (models.Node, error) { case errors.Is(err, io.ErrUnexpectedEOF): case errors.Is(err, io.EOF): case err != nil: - return models.Node{}, err + return nil, err } if len(extraBytes) > 0 { diff --git a/graph/db/models/node.go b/graph/db/models/node.go index c347db501..8bbd837db 100644 --- a/graph/db/models/node.go +++ b/graph/db/models/node.go @@ -7,7 +7,9 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // Node represents an individual vertex/node within the channel graph. @@ -15,6 +17,9 @@ import ( // from it. As the graph is directed, a node will also have an incoming edge // attached to it for each outgoing edge. type Node struct { + // Version is the gossip version that this node was advertised on. + Version lnwire.GossipVersion + // PubKeyBytes is the raw bytes of the public key of the target node. PubKeyBytes [33]byte pubKey *btcec.PublicKey @@ -27,11 +32,11 @@ type Node struct { Addresses []net.Addr // Color is the selected color for the node. - Color color.RGBA + Color fn.Option[color.RGBA] // Alias is a nick-name for the node. The alias can be used to confirm // a node's identity or to serve as a short ID for an address book. - Alias string + Alias fn.Option[string] // AuthSigBytes is the raw signature under the advertised public key // which serves to authenticate the attributes announced by this node. @@ -49,6 +54,72 @@ type Node struct { ExtraOpaqueData []byte } +// NodeV1Fields houses the fields that are specific to a version 1 node +// announcement. +type NodeV1Fields struct { + // Address is the TCP address this node is reachable over. + Addresses []net.Addr + + // AuthSigBytes is the raw signature under the advertised public key + // which serves to authenticate the attributes announced by this node. + AuthSigBytes []byte + + // Features is the list of protocol features supported by this node. + Features *lnwire.RawFeatureVector + + // Color is the selected color for the node. + Color color.RGBA + + // Alias is a nick-name for the node. The alias can be used to confirm + // a node's identity or to serve as a short ID for an address book. + Alias string + + // LastUpdate is the last time the vertex information for this node has + // been updated. + LastUpdate time.Time + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte +} + +// NewV1Node creates a new version 1 node from the passed fields. +func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node { + return &Node{ + Version: lnwire.GossipVersion1, + PubKeyBytes: pub, + Addresses: n.Addresses, + AuthSigBytes: n.AuthSigBytes, + Features: lnwire.NewFeatureVector( + n.Features, lnwire.Features, + ), + Color: fn.Some(n.Color), + Alias: fn.Some(n.Alias), + LastUpdate: n.LastUpdate, + ExtraOpaqueData: n.ExtraOpaqueData, + } +} + +// NewV1ShellNode creates a new shell version 1 node. +func NewV1ShellNode(pubKey route.Vertex) *Node { + return NewShellNode(lnwire.GossipVersion1, pubKey) +} + +// NewShellNode creates a new shell node with the given gossip version and +// public key. +func NewShellNode(v lnwire.GossipVersion, pubKey route.Vertex) *Node { + return &Node{ + Version: v, + PubKeyBytes: pubKey, + Features: lnwire.EmptyFeatureVector(), + LastUpdate: time.Unix(0, 0), + } +} + // HaveAnnouncement returns true if we have received a node announcement for // this node. We determine this by checking if we have a signature for the // announcement. @@ -85,7 +156,7 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, return nil, fmt.Errorf("node does not have node announcement") } - alias, err := lnwire.NewNodeAlias(n.Alias) + alias, err := lnwire.NewNodeAlias(n.Alias.UnwrapOr("")) if err != nil { return nil, err } @@ -93,7 +164,7 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, nodeAnn := &lnwire.NodeAnnouncement1{ Features: n.Features.RawFeatureVector, NodeID: n.PubKeyBytes, - RGBColor: n.Color, + RGBColor: n.Color.UnwrapOr(color.RGBA{}), Alias: alias, Addresses: n.Addresses, Timestamp: uint32(n.LastUpdate.Unix()), @@ -118,16 +189,17 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1, // lnwire.NodeAnnouncement1 message. func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node { timestamp := time.Unix(int64(msg.Timestamp), 0) - features := lnwire.NewFeatureVector(msg.Features, lnwire.Features) - return &Node{ - LastUpdate: timestamp, - Addresses: msg.Addresses, - PubKeyBytes: msg.NodeID, - Alias: msg.Alias.String(), - AuthSigBytes: msg.Signature.ToSignatureBytes(), - Features: features, - Color: msg.RGBColor, - ExtraOpaqueData: msg.ExtraOpaqueData, - } + return NewV1Node( + msg.NodeID, + &NodeV1Fields{ + LastUpdate: timestamp, + Addresses: msg.Addresses, + Alias: msg.Alias.String(), + AuthSigBytes: msg.Signature.ToSignatureBytes(), + Features: msg.Features, + Color: msg.RGBColor, + ExtraOpaqueData: msg.ExtraOpaqueData, + }, + ) } diff --git a/graph/db/notifications.go b/graph/db/notifications.go index eecc38c27..54a748c66 100644 --- a/graph/db/notifications.go +++ b/graph/db/notifications.go @@ -391,9 +391,11 @@ func (c *ChannelGraph) addToTopologyChange(update *TopologyChange, nodeUpdate := &NetworkNodeUpdate{ Addresses: m.Addresses, IdentityKey: pubKey, - Alias: m.Alias, - Color: EncodeHexColor(m.Color), - Features: m.Features.Clone(), + Alias: m.Alias.UnwrapOr(""), + Color: EncodeHexColor( + m.Color.UnwrapOr(color.RGBA{}), + ), + Features: m.Features.Clone(), } update.NodeUpdates = append(update.NodeUpdates, nodeUpdate) diff --git a/graph/db/sql_migration.go b/graph/db/sql_migration.go index 71a5525cb..5b5f6bf40 100644 --- a/graph/db/sql_migration.go +++ b/graph/db/sql_migration.go @@ -7,6 +7,7 @@ import ( "database/sql" "errors" "fmt" + "image/color" "net" "slices" "time" @@ -1451,8 +1452,10 @@ func insertNodeSQLMig(ctx context.Context, db SQLQueries, if node.HaveAnnouncement() { params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) - params.Color = sqldb.SQLStrValid(EncodeHexColor(node.Color)) - params.Alias = sqldb.SQLStrValid(node.Alias) + params.Color = sqldb.SQLStrValid( + EncodeHexColor(node.Color.UnwrapOr(color.RGBA{})), + ) + params.Alias = sqldb.SQLStrValid(node.Alias.UnwrapOr("")) params.Signature = node.AuthSigBytes } diff --git a/graph/db/sql_migration_test.go b/graph/db/sql_migration_test.go index fd44e0f33..9819cf7f9 100644 --- a/graph/db/sql_migration_test.go +++ b/graph/db/sql_migration_test.go @@ -384,9 +384,7 @@ func TestMigrateGraphToSQL(t *testing.T) { // The PruneGraph call requires that the source // node be set. So that is the first object // we will write. - &models.Node{ - PubKeyBytes: testPub, - }, + models.NewV1ShellNode(testPub), // Now we add some block heights to prune // the graph at. uint32(1), uint32(2), uint32(20), uint32(3), @@ -746,16 +744,15 @@ type testNodeOpt func(*models.Node) // makeTestNode can be used to create a test models.Node. The // functional options can be used to modify the node's attributes. func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node { - n := &models.Node{ + n := models.NewV1Node(genPubKey(t), &models.NodeV1Fields{ AuthSigBytes: testSigBytes, LastUpdate: testTime, Color: testColor, Alias: "kek", - Features: testFeatures, + Features: testFeatures.RawFeatureVector, Addresses: testAddrs, ExtraOpaqueData: testExtraData, - PubKeyBytes: genPubKey(t), - } + }) for _, opt := range opts { opt(n) @@ -774,11 +771,7 @@ func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node { func makeTestShellNode(t *testing.T, opts ...testNodeOpt) *models.Node { - n := &models.Node{ - PubKeyBytes: genPubKey(t), - Features: testEmptyFeatures, - LastUpdate: time.Unix(0, 0), - } + n := models.NewV1ShellNode(genPubKey(t)) for _, opt := range opts { opt(n) @@ -1813,18 +1806,15 @@ func genRandomNode(t *rapid.T) *models.Node { extraOpaqueData = nil } - node := &models.Node{ - AuthSigBytes: sigBytes, - LastUpdate: randTime, - Color: randColor, - Alias: alias.String(), - Features: lnwire.NewFeatureVector( - features, lnwire.Features, - ), + node := models.NewV1Node(pubKeyBytes, &models.NodeV1Fields{ + AuthSigBytes: sigBytes, + LastUpdate: randTime, + Color: randColor, + Alias: alias.String(), + Features: features, Addresses: addrs, ExtraOpaqueData: extraOpaqueData, - PubKeyBytes: pubKeyBytes, - } + }) // We call this method so that the internal pubkey field is populated // which then lets us to proper struct comparison later on. diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index d63021abd..3578fd945 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + color "image/color" "iter" "maps" "math" @@ -555,14 +556,14 @@ func (s *SQLStore) SetSourceNode(ctx context.Context, // // NOTE: This is part of the V1Store interface. func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, - opts ...IteratorOption) iter.Seq2[models.Node, error] { + opts ...IteratorOption) iter.Seq2[*models.Node, error] { cfg := defaultIteratorConfig() for _, opt := range opts { opt(cfg) } - return func(yield func(models.Node, error) bool) { + return func(yield func(*models.Node, error) bool) { var ( ctx = context.TODO() lastUpdateTime sql.NullInt64 @@ -573,7 +574,7 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, // Each iteration, we'll read a batch amount of nodes, yield // them, then decide is we have more or not. for hasMore { - var batch []models.Node + var batch []*models.Node //nolint:ll err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { @@ -607,7 +608,7 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, err = forEachNodeInBatch( ctx, s.cfg.QueryCfg, db, rows, func(_ int64, node *models.Node) error { - batch = append(batch, *node) + batch = append(batch, node) // Update pagination cursors // based on the last processed @@ -629,14 +630,14 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, return nil }, func() { - batch = []models.Node{} + batch = []*models.Node{} }) if err != nil { log.Errorf("NodeUpdatesInHorizon batch "+ "error: %v", err) - yield(models.Node{}, err) + yield(&models.Node{}, err) return } @@ -3485,27 +3486,30 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode, var pub [33]byte copy(pub[:], dbNode.PubKey) - node := &models.Node{ - PubKeyBytes: pub, - Features: lnwire.EmptyFeatureVector(), - LastUpdate: time.Unix(0, 0), - } + node := models.NewV1ShellNode(pub) if len(dbNode.Signature) == 0 { return node, nil } node.AuthSigBytes = dbNode.Signature - node.Alias = dbNode.Alias.String - node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) + + if dbNode.Alias.Valid { + node.Alias = fn.Some(dbNode.Alias.String) + } + if dbNode.LastUpdate.Valid { + node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) + } var err error if dbNode.Color.Valid { - node.Color, err = DecodeHexColor(dbNode.Color.String) + nodeColor, err := DecodeHexColor(dbNode.Color.String) if err != nil { return nil, fmt.Errorf("unable to decode color: %w", err) } + + node.Color = fn.Some(nodeColor) } // Use preloaded features. @@ -3608,9 +3612,26 @@ func upsertNode(ctx context.Context, db SQLQueries, } if node.HaveAnnouncement() { - params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) - params.Color = sqldb.SQLStrValid(EncodeHexColor(node.Color)) - params.Alias = sqldb.SQLStrValid(node.Alias) + switch node.Version { + case lnwire.GossipVersion1: + params.LastUpdate = sqldb.SQLInt64( + node.LastUpdate.Unix(), + ) + + case lnwire.GossipVersion2: + + default: + return 0, fmt.Errorf("unknown gossip version: %d", + node.Version) + } + + node.Color.WhenSome(func(rgba color.RGBA) { + params.Color = sqldb.SQLStrValid(EncodeHexColor(rgba)) + }) + node.Alias.WhenSome(func(s string) { + params.Alias = sqldb.SQLStrValid(s) + }) + params.Signature = node.AuthSigBytes } diff --git a/graph/notifications_test.go b/graph/notifications_test.go index c3db240c7..3a30486c9 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -83,15 +83,16 @@ func createTestNode(t *testing.T) *models.Node { require.NoError(t, err) pub := priv.PubKey().SerializeCompressed() - n := &models.Node{ - LastUpdate: time.Unix(updateTime, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + hex.EncodeToString(pub), - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(n.PubKeyBytes[:], pub) + n := models.NewV1Node( + route.NewVertex(priv.PubKey()), &models.NodeV1Fields{ + LastUpdate: time.Unix(updateTime, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + hex.EncodeToString(pub), + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + }, + ) return n } @@ -699,15 +700,12 @@ func TestNodeUpdateNotification(t *testing.T) { t, testFeaturesBuf.Bytes(), featuresBuf.Bytes(), ) - if nodeUpdate.Alias != ann.Alias { - t.Fatalf("node alias doesn't match: expected %v, got %v", - ann.Alias, nodeUpdate.Alias) - } - if nodeUpdate.Color != graphdb.EncodeHexColor(ann.Color) { - t.Fatalf("node color doesn't match: expected %v, "+ - "got %v", graphdb.EncodeHexColor(ann.Color), - nodeUpdate.Color) - } + require.Equal(t, nodeUpdate.Alias, ann.Alias.UnwrapOr("")) + require.Equal( + t, nodeUpdate.Color, graphdb.EncodeHexColor( + ann.Color.UnwrapOr(color.RGBA{}), + ), + ) } // Create lookup map for notifications we are intending to receive. Entries diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index de142b9d9..39c089dff 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -226,18 +226,7 @@ func (s *Server) ImportGraph(ctx context.Context, var err error for _, rpcNode := range graph.Nodes { - node := &models.Node{ - LastUpdate: time.Unix( - int64(rpcNode.LastUpdate), 0, - ), - Alias: rpcNode.Alias, - // NOTE: this is a workaround to ensure that - // HaveAnnouncement() returns true so that the other - // fields are properly persisted. However, - AuthSigBytes: []byte{0}, - } - - node.PubKeyBytes, err = parsePubKey(rpcNode.PubKey) + pubKeyBytes, err := parsePubKey(rpcNode.PubKey) if err != nil { return nil, err } @@ -254,15 +243,25 @@ func (s *Server) ImportGraph(ctx context.Context, } featureVector := lnwire.NewRawFeatureVector(featureBits...) - node.Features = lnwire.NewFeatureVector( - featureVector, featureNames, - ) - node.Color, err = lncfg.ParseHexColor(rpcNode.Color) + nodeColor, err := lncfg.ParseHexColor(rpcNode.Color) if err != nil { return nil, err } + node := models.NewV1Node(pubKeyBytes, &models.NodeV1Fields{ + LastUpdate: time.Unix( + int64(rpcNode.LastUpdate), 0, + ), + Alias: rpcNode.Alias, + Features: featureVector, + Color: nodeColor, + // NOTE: this is a workaround to ensure that + // HaveAnnouncement() returns true so that the other + // fields are properly persisted. However, + AuthSigBytes: []byte{0}, + }) + if err := graphDB.AddNode(ctx, node); err != nil { return nil, fmt.Errorf("unable to add node %v: %w", rpcNode.PubKey, err) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 664f5c019..473bd413e 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -228,14 +228,16 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( return nil, err } - dbNode := &models.Node{ + pubKey, err := route.NewVertexFromBytes(pubBytes) + require.NoError(t, err) + + dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{ AuthSigBytes: testSig.Serialize(), LastUpdate: testTime, Addresses: testAddrs, Alias: node.Alias, - Features: testFeatures, - } - copy(dbNode.PubKeyBytes[:], pubBytes) + Features: testFeatures.RawFeatureVector, + }) // We require all aliases within the graph to be unique for our // tests. @@ -564,15 +566,15 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, features = lnwire.EmptyFeatureVector() } - dbNode := &models.Node{ - AuthSigBytes: testSig.Serialize(), - LastUpdate: testTime, - Addresses: testAddrs, - Alias: alias, - Features: features, - } - - copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) + dbNode := models.NewV1Node( + route.NewVertex(pubKey), &models.NodeV1Fields{ + AuthSigBytes: testSig.Serialize(), + LastUpdate: testTime, + Addresses: testAddrs, + Alias: alias, + Features: features.RawFeatureVector, + }, + ) privKeyMap[alias] = privKey @@ -1249,10 +1251,12 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex) require.NoError(t, err, "unable to decode public key") - doge := &models.Node{} - copy(doge.PubKeyBytes[:], dogePubKeyBytes[:]) - doge.Alias = "doge" - copy(doge.PubKeyBytes[:], dogePubKeyBytes) + pubKey, err := route.NewVertexFromBytes(dogePubKeyBytes) + require.NoError(t, err) + + doge := models.NewV1Node(pubKey, &models.NodeV1Fields{ + Alias: "doge", + }) graph.aliasMap["doge"] = doge.PubKeyBytes // Create the channel edge going from songoku to doge and include it in diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 12d8608a4..547fe0e2d 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -89,8 +89,9 @@ func TestUpdateAdditionalEdge(t *testing.T) { // Create a minimal test node using the private key priv1. pub := priv1.PubKey().SerializeCompressed() - testNode := &models.Node{} - copy(testNode.PubKeyBytes[:], pub) + var pubKey [33]byte + copy(pubKey[:], pub) + testNode := models.NewV1ShellNode(pubKey) nodeID, err := testNode.PubKey() require.NoError(t, err, "failed to get node id") diff --git a/routing/router_test.go b/routing/router_test.go index e46bb1f2b..d087e2d9c 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -192,15 +192,16 @@ func createTestNode() (*models.Node, error) { } pub := priv.PubKey().SerializeCompressed() - n := &models.Node{ - LastUpdate: time.Unix(updateTime, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub), - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(n.PubKeyBytes[:], pub) + n := models.NewV1Node( + route.NewVertex(priv.PubKey()), &models.NodeV1Fields{ + LastUpdate: time.Unix(updateTime, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek" + string(pub), + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + }, + ) return n, nil } @@ -2870,27 +2871,29 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // Now check that we can update the node info for the partial node // without messing up the channel graph. - n1 := &models.Node{ - LastUpdate: time.Unix(123, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node11", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) + n1 := models.NewV1Node( + route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{ + LastUpdate: time.Unix(123, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node11", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + }, + ) require.NoError(t, ctx.graph.AddNode(ctxb, n1)) - n2 := &models.Node{ - LastUpdate: time.Unix(123, 0), - Addresses: testAddrs, - Color: color.RGBA{1, 2, 3, 0}, - Alias: "node22", - AuthSigBytes: testSig.Serialize(), - Features: testFeatures, - } - copy(n2.PubKeyBytes[:], priv2.PubKey().SerializeCompressed()) + n2 := models.NewV1Node( + route.NewVertex(priv2.PubKey()), &models.NodeV1Fields{ + LastUpdate: time.Unix(123, 0), + Addresses: testAddrs, + Color: color.RGBA{1, 2, 3, 0}, + Alias: "node22", + AuthSigBytes: testSig.Serialize(), + Features: testFeatures.RawFeatureVector, + }, + ) require.NoError(t, ctx.graph.AddNode(ctxb, n2)) diff --git a/rpcserver.go b/rpcserver.go index 603856379..ee810d1e1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "image/color" "io" "maps" "math" @@ -7187,11 +7188,13 @@ func marshalNode(node *models.Node) *lnrpc.LightningNode { customRecords := marshalExtraOpaqueData(node.ExtraOpaqueData) return &lnrpc.LightningNode{ - LastUpdate: uint32(node.LastUpdate.Unix()), - PubKey: hex.EncodeToString(node.PubKeyBytes[:]), - Addresses: nodeAddrs, - Alias: node.Alias, - Color: graphdb.EncodeHexColor(node.Color), + LastUpdate: uint32(node.LastUpdate.Unix()), + PubKey: hex.EncodeToString(node.PubKeyBytes[:]), + Addresses: nodeAddrs, + Alias: node.Alias.UnwrapOr(""), + Color: graphdb.EncodeHexColor( + node.Color.UnwrapOr(color.RGBA{}), + ), Features: features, CustomRecords: customRecords, } @@ -8258,9 +8261,9 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context, } // Cache the peer alias. - chanToPeerAlias[chanID] = peer.Alias + chanToPeerAlias[chanID] = peer.Alias.UnwrapOr("") - return peer.Alias, nil + return peer.Alias.UnwrapOr(""), nil } // TODO(roasbeef): add settlement latency? diff --git a/server.go b/server.go index 3594b7d57..d0289de2a 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + "image/color" "math/big" prand "math/rand" "net" @@ -3299,17 +3300,17 @@ func (s *server) createNewHiddenService(ctx context.Context) error { // Finally, we'll update the on-disk version of our announcement so it // will eventually propagate to nodes in the network. - selfNode := &models.Node{ - LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), - Addresses: newNodeAnn.Addresses, - Alias: newNodeAnn.Alias.String(), - Features: lnwire.NewFeatureVector( - newNodeAnn.Features, lnwire.Features, - ), - Color: newNodeAnn.RGBColor, - AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), - } - copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) + selfNode := models.NewV1Node( + route.NewVertex(s.identityECDH.PubKey()), &models.NodeV1Fields{ + Addresses: newNodeAnn.Addresses, + Features: newNodeAnn.Features, + AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), + Color: newNodeAnn.RGBColor, + Alias: newNodeAnn.Alias.String(), + LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), + }, + ) + if err := s.graphDB.SetSourceNode(ctx, selfNode); err != nil { return fmt.Errorf("can't set self node: %w", err) } @@ -3425,9 +3426,9 @@ func (s *server) updateAndBroadcastSelfNode(ctx context.Context, selfNode.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0) selfNode.Addresses = newNodeAnn.Addresses - selfNode.Alias = newNodeAnn.Alias.String() + selfNode.Alias = fn.Some(newNodeAnn.Alias.String()) selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn) - selfNode.Color = newNodeAnn.RGBColor + selfNode.Color = fn.Some(newNodeAnn.RGBColor) selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes() copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) @@ -5583,7 +5584,7 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // Parse the color from config. We will update this later if the config // color is not changed from default (#3399FF) and we have a value in // the source node. - color, err := lncfg.ParseHexColor(s.cfg.Color) + nodeColor, err := lncfg.ParseHexColor(s.cfg.Color) if err != nil { return fmt.Errorf("unable to parse color: %w", err) } @@ -5607,13 +5608,17 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // didn't specify a different color in the config. We'll use the // source node's color. if s.cfg.Color == defaultColor { - color = srcNode.Color + srcNode.Color.WhenSome(func(rgba color.RGBA) { + nodeColor = rgba + }) } // If an alias is not specified in the config, we'll use the // source node's alias. if alias == "" { - alias = srcNode.Alias + srcNode.Alias.WhenSome(func(s string) { + alias = s + }) } // If the `externalip` is not specified in the config, it means @@ -5643,15 +5648,15 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, // TODO(abdulkbk): potentially find a way to use the source node's // features in the self node. - selfNode := &models.Node{ - LastUpdate: nodeLastUpdate, - Addresses: addrs, - Alias: nodeAlias.String(), - Color: color, - Features: s.featureMgr.Get(feature.SetNodeAnn), - } - - copy(selfNode.PubKeyBytes[:], nodePub[:]) + selfNode := models.NewV1Node( + nodePub, &models.NodeV1Fields{ + Alias: nodeAlias.String(), + Color: nodeColor, + LastUpdate: nodeLastUpdate, + Addresses: addrs, + Features: s.featureMgr.GetRaw(feature.SetNodeAnn), + }, + ) // Based on the disk representation of the node announcement generated // above, we'll generate a node announcement that can go out on the