multi: add models.Node V1 constructor

Add a version field to models.Node and a V1 constructor for it.
This commit is contained in:
Elle Mouton authored and yyforyongyu committed 2025-11-12 22:54:05 +08:00
1 parent 8f205e2d1c
commit 80e70096cb
18 files changed
+443 -340

No files matched your search

+35 -34
View File
@@ -417,20 +417,20 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey,
case errors.Is(err, graphdb.ErrGraphNodeNotFound): case errors.Is(err, graphdb.ErrGraphNodeNotFound):
fallthrough fallthrough
case errors.Is(err, graphdb.ErrGraphNotFound): case errors.Is(err, graphdb.ErrGraphNotFound):
graphNode := &models.Node{ //nolint:ll
Addresses: []net.Addr{&net.TCPAddr{ graphNode := models.NewV1Node(
IP: bytes.Repeat( route.NewVertex(pub),
[]byte("a"), 16, &models.NodeV1Fields{
), Addresses: []net.Addr{&net.TCPAddr{
}}, IP: bytes.Repeat(
Features: lnwire.NewFeatureVector( []byte("a"), 16,
nil, lnwire.Features, ),
), }},
AuthSigBytes: testSig.Serialize(), Features: lnwire.NewFeatureVector(
} nil, lnwire.Features,
copy( ).RawFeatureVector,
graphNode.PubKeyBytes[:], AuthSigBytes: testSig.Serialize(),
pub.SerializeCompressed(), },
) )
err := d.db.AddNode( err := d.db.AddNode(
context.Background(), graphNode, context.Background(), graphNode,
@@ -449,18 +449,18 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey,
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbNode := &models.Node{
Addresses: []net.Addr{ dbNode := models.NewV1Node(
&net.TCPAddr{ route.NewVertex(nodeKey), &models.NodeV1Fields{
Addresses: []net.Addr{&net.TCPAddr{
IP: bytes.Repeat([]byte("a"), 16), 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( if err := d.db.AddNode(
context.Background(), dbNode, context.Background(), dbNode,
); err != nil { ); err != nil {
@@ -549,18 +549,19 @@ func (d *testDBGraph) addRandNode() (*btcec.PublicKey, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbNode := &models.Node{ dbNode := models.NewV1Node(
Addresses: []net.Addr{ route.NewVertex(nodeKey), &models.NodeV1Fields{
&net.TCPAddr{ Addresses: []net.Addr{
IP: bytes.Repeat([]byte("a"), 16), &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) err = d.db.AddNode(context.Background(), dbNode)
if err != nil { if err != nil {
return nil, err return nil, err
+12 -9
View File
@@ -19,6 +19,7 @@ import (
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/shachain" "github.com/lightningnetwork/lnd/shachain"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -811,15 +812,17 @@ func createNode(priv *btcec.PrivateKey) *models.Node {
updateTime := rand.Int63() updateTime := rand.Int63()
pub := priv.PubKey().SerializeCompressed() pub := priv.PubKey().SerializeCompressed()
n := &models.Node{ n := models.NewV1Node(
AuthSigBytes: testSig.Serialize(), route.NewVertex(priv.PubKey()),
LastUpdate: time.Unix(updateTime, 0), &models.NodeV1Fields{
Color: color.RGBA{1, 2, 3, 0}, AuthSigBytes: testSig.Serialize(),
Alias: "kek" + string(pub), LastUpdate: time.Unix(updateTime, 0),
Features: testFeatures, Color: color.RGBA{1, 2, 3, 0},
Addresses: testAddrs, Alias: "kek" + string(pub),
} Features: testFeatures.RawFeatureVector,
copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) Addresses: testAddrs,
},
)
return n return n
} }
+35 -31
View File
@@ -97,15 +97,16 @@ func TestIgnoreNodeAnnouncement(t *testing.T) {
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath) ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
pub := priv1.PubKey() pub := priv1.PubKey()
node := &models.Node{ node := models.NewV1Node(
LastUpdate: time.Unix(123, 0), route.NewVertex(pub), &models.NodeV1Fields{
Addresses: testAddrs, Addresses: testAddrs,
Color: color.RGBA{1, 2, 3, 0}, AuthSigBytes: testSig.Serialize(),
Alias: "node11", Features: testFeatures.RawFeatureVector,
AuthSigBytes: testSig.Serialize(), LastUpdate: time.Unix(123, 0),
Features: testFeatures, Color: color.RGBA{1, 2, 3, 0},
} Alias: "node11",
copy(node.PubKeyBytes[:], pub.SerializeCompressed()) },
)
err := ctx.builder.AddNode(t.Context(), node) err := ctx.builder.AddNode(t.Context(), node)
if !IsError(err, ErrIgnored) { 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 // With the node stub in the database, we'll add the fully node
// announcement to the database. // announcement to the database.
n1 := &models.Node{ n1 := models.NewV1Node(
LastUpdate: updateTimeStamp, route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{
Addresses: testAddrs, LastUpdate: updateTimeStamp,
Color: color.RGBA{1, 2, 3, 0}, Addresses: testAddrs,
Alias: "node11", Color: color.RGBA{1, 2, 3, 0},
AuthSigBytes: testSig.Serialize(), Alias: "node11",
Features: testFeatures, AuthSigBytes: testSig.Serialize(),
} Features: testFeatures.RawFeatureVector,
copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) },
)
if err := ctx.builder.AddNode(t.Context(), n1); err != nil { if err := ctx.builder.AddNode(t.Context(), n1); err != nil {
t.Fatalf("could not add node: %v", err) t.Fatalf("could not add node: %v", err)
} }
@@ -1399,14 +1401,16 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
return nil, err return nil, err
} }
dbNode := &models.Node{ pubKey, err := route.NewVertexFromBytes(pubBytes)
require.NoError(t, err)
dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{
AuthSigBytes: testSig.Serialize(), AuthSigBytes: testSig.Serialize(),
LastUpdate: testTime, LastUpdate: testTime,
Addresses: testAddrs, Addresses: testAddrs,
Alias: node.Alias, Alias: node.Alias,
Features: testFeatures, Features: testFeatures.RawFeatureVector,
} })
copy(dbNode.PubKeyBytes[:], pubBytes)
// We require all aliases within the graph to be unique for our // We require all aliases within the graph to be unique for our
// tests. // tests.
@@ -1784,15 +1788,15 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
features = lnwire.EmptyFeatureVector() features = lnwire.EmptyFeatureVector()
} }
dbNode := &models.Node{ dbNode := models.NewV1Node(
AuthSigBytes: testSig.Serialize(), route.NewVertex(pubKey), &models.NodeV1Fields{
LastUpdate: testTime, AuthSigBytes: testSig.Serialize(),
Addresses: testAddrs, LastUpdate: testTime,
Alias: alias, Addresses: testAddrs,
Features: features, Alias: alias,
} Features: features.RawFeatureVector,
},
copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) )
privKeyMap[alias] = privKey privKeyMap[alias] = privKey
+31 -37
View File
@@ -70,18 +70,18 @@ var (
) )
func createNode(priv *btcec.PrivateKey) *models.Node { func createNode(priv *btcec.PrivateKey) *models.Node {
pub := priv.PubKey().SerializeCompressed() pubKey := route.NewVertex(priv.PubKey())
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())
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 { func createTestVertex(t testing.TB) *models.Node {
@@ -105,16 +105,18 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
timeStamp := int64(1232342) timeStamp := int64(1232342)
nodeWithAddrs := func(addrs []net.Addr) *models.Node { nodeWithAddrs := func(addrs []net.Addr) *models.Node {
timeStamp++ timeStamp++
return &models.Node{
AuthSigBytes: testSig.Serialize(), return models.NewV1Node(
LastUpdate: time.Unix(timeStamp, 0), testPub, &models.NodeV1Fields{
Color: color.RGBA{1, 2, 3, 0}, AuthSigBytes: testSig.Serialize(),
Alias: "kek", LastUpdate: time.Unix(timeStamp, 0),
Features: testFeatures, Color: color.RGBA{1, 2, 3, 0},
Addresses: addrs, Alias: "kek",
ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, Features: testFeatures.RawFeatureVector,
PubKeyBytes: testPub, Addresses: addrs,
} ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2},
},
)
} }
// First, insert the node into the graph DB. This should succeed // 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 // The two nodes should match exactly! (with default values for
// LastUpdate and db set to satisfy compareNodes()) // LastUpdate and db set to satisfy compareNodes())
expectedNode1 := &models.Node{ expectedNode1 := models.NewV1ShellNode(pubKey1)
LastUpdate: time.Unix(0, 0),
PubKeyBytes: pubKey1,
Features: lnwire.EmptyFeatureVector(),
}
compareNodes(t, expectedNode1, dbNode1) compareNodes(t, expectedNode1, dbNode1)
_, exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) _, 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 // The two nodes should match exactly! (with default values for
// LastUpdate and db set to satisfy compareNodes()) // LastUpdate and db set to satisfy compareNodes())
expectedNode2 := &models.Node{ expectedNode2 := models.NewV1ShellNode(pubKey2)
LastUpdate: time.Unix(0, 0),
PubKeyBytes: pubKey2,
Features: lnwire.EmptyFeatureVector(),
}
compareNodes(t, expectedNode2, dbNode2) compareNodes(t, expectedNode2, dbNode2)
// Next, delete the node from the graph, this should purge all data // 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") require.NoError(t, err, "unable to generate pubkey")
dbAlias, err := graph.LookupAlias(ctx, nodePub) dbAlias, err := graph.LookupAlias(ctx, nodePub)
require.NoError(t, err, "unable to find alias") 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. // Ensure that looking up a non-existent alias results in an error.
node := createTestVertex(t) node := createTestVertex(t)
@@ -1600,7 +1594,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes,
node := createTestVertex(t) node := createTestVertex(t)
nodes[i] = node nodes[i] = node
nodeIndex[node.Alias] = struct{}{} nodeIndex[node.Alias.UnwrapOr("")] = struct{}{}
} }
// Add each of the nodes into the graph, they should be inserted // 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 // Iterate over each node as returned by the graph, if all nodes are
// reached, then the map created above should be empty. // reached, then the map created above should be empty.
err := graph.ForEachNode(ctx, func(n *models.Node) error { err := graph.ForEachNode(ctx, func(n *models.Node) error {
delete(nodeIndex, n.Alias) delete(nodeIndex, n.Alias.UnwrapOr(""))
return nil return nil
}, func() {}) }, func() {})
require.NoError(t, err) require.NoError(t, err)
@@ -2289,7 +2283,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) {
require.Len(t, resp, len(queryCase.resp)) require.Len(t, resp, len(queryCase.resp))
for i := 0; i < len(resp); i++ { 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. // Collect only up to stopAt nodes, breaking afterwards.
var collected []models.Node var collected []*models.Node
count := 0 count := 0
for node := range iter { for node := range iter {
if count >= stopAt { if count >= stopAt {
@@ -3824,7 +3818,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) {
t.Fatalf("should have 1 nodes instead have: %v", t.Fatalf("should have 1 nodes instead have: %v",
len(nodesInHorizon)) 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 // We'll now delete the node from the graph, this should result in it
// being removed from the update index as well. // being removed from the update index as well.
+1 -1
View File
@@ -111,7 +111,7 @@ type V1Store interface { //nolint:interfacebloat
// by two nodes to quickly determine if they have the same set of up to // by two nodes to quickly determine if they have the same set of up to
// date node announcements. // date node announcements.
NodeUpdatesInHorizon(startTime, endTime time.Time, 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 // FetchNode attempts to look up a target node by its identity
// public key. If the node isn't found in the database, then // public key. If the node isn't found in the database, then
+57 -57
View File
@@ -7,6 +7,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"image/color"
"io" "io"
"iter" "iter"
"math" "math"
@@ -848,7 +849,7 @@ func forEachNode(db kvdb.Backend,
// Execute the callback, the transaction will abort if // Execute the callback, the transaction will abort if
// this returns an error. // 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 // With the pubKey of the source node retrieved, we're able to
// fetch the full node information. // fetch the full node information.
node, err := fetchLightningNode(nodes, selfPub) return fetchLightningNode(nodes, selfPub)
if err != nil {
return nil, err
}
return &node, nil
} }
// SetSourceNode sets the source node within the graph database. The source // 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[:]) _, node1Err := fetchLightningNode(nodes, edge.NodeKey1Bytes[:])
switch { switch {
case errors.Is(node1Err, ErrGraphNodeNotFound): case errors.Is(node1Err, ErrGraphNodeNotFound):
node1Shell := models.Node{ err := addLightningNode(
PubKeyBytes: edge.NodeKey1Bytes, tx, models.NewV1ShellNode(edge.NodeKey1Bytes),
} )
err := addLightningNode(tx, &node1Shell)
if err != nil { if err != nil {
return fmt.Errorf("unable to create shell node "+ return fmt.Errorf("unable to create shell node "+
"for: %x: %w", edge.NodeKey1Bytes, err) "for: %x: %w", edge.NodeKey1Bytes, err)
@@ -1220,10 +1215,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx,
_, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:]) _, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:])
switch { switch {
case errors.Is(node2Err, ErrGraphNodeNotFound): case errors.Is(node2Err, ErrGraphNodeNotFound):
node2Shell := models.Node{ err := addLightningNode(
PubKeyBytes: edge.NodeKey2Bytes, tx, models.NewV1ShellNode(edge.NodeKey2Bytes),
} )
err := addLightningNode(tx, &node2Shell)
if err != nil { if err != nil {
return fmt.Errorf("unable to create shell node "+ return fmt.Errorf("unable to create shell node "+
"for: %x: %w", edge.NodeKey2Bytes, err) "for: %x: %w", edge.NodeKey2Bytes, err)
@@ -2247,8 +2241,8 @@ func (c *KVStore) fetchNextChanUpdateBatch(
Info: &edgeInfo, Info: &edgeInfo,
Policy1: edge1, Policy1: edge1,
Policy2: edge2, Policy2: edge2,
Node1: &node1, Node1: node1,
Node2: &node2, Node2: node2,
} }
state.edgesSeen[chanIDInt] = struct{}{} 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 // fetchNextNodeBatch fetches the next batch of node announcements using the
// iterator state. // iterator state.
func (c *KVStore) fetchNextNodeBatch( func (c *KVStore) fetchNextNodeBatch(
state *nodeUpdatesIterator) ([]models.Node, bool, error) { state *nodeUpdatesIterator) ([]*models.Node, bool, error) {
var ( var (
nodeBatch []models.Node nodeBatch []*models.Node
hasMore bool hasMore bool
) )
@@ -2536,14 +2530,14 @@ func (c *KVStore) fetchNextNodeBatch(
// update timestamp within the passed range. // update timestamp within the passed range.
func (c *KVStore) NodeUpdatesInHorizon(startTime, func (c *KVStore) NodeUpdatesInHorizon(startTime,
endTime time.Time, endTime time.Time,
opts ...IteratorOption) iter.Seq2[models.Node, error] { opts ...IteratorOption) iter.Seq2[*models.Node, error] {
cfg := defaultIteratorConfig() cfg := defaultIteratorConfig()
for _, opt := range opts { for _, opt := range opts {
opt(cfg) opt(cfg)
} }
return func(yield func(models.Node, error) bool) { return func(yield func(*models.Node, error) bool) {
// Initialize iterator state. // Initialize iterator state.
state := newNodeUpdatesIterator( state := newNodeUpdatesIterator(
cfg.nodeUpdateIterBatchSize, cfg.nodeUpdateIterBatchSize,
@@ -2557,7 +2551,7 @@ func (c *KVStore) NodeUpdatesInHorizon(startTime,
log.Errorf("unable to read node updates in "+ log.Errorf("unable to read node updates in "+
"horizon: %v", err) "horizon: %v", err)
yield(models.Node{}, err) yield(&models.Node{}, err)
return return
} }
@@ -2945,8 +2939,8 @@ func (c *KVStore) fetchChanInfos(tx kvdb.RTx, chanIDs []uint64) (
Info: &edgeInfo, Info: &edgeInfo,
Policy1: edge1, Policy1: edge1,
Policy2: edge2, Policy2: edge2,
Node1: &node1, Node1: node1,
Node2: &node2, Node2: node2,
}) })
} }
@@ -3411,7 +3405,7 @@ func (c *KVStore) fetchLightningNode(tx kvdb.RTx,
return err return err
} }
node = &n node = n
return nil return nil
} }
@@ -3694,7 +3688,7 @@ func (c *KVStore) fetchOtherNode(tx kvdb.RTx,
return err return err
} }
targetNode = &node targetNode = node
return nil return nil
} }
@@ -4383,17 +4377,20 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket,
return err 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 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 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 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 return err
} }
@@ -4432,7 +4429,8 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket,
return err 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 return err
} }
@@ -4466,11 +4464,11 @@ func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket,
} }
func fetchLightningNode(nodeBucket kvdb.RBucket, func fetchLightningNode(nodeBucket kvdb.RBucket,
nodePub []byte) (models.Node, error) { nodePub []byte) (*models.Node, error) {
nodeBytes := nodeBucket.Get(nodePub) nodeBytes := nodeBucket.Get(nodePub)
if nodeBytes == nil { if nodeBytes == nil {
return models.Node{}, ErrGraphNodeNotFound return nil, ErrGraphNodeNotFound
} }
nodeReader := bytes.NewReader(nodeBytes) nodeReader := bytes.NewReader(nodeBytes)
@@ -4533,30 +4531,29 @@ func deserializeLightningNodeCacheable(r io.Reader) (route.Vertex,
return pubKey, features, nil return pubKey, features, nil
} }
func deserializeLightningNode(r io.Reader) (models.Node, error) { func deserializeLightningNode(r io.Reader) (*models.Node, error) {
var ( var (
node models.Node
scratch [8]byte scratch [8]byte
err error 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 { if _, err := r.Read(scratch[:]); err != nil {
return models.Node{}, err return nil, err
} }
unix := int64(byteOrder.Uint64(scratch[:])) 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 { if _, err := io.ReadFull(r, pubKey[:]); err != nil {
return models.Node{}, err return nil, err
} }
node := models.NewV1ShellNode(pubKey)
node.LastUpdate = lastUpdate
if _, err := r.Read(scratch[:2]); err != nil { if _, err := r.Read(scratch[:2]); err != nil {
return models.Node{}, err return nil, err
} }
hasNodeAnn := byteOrder.Uint16(scratch[:2]) 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 // We did get a node announcement for this node, so we'll have the rest
// of the data available. // of the data available.
if err := binary.Read(r, byteOrder, &node.Color.R); err != nil { var nodeColor color.RGBA
return models.Node{}, err if err := binary.Read(r, byteOrder, &nodeColor.R); err != nil {
return nil, err
} }
if err := binary.Read(r, byteOrder, &node.Color.G); err != nil { if err := binary.Read(r, byteOrder, &nodeColor.G); err != nil {
return models.Node{}, err return nil, err
} }
if err := binary.Read(r, byteOrder, &node.Color.B); err != nil { if err := binary.Read(r, byteOrder, &nodeColor.B); err != nil {
return models.Node{}, err return nil, err
} }
node.Color = fn.Some(nodeColor)
node.Alias, err = wire.ReadVarString(r, 0) alias, err := wire.ReadVarString(r, 0)
if err != nil { if err != nil {
return models.Node{}, err return nil, err
} }
node.Alias = fn.Some(alias)
err = node.Features.Decode(r) err = node.Features.Decode(r)
if err != nil { if err != nil {
return models.Node{}, err return nil, err
} }
if _, err := r.Read(scratch[:2]); err != nil { if _, err := r.Read(scratch[:2]); err != nil {
return models.Node{}, err return nil, err
} }
numAddresses := int(byteOrder.Uint16(scratch[:2])) numAddresses := int(byteOrder.Uint16(scratch[:2]))
@@ -4597,7 +4597,7 @@ func deserializeLightningNode(r io.Reader) (models.Node, error) {
for i := 0; i < numAddresses; i++ { for i := 0; i < numAddresses; i++ {
address, err := DeserializeAddr(r) address, err := DeserializeAddr(r)
if err != nil { if err != nil {
return models.Node{}, err return nil, err
} }
addresses = append(addresses, address) 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") node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil { 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 // 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.ErrUnexpectedEOF):
case errors.Is(err, io.EOF): case errors.Is(err, io.EOF):
case err != nil: case err != nil:
return models.Node{}, err return nil, err
} }
if len(extraBytes) > 0 { if len(extraBytes) > 0 {
+87 -15
View File
@@ -7,7 +7,9 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
) )
// Node represents an individual vertex/node within the channel graph. // 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 // from it. As the graph is directed, a node will also have an incoming edge
// attached to it for each outgoing edge. // attached to it for each outgoing edge.
type Node struct { 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 is the raw bytes of the public key of the target node.
PubKeyBytes [33]byte PubKeyBytes [33]byte
pubKey *btcec.PublicKey pubKey *btcec.PublicKey
@@ -27,11 +32,11 @@ type Node struct {
Addresses []net.Addr Addresses []net.Addr
// Color is the selected color for the node. // 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 // 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. // 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 // AuthSigBytes is the raw signature under the advertised public key
// which serves to authenticate the attributes announced by this node. // which serves to authenticate the attributes announced by this node.
@@ -49,6 +54,72 @@ type Node struct {
ExtraOpaqueData []byte 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 // 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 // this node. We determine this by checking if we have a signature for the
// announcement. // announcement.
@@ -85,7 +156,7 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
return nil, fmt.Errorf("node does not have node announcement") 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 { if err != nil {
return nil, err return nil, err
} }
@@ -93,7 +164,7 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
nodeAnn := &lnwire.NodeAnnouncement1{ nodeAnn := &lnwire.NodeAnnouncement1{
Features: n.Features.RawFeatureVector, Features: n.Features.RawFeatureVector,
NodeID: n.PubKeyBytes, NodeID: n.PubKeyBytes,
RGBColor: n.Color, RGBColor: n.Color.UnwrapOr(color.RGBA{}),
Alias: alias, Alias: alias,
Addresses: n.Addresses, Addresses: n.Addresses,
Timestamp: uint32(n.LastUpdate.Unix()), Timestamp: uint32(n.LastUpdate.Unix()),
@@ -118,16 +189,17 @@ func (n *Node) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement1,
// lnwire.NodeAnnouncement1 message. // lnwire.NodeAnnouncement1 message.
func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node { func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement1) *Node {
timestamp := time.Unix(int64(msg.Timestamp), 0) timestamp := time.Unix(int64(msg.Timestamp), 0)
features := lnwire.NewFeatureVector(msg.Features, lnwire.Features)
return &Node{ return NewV1Node(
LastUpdate: timestamp, msg.NodeID,
Addresses: msg.Addresses, &NodeV1Fields{
PubKeyBytes: msg.NodeID, LastUpdate: timestamp,
Alias: msg.Alias.String(), Addresses: msg.Addresses,
AuthSigBytes: msg.Signature.ToSignatureBytes(), Alias: msg.Alias.String(),
Features: features, AuthSigBytes: msg.Signature.ToSignatureBytes(),
Color: msg.RGBColor, Features: msg.Features,
ExtraOpaqueData: msg.ExtraOpaqueData, Color: msg.RGBColor,
} ExtraOpaqueData: msg.ExtraOpaqueData,
},
)
} }
+5 -3
View File
@@ -391,9 +391,11 @@ func (c *ChannelGraph) addToTopologyChange(update *TopologyChange,
nodeUpdate := &NetworkNodeUpdate{ nodeUpdate := &NetworkNodeUpdate{
Addresses: m.Addresses, Addresses: m.Addresses,
IdentityKey: pubKey, IdentityKey: pubKey,
Alias: m.Alias, Alias: m.Alias.UnwrapOr(""),
Color: EncodeHexColor(m.Color), Color: EncodeHexColor(
Features: m.Features.Clone(), m.Color.UnwrapOr(color.RGBA{}),
),
Features: m.Features.Clone(),
} }
update.NodeUpdates = append(update.NodeUpdates, nodeUpdate) update.NodeUpdates = append(update.NodeUpdates, nodeUpdate)
+5 -2
View File
@@ -7,6 +7,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"image/color"
"net" "net"
"slices" "slices"
"time" "time"
@@ -1451,8 +1452,10 @@ func insertNodeSQLMig(ctx context.Context, db SQLQueries,
if node.HaveAnnouncement() { if node.HaveAnnouncement() {
params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix())
params.Color = sqldb.SQLStrValid(EncodeHexColor(node.Color)) params.Color = sqldb.SQLStrValid(
params.Alias = sqldb.SQLStrValid(node.Alias) EncodeHexColor(node.Color.UnwrapOr(color.RGBA{})),
)
params.Alias = sqldb.SQLStrValid(node.Alias.UnwrapOr(""))
params.Signature = node.AuthSigBytes params.Signature = node.AuthSigBytes
} }
+12 -22
View File
@@ -384,9 +384,7 @@ func TestMigrateGraphToSQL(t *testing.T) {
// The PruneGraph call requires that the source // The PruneGraph call requires that the source
// node be set. So that is the first object // node be set. So that is the first object
// we will write. // we will write.
&models.Node{ models.NewV1ShellNode(testPub),
PubKeyBytes: testPub,
},
// Now we add some block heights to prune // Now we add some block heights to prune
// the graph at. // the graph at.
uint32(1), uint32(2), uint32(20), uint32(3), 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 // makeTestNode can be used to create a test models.Node. The
// functional options can be used to modify the node's attributes. // functional options can be used to modify the node's attributes.
func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node { func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node {
n := &models.Node{ n := models.NewV1Node(genPubKey(t), &models.NodeV1Fields{
AuthSigBytes: testSigBytes, AuthSigBytes: testSigBytes,
LastUpdate: testTime, LastUpdate: testTime,
Color: testColor, Color: testColor,
Alias: "kek", Alias: "kek",
Features: testFeatures, Features: testFeatures.RawFeatureVector,
Addresses: testAddrs, Addresses: testAddrs,
ExtraOpaqueData: testExtraData, ExtraOpaqueData: testExtraData,
PubKeyBytes: genPubKey(t), })
}
for _, opt := range opts { for _, opt := range opts {
opt(n) opt(n)
@@ -774,11 +771,7 @@ func makeTestNode(t *testing.T, opts ...testNodeOpt) *models.Node {
func makeTestShellNode(t *testing.T, func makeTestShellNode(t *testing.T,
opts ...testNodeOpt) *models.Node { opts ...testNodeOpt) *models.Node {
n := &models.Node{ n := models.NewV1ShellNode(genPubKey(t))
PubKeyBytes: genPubKey(t),
Features: testEmptyFeatures,
LastUpdate: time.Unix(0, 0),
}
for _, opt := range opts { for _, opt := range opts {
opt(n) opt(n)
@@ -1813,18 +1806,15 @@ func genRandomNode(t *rapid.T) *models.Node {
extraOpaqueData = nil extraOpaqueData = nil
} }
node := &models.Node{ node := models.NewV1Node(pubKeyBytes, &models.NodeV1Fields{
AuthSigBytes: sigBytes, AuthSigBytes: sigBytes,
LastUpdate: randTime, LastUpdate: randTime,
Color: randColor, Color: randColor,
Alias: alias.String(), Alias: alias.String(),
Features: lnwire.NewFeatureVector( Features: features,
features, lnwire.Features,
),
Addresses: addrs, Addresses: addrs,
ExtraOpaqueData: extraOpaqueData, ExtraOpaqueData: extraOpaqueData,
PubKeyBytes: pubKeyBytes, })
}
// We call this method so that the internal pubkey field is populated // We call this method so that the internal pubkey field is populated
// which then lets us to proper struct comparison later on. // which then lets us to proper struct comparison later on.
+38 -17
View File
@@ -7,6 +7,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
color "image/color"
"iter" "iter"
"maps" "maps"
"math" "math"
@@ -555,14 +556,14 @@ func (s *SQLStore) SetSourceNode(ctx context.Context,
// //
// NOTE: This is part of the V1Store interface. // NOTE: This is part of the V1Store interface.
func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time,
opts ...IteratorOption) iter.Seq2[models.Node, error] { opts ...IteratorOption) iter.Seq2[*models.Node, error] {
cfg := defaultIteratorConfig() cfg := defaultIteratorConfig()
for _, opt := range opts { for _, opt := range opts {
opt(cfg) opt(cfg)
} }
return func(yield func(models.Node, error) bool) { return func(yield func(*models.Node, error) bool) {
var ( var (
ctx = context.TODO() ctx = context.TODO()
lastUpdateTime sql.NullInt64 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 // Each iteration, we'll read a batch amount of nodes, yield
// them, then decide is we have more or not. // them, then decide is we have more or not.
for hasMore { for hasMore {
var batch []models.Node var batch []*models.Node
//nolint:ll //nolint:ll
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { 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( err = forEachNodeInBatch(
ctx, s.cfg.QueryCfg, db, rows, ctx, s.cfg.QueryCfg, db, rows,
func(_ int64, node *models.Node) error { func(_ int64, node *models.Node) error {
batch = append(batch, *node) batch = append(batch, node)
// Update pagination cursors // Update pagination cursors
// based on the last processed // based on the last processed
@@ -629,14 +630,14 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time,
return nil return nil
}, func() { }, func() {
batch = []models.Node{} batch = []*models.Node{}
}) })
if err != nil { if err != nil {
log.Errorf("NodeUpdatesInHorizon batch "+ log.Errorf("NodeUpdatesInHorizon batch "+
"error: %v", err) "error: %v", err)
yield(models.Node{}, err) yield(&models.Node{}, err)
return return
} }
@@ -3485,27 +3486,30 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode,
var pub [33]byte var pub [33]byte
copy(pub[:], dbNode.PubKey) copy(pub[:], dbNode.PubKey)
node := &models.Node{ node := models.NewV1ShellNode(pub)
PubKeyBytes: pub,
Features: lnwire.EmptyFeatureVector(),
LastUpdate: time.Unix(0, 0),
}
if len(dbNode.Signature) == 0 { if len(dbNode.Signature) == 0 {
return node, nil return node, nil
} }
node.AuthSigBytes = dbNode.Signature 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 var err error
if dbNode.Color.Valid { if dbNode.Color.Valid {
node.Color, err = DecodeHexColor(dbNode.Color.String) nodeColor, err := DecodeHexColor(dbNode.Color.String)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to decode color: %w", return nil, fmt.Errorf("unable to decode color: %w",
err) err)
} }
node.Color = fn.Some(nodeColor)
} }
// Use preloaded features. // Use preloaded features.
@@ -3608,9 +3612,26 @@ func upsertNode(ctx context.Context, db SQLQueries,
} }
if node.HaveAnnouncement() { if node.HaveAnnouncement() {
params.LastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) switch node.Version {
params.Color = sqldb.SQLStrValid(EncodeHexColor(node.Color)) case lnwire.GossipVersion1:
params.Alias = sqldb.SQLStrValid(node.Alias) 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 params.Signature = node.AuthSigBytes
} }
+16 -18
View File
@@ -83,15 +83,16 @@ func createTestNode(t *testing.T) *models.Node {
require.NoError(t, err) require.NoError(t, err)
pub := priv.PubKey().SerializeCompressed() pub := priv.PubKey().SerializeCompressed()
n := &models.Node{ n := models.NewV1Node(
LastUpdate: time.Unix(updateTime, 0), route.NewVertex(priv.PubKey()), &models.NodeV1Fields{
Addresses: testAddrs, LastUpdate: time.Unix(updateTime, 0),
Color: color.RGBA{1, 2, 3, 0}, Addresses: testAddrs,
Alias: "kek" + hex.EncodeToString(pub), Color: color.RGBA{1, 2, 3, 0},
AuthSigBytes: testSig.Serialize(), Alias: "kek" + hex.EncodeToString(pub),
Features: testFeatures, AuthSigBytes: testSig.Serialize(),
} Features: testFeatures.RawFeatureVector,
copy(n.PubKeyBytes[:], pub) },
)
return n return n
} }
@@ -699,15 +700,12 @@ func TestNodeUpdateNotification(t *testing.T) {
t, testFeaturesBuf.Bytes(), featuresBuf.Bytes(), t, testFeaturesBuf.Bytes(), featuresBuf.Bytes(),
) )
if nodeUpdate.Alias != ann.Alias { require.Equal(t, nodeUpdate.Alias, ann.Alias.UnwrapOr(""))
t.Fatalf("node alias doesn't match: expected %v, got %v", require.Equal(
ann.Alias, nodeUpdate.Alias) t, nodeUpdate.Color, graphdb.EncodeHexColor(
} ann.Color.UnwrapOr(color.RGBA{}),
if nodeUpdate.Color != graphdb.EncodeHexColor(ann.Color) { ),
t.Fatalf("node color doesn't match: expected %v, "+ )
"got %v", graphdb.EncodeHexColor(ann.Color),
nodeUpdate.Color)
}
} }
// Create lookup map for notifications we are intending to receive. Entries // Create lookup map for notifications we are intending to receive. Entries
+15 -16
View File
@@ -226,18 +226,7 @@ func (s *Server) ImportGraph(ctx context.Context,
var err error var err error
for _, rpcNode := range graph.Nodes { for _, rpcNode := range graph.Nodes {
node := &models.Node{ pubKeyBytes, err := parsePubKey(rpcNode.PubKey)
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -254,15 +243,25 @@ func (s *Server) ImportGraph(ctx context.Context,
} }
featureVector := lnwire.NewRawFeatureVector(featureBits...) 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 { if err != nil {
return nil, err 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 { if err := graphDB.AddNode(ctx, node); err != nil {
return nil, fmt.Errorf("unable to add node %v: %w", return nil, fmt.Errorf("unable to add node %v: %w",
rpcNode.PubKey, err) rpcNode.PubKey, err)
+21 -17
View File
@@ -228,14 +228,16 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
return nil, err return nil, err
} }
dbNode := &models.Node{ pubKey, err := route.NewVertexFromBytes(pubBytes)
require.NoError(t, err)
dbNode := models.NewV1Node(pubKey, &models.NodeV1Fields{
AuthSigBytes: testSig.Serialize(), AuthSigBytes: testSig.Serialize(),
LastUpdate: testTime, LastUpdate: testTime,
Addresses: testAddrs, Addresses: testAddrs,
Alias: node.Alias, Alias: node.Alias,
Features: testFeatures, Features: testFeatures.RawFeatureVector,
} })
copy(dbNode.PubKeyBytes[:], pubBytes)
// We require all aliases within the graph to be unique for our // We require all aliases within the graph to be unique for our
// tests. // tests.
@@ -564,15 +566,15 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
features = lnwire.EmptyFeatureVector() features = lnwire.EmptyFeatureVector()
} }
dbNode := &models.Node{ dbNode := models.NewV1Node(
AuthSigBytes: testSig.Serialize(), route.NewVertex(pubKey), &models.NodeV1Fields{
LastUpdate: testTime, AuthSigBytes: testSig.Serialize(),
Addresses: testAddrs, LastUpdate: testTime,
Alias: alias, Addresses: testAddrs,
Features: features, Alias: alias,
} Features: features.RawFeatureVector,
},
copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) )
privKeyMap[alias] = privKey privKeyMap[alias] = privKey
@@ -1249,10 +1251,12 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) {
dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex) dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex)
require.NoError(t, err, "unable to decode public key") require.NoError(t, err, "unable to decode public key")
doge := &models.Node{} pubKey, err := route.NewVertexFromBytes(dogePubKeyBytes)
copy(doge.PubKeyBytes[:], dogePubKeyBytes[:]) require.NoError(t, err)
doge.Alias = "doge"
copy(doge.PubKeyBytes[:], dogePubKeyBytes) doge := models.NewV1Node(pubKey, &models.NodeV1Fields{
Alias: "doge",
})
graph.aliasMap["doge"] = doge.PubKeyBytes graph.aliasMap["doge"] = doge.PubKeyBytes
// Create the channel edge going from songoku to doge and include it in // Create the channel edge going from songoku to doge and include it in
+3 -2
View File
@@ -89,8 +89,9 @@ func TestUpdateAdditionalEdge(t *testing.T) {
// Create a minimal test node using the private key priv1. // Create a minimal test node using the private key priv1.
pub := priv1.PubKey().SerializeCompressed() pub := priv1.PubKey().SerializeCompressed()
testNode := &models.Node{} var pubKey [33]byte
copy(testNode.PubKeyBytes[:], pub) copy(pubKey[:], pub)
testNode := models.NewV1ShellNode(pubKey)
nodeID, err := testNode.PubKey() nodeID, err := testNode.PubKey()
require.NoError(t, err, "failed to get node id") require.NoError(t, err, "failed to get node id")
+30 -27
View File
@@ -192,15 +192,16 @@ func createTestNode() (*models.Node, error) {
} }
pub := priv.PubKey().SerializeCompressed() pub := priv.PubKey().SerializeCompressed()
n := &models.Node{ n := models.NewV1Node(
LastUpdate: time.Unix(updateTime, 0), route.NewVertex(priv.PubKey()), &models.NodeV1Fields{
Addresses: testAddrs, LastUpdate: time.Unix(updateTime, 0),
Color: color.RGBA{1, 2, 3, 0}, Addresses: testAddrs,
Alias: "kek" + string(pub), Color: color.RGBA{1, 2, 3, 0},
AuthSigBytes: testSig.Serialize(), Alias: "kek" + string(pub),
Features: testFeatures, AuthSigBytes: testSig.Serialize(),
} Features: testFeatures.RawFeatureVector,
copy(n.PubKeyBytes[:], pub) },
)
return n, nil 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 // Now check that we can update the node info for the partial node
// without messing up the channel graph. // without messing up the channel graph.
n1 := &models.Node{ n1 := models.NewV1Node(
LastUpdate: time.Unix(123, 0), route.NewVertex(priv1.PubKey()), &models.NodeV1Fields{
Addresses: testAddrs, LastUpdate: time.Unix(123, 0),
Color: color.RGBA{1, 2, 3, 0}, Addresses: testAddrs,
Alias: "node11", Color: color.RGBA{1, 2, 3, 0},
AuthSigBytes: testSig.Serialize(), Alias: "node11",
Features: testFeatures, AuthSigBytes: testSig.Serialize(),
} Features: testFeatures.RawFeatureVector,
copy(n1.PubKeyBytes[:], priv1.PubKey().SerializeCompressed()) },
)
require.NoError(t, ctx.graph.AddNode(ctxb, n1)) require.NoError(t, ctx.graph.AddNode(ctxb, n1))
n2 := &models.Node{ n2 := models.NewV1Node(
LastUpdate: time.Unix(123, 0), route.NewVertex(priv2.PubKey()), &models.NodeV1Fields{
Addresses: testAddrs, LastUpdate: time.Unix(123, 0),
Color: color.RGBA{1, 2, 3, 0}, Addresses: testAddrs,
Alias: "node22", Color: color.RGBA{1, 2, 3, 0},
AuthSigBytes: testSig.Serialize(), Alias: "node22",
Features: testFeatures, AuthSigBytes: testSig.Serialize(),
} Features: testFeatures.RawFeatureVector,
copy(n2.PubKeyBytes[:], priv2.PubKey().SerializeCompressed()) },
)
require.NoError(t, ctx.graph.AddNode(ctxb, n2)) require.NoError(t, ctx.graph.AddNode(ctxb, n2))
+10 -7
View File
@@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"image/color"
"io" "io"
"maps" "maps"
"math" "math"
@@ -7187,11 +7188,13 @@ func marshalNode(node *models.Node) *lnrpc.LightningNode {
customRecords := marshalExtraOpaqueData(node.ExtraOpaqueData) customRecords := marshalExtraOpaqueData(node.ExtraOpaqueData)
return &lnrpc.LightningNode{ return &lnrpc.LightningNode{
LastUpdate: uint32(node.LastUpdate.Unix()), LastUpdate: uint32(node.LastUpdate.Unix()),
PubKey: hex.EncodeToString(node.PubKeyBytes[:]), PubKey: hex.EncodeToString(node.PubKeyBytes[:]),
Addresses: nodeAddrs, Addresses: nodeAddrs,
Alias: node.Alias, Alias: node.Alias.UnwrapOr(""),
Color: graphdb.EncodeHexColor(node.Color), Color: graphdb.EncodeHexColor(
node.Color.UnwrapOr(color.RGBA{}),
),
Features: features, Features: features,
CustomRecords: customRecords, CustomRecords: customRecords,
} }
@@ -8258,9 +8261,9 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context,
} }
// Cache the peer alias. // 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? // TODO(roasbeef): add settlement latency?
+30 -25
View File
@@ -7,6 +7,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"image/color"
"math/big" "math/big"
prand "math/rand" prand "math/rand"
"net" "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 // Finally, we'll update the on-disk version of our announcement so it
// will eventually propagate to nodes in the network. // will eventually propagate to nodes in the network.
selfNode := &models.Node{ selfNode := models.NewV1Node(
LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), route.NewVertex(s.identityECDH.PubKey()), &models.NodeV1Fields{
Addresses: newNodeAnn.Addresses, Addresses: newNodeAnn.Addresses,
Alias: newNodeAnn.Alias.String(), Features: newNodeAnn.Features,
Features: lnwire.NewFeatureVector( AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(),
newNodeAnn.Features, lnwire.Features, Color: newNodeAnn.RGBColor,
), Alias: newNodeAnn.Alias.String(),
Color: newNodeAnn.RGBColor, LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0),
AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), },
} )
copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed())
if err := s.graphDB.SetSourceNode(ctx, selfNode); err != nil { if err := s.graphDB.SetSourceNode(ctx, selfNode); err != nil {
return fmt.Errorf("can't set self node: %w", err) 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.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0)
selfNode.Addresses = newNodeAnn.Addresses selfNode.Addresses = newNodeAnn.Addresses
selfNode.Alias = newNodeAnn.Alias.String() selfNode.Alias = fn.Some(newNodeAnn.Alias.String())
selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn) selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn)
selfNode.Color = newNodeAnn.RGBColor selfNode.Color = fn.Some(newNodeAnn.RGBColor)
selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes() selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes()
copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed()) 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 // 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 // color is not changed from default (#3399FF) and we have a value in
// the source node. // the source node.
color, err := lncfg.ParseHexColor(s.cfg.Color) nodeColor, err := lncfg.ParseHexColor(s.cfg.Color)
if err != nil { if err != nil {
return fmt.Errorf("unable to parse color: %w", err) 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 // didn't specify a different color in the config. We'll use the
// source node's color. // source node's color.
if s.cfg.Color == defaultColor { 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 // If an alias is not specified in the config, we'll use the
// source node's alias. // source node's alias.
if 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 // 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 // TODO(abdulkbk): potentially find a way to use the source node's
// features in the self node. // features in the self node.
selfNode := &models.Node{ selfNode := models.NewV1Node(
LastUpdate: nodeLastUpdate, nodePub, &models.NodeV1Fields{
Addresses: addrs, Alias: nodeAlias.String(),
Alias: nodeAlias.String(), Color: nodeColor,
Color: color, LastUpdate: nodeLastUpdate,
Features: s.featureMgr.Get(feature.SetNodeAnn), Addresses: addrs,
} Features: s.featureMgr.GetRaw(feature.SetNodeAnn),
},
copy(selfNode.PubKeyBytes[:], nodePub[:]) )
// Based on the disk representation of the node announcement generated // Based on the disk representation of the node announcement generated
// above, we'll generate a node announcement that can go out on the // above, we'll generate a node announcement that can go out on the