diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index 78b738d3e..efddfb81e 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -31,12 +32,14 @@ type testGraph interface { } type testDBGraph struct { - db *graphdb.ChannelGraph + db *graphdb.VersionedGraph databaseChannelGraph } func newDiskChanGraph(t *testing.T) (testGraph, error) { - graphDB := graphdb.MakeTestGraph(t) + graphDB := graphdb.NewVersionedGraph( + graphdb.MakeTestGraph(t), lnwire.GossipVersion1, + ) require.NoError(t, graphDB.Start()) t.Cleanup(func() { require.NoError(t, graphDB.Stop()) @@ -490,20 +493,26 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, } chanID := randChanID() - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - Capacity: capacity, - Features: lnwire.EmptyFeatureVector(), + nodeKey1 := route.NewVertex(lnNode1) + nodeKey2 := route.NewVertex(lnNode2) + btcKey1 := route.NewVertex(lnNode1) + btcKey2 := route.NewVertex(lnNode2) + edge, err := models.NewV1Channel( + chanID.ToUint64(), chainhash.Hash{}, nodeKey1, nodeKey2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithCapacity(capacity), + ) + if err != nil { + return nil, nil, err } - copy(edge.NodeKey1Bytes[:], lnNode1.SerializeCompressed()) - copy(edge.NodeKey2Bytes[:], lnNode2.SerializeCompressed()) - copy(edge.BitcoinKey1Bytes[:], lnNode1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], lnNode2.SerializeCompressed()) if err := d.db.AddChannelEdge(ctx, edge); err != nil { return nil, nil, err } edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Now(), @@ -520,6 +529,7 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, return nil, nil, err } edgePolicy = &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Now(), diff --git a/config_builder.go b/config_builder.go index 7ce63041e..3fe62cd2a 100644 --- a/config_builder.go +++ b/config_builder.go @@ -1096,7 +1096,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( // The graph store implementation we will use depends on whether // native SQL is enabled or not. - var graphStore graphdb.V1Store + var graphStore graphdb.Store // Instantiate a native SQL store if the flag is set. if d.cfg.DB.UseNativeSQL { diff --git a/discovery/chan_series.go b/discovery/chan_series.go index 050b82b10..9ba9607e9 100644 --- a/discovery/chan_series.go +++ b/discovery/chan_series.go @@ -133,8 +133,7 @@ func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash, //nolint:ll chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info.AuthProof, channel.Info, - channel.Policy1, channel.Policy2, + channel.Info, channel.Policy1, channel.Policy2, ) if err != nil { if !yield(nil, err) { @@ -281,8 +280,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, } chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info.AuthProof, channel.Info, channel.Policy1, - channel.Policy2, + channel.Info, channel.Policy1, channel.Policy2, ) if err != nil { return nil, err diff --git a/discovery/gossiper.go b/discovery/gossiper.go index ee83d5048..d2375cb1d 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2096,10 +2096,14 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context, return nil, nil } + // Attach the proof to the channel info before creating the + // announcement. + chanInfo.AuthProof = proof + // We'll then create then validate the new fully assembled // announcement. chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - proof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { return nil, err @@ -2492,44 +2496,13 @@ func (d *AuthenticatedGossiper) updateChannel(ctx context.Context, // have a full channel announcement for this channel. var chanAnn *lnwire.ChannelAnnouncement1 if info.AuthProof != nil { - chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) - chanAnn = &lnwire.ChannelAnnouncement1{ - ShortChannelID: chanID, - NodeID1: info.NodeKey1Bytes, - NodeID2: info.NodeKey2Bytes, - ChainHash: info.ChainHash, - BitcoinKey1: info.BitcoinKey1Bytes, - Features: lnwire.NewRawFeatureVector(), - BitcoinKey2: info.BitcoinKey2Bytes, - ExtraOpaqueData: info.ExtraOpaqueData, - } - chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig1Bytes, - ) - if err != nil { - return nil, nil, err - } - chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig2Bytes, - ) - if err != nil { - return nil, nil, err - } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig1Bytes, - ) - if err != nil { - return nil, nil, err - } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig2Bytes, - ) + chanAnn, err = info.ToChannelAnnouncement() if err != nil { return nil, nil, err } } - return chanAnn, chanUpdate, err + return chanAnn, chanUpdate, nil } // SyncManager returns the gossiper's SyncManager instance. @@ -2808,28 +2781,36 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // If the proof checks out, then we'll save the proof itself to // the database so we can fetch it later when gossiping with // other nodes. - proof = &models.ChannelAuthProof{ - NodeSig1Bytes: ann.NodeSig1.ToSignatureBytes(), - NodeSig2Bytes: ann.NodeSig2.ToSignatureBytes(), - BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(), - BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(), - } + proof = models.NewV1ChannelAuthProof( + ann.NodeSig1.ToSignatureBytes(), + ann.NodeSig2.ToSignatureBytes(), + ann.BitcoinSig1.ToSignatureBytes(), + ann.BitcoinSig2.ToSignatureBytes(), + ) } // With the proof validated (if necessary), we can now store it within // the database for our path finding and syncing needs. - edge := &models.ChannelEdgeInfo{ - ChannelID: scid.ToUint64(), - ChainHash: ann.ChainHash, - NodeKey1Bytes: ann.NodeID1, - NodeKey2Bytes: ann.NodeID2, - BitcoinKey1Bytes: ann.BitcoinKey1, - BitcoinKey2Bytes: ann.BitcoinKey2, - AuthProof: proof, - Features: lnwire.NewFeatureVector( - ann.Features, lnwire.Features, - ), - ExtraOpaqueData: ann.ExtraOpaqueData, + edge, err := models.NewV1Channel( + scid.ToUint64(), ann.ChainHash, ann.NodeID1, ann.NodeID2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: ann.BitcoinKey1, + BitcoinKey2Bytes: ann.BitcoinKey2, + ExtraOpaqueData: ann.ExtraOpaqueData, + }, + models.WithChanProof(proof), models.WithFeatures(ann.Features), + ) + if err != nil { + key := newRejectCacheKey( + ann.GossipVersion(), + scid.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) + + log.Errorf("unable to create channel edge: %v", err) + nMsg.err <- err + return nil, false } // If there were any optional message fields provided, we'll include @@ -3441,19 +3422,12 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context, // different alias. This might mean that SigBytes is incorrect as it // signs a different SCID than the database SCID, but since there will // only be a difference if AuthProof == nil, this is fine. - update := &models.ChannelEdgePolicy{ - SigBytes: upd.Signature.ToSignatureBytes(), - ChannelID: chanInfo.ChannelID, - LastUpdate: timestamp, - MessageFlags: upd.MessageFlags, - ChannelFlags: upd.ChannelFlags, - TimeLockDelta: upd.TimeLockDelta, - MinHTLC: upd.HtlcMinimumMsat, - MaxHTLC: upd.HtlcMaximumMsat, - FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), - FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), - InboundFee: upd.InboundFee.ValOpt(), - ExtraOpaqueData: upd.ExtraOpaqueData, + update, err := models.ChanEdgePolicyFromWire( + chanInfo.ChannelID, upd, + ) + if err != nil { + nMsg.err <- err + return nil, false } if err := d.cfg.Graph.UpdateEdge(ctx, update, ops...); err != nil { @@ -3695,7 +3669,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, ann.ChannelID, peerID) ca, _, _, err := netann.CreateChanAnnouncement( - chanInfo.AuthProof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { log.Errorf("unable to gen ann: %v", @@ -3757,21 +3731,29 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, // We now have both halves of the channel announcement proof, then // we'll reconstruct the initial announcement so we can validate it // shortly below. - var dbProof models.ChannelAuthProof + var dbProof *models.ChannelAuthProof if isFirstNode { - dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes() + dbProof = models.NewV1ChannelAuthProof( + ann.NodeSignature.ToSignatureBytes(), + oppProof.NodeSignature.ToSignatureBytes(), + ann.BitcoinSignature.ToSignatureBytes(), + oppProof.BitcoinSignature.ToSignatureBytes(), + ) } else { - dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes() + dbProof = models.NewV1ChannelAuthProof( + oppProof.NodeSignature.ToSignatureBytes(), + ann.NodeSignature.ToSignatureBytes(), + oppProof.BitcoinSignature.ToSignatureBytes(), + ann.BitcoinSignature.ToSignatureBytes(), + ) } + // Attach the proof to the channel info before creating the + // announcement. + chanInfo.AuthProof = dbProof + chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - &dbProof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { log.Error(err) @@ -3797,7 +3779,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, // attest to the bitcoin keys by validating the signatures of // announcement. If proof is valid then we'll populate the channel edge // with it, so we can announce it on peer connect. - err = d.cfg.Graph.AddProof(ann.ShortChannelID, &dbProof) + err = d.cfg.Graph.AddProof(ann.ShortChannelID, dbProof) if err != nil { err := fmt.Errorf("unable add proof to the channel chanID=%v:"+ " %v", ann.ChannelID, err) diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index efcedb0e9..74409196b 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -272,10 +272,15 @@ func (r *mockGraphSource) GetChannelByID(chanID lnwire.ShortChannelID) ( return nil, nil, nil, graphdb.ErrEdgeNotFound } - return &models.ChannelEdgeInfo{ - NodeKey1Bytes: pubKeys[0], - NodeKey2Bytes: pubKeys[1], - }, nil, nil, graphdb.ErrZombieEdge + zombieEdge, err := models.NewV1Channel( + 0, chainhash.Hash{}, pubKeys[0], pubKeys[1], + &models.ChannelV1Fields{}, + ) + if err != nil { + return nil, nil, nil, err + } + + return zombieEdge, nil, nil, graphdb.ErrZombieEdge } edges := r.edges[chanID.ToUint64()] @@ -4443,7 +4448,9 @@ func TestRateLimitChannelUpdates(t *testing.T) { // our rebroadcast interval. rateLimitKeepAliveUpdate := *batch.chanUpdAnn1 rateLimitKeepAliveUpdate.Timestamp++ - require.NoError(t, signUpdate(remoteKeyPriv1, &rateLimitKeepAliveUpdate)) + require.NoError( + t, signUpdate(remoteKeyPriv1, &rateLimitKeepAliveUpdate), + ) assertRateLimit(&rateLimitKeepAliveUpdate, nodePeer1, true) keepAliveUpdate := *batch.chanUpdAnn1 diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index 1ba4f3fd9..f81ba63dc 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -176,6 +176,11 @@ * Freeze the [graph SQL migration code](https://github.com/lightningnetwork/lnd/pull/10338) to prevent the need for maintenance as the sqlc code evolves. +* Prepare the graph DB for handling gossip V2 + nodes and channels [1](https://github.com/lightningnetwork/lnd/pull/10339) + [2](https://github.com/lightningnetwork/lnd/pull/10379) + [3](https://github.com/lightningnetwork/lnd/pull/10380) + [4](https://github.com/lightningnetwork/lnd/pull/10542). ## Code Health diff --git a/graph/builder.go b/graph/builder.go index 91040dd08..9d3baebfe 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -111,7 +111,8 @@ type Builder struct { bestHeight atomic.Uint32 - cfg *Config + cfg *Config + v1Graph *graphdb.VersionedGraph // newBlocks is a channel in which new blocks connected to the end of // the main chain are sent over, and blocks updated after a call to @@ -146,7 +147,11 @@ var _ ChannelGraphSource = (*Builder)(nil) // NewBuilder constructs a new Builder. func NewBuilder(cfg *Config) (*Builder, error) { return &Builder{ - cfg: cfg, + cfg: cfg, + // For now, we'll just use V1 graph reader. + v1Graph: graphdb.NewVersionedGraph( + cfg.Graph, lnwire.GossipVersion1, + ), channelEdgeMtx: multimutex.NewMutex[uint64](), statTicker: ticker.New(defaultStatInterval), stats: new(builderStats), @@ -620,7 +625,7 @@ func (b *Builder) pruneZombieChans() error { toPrune = append(toPrune, chanID) log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID) } - err := b.cfg.Graph.DeleteChannelEdges( + err := b.v1Graph.DeleteChannelEdges( b.cfg.StrictZombiePruning, true, toPrune..., ) if err != nil { @@ -874,7 +879,7 @@ func (b *Builder) assertNodeAnnFreshness(ctx context.Context, node route.Vertex, // node announcements, we will ignore such nodes. If we do know about // this node, check that this update brings info newer than what we // already have. - lastUpdate, exists, err := b.cfg.Graph.HasNode(ctx, node) + lastUpdate, exists, err := b.cfg.Graph.HasV1Node(ctx, node) if err != nil { return fmt.Errorf("unable to query for the "+ "existence of node: %w", err) @@ -947,19 +952,12 @@ func (b *Builder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool { return false } - update := &models.ChannelEdgePolicy{ - SigBytes: msg.Signature.ToSignatureBytes(), - ChannelID: msg.ShortChannelID.ToUint64(), - LastUpdate: time.Unix(int64(msg.Timestamp), 0), - MessageFlags: msg.MessageFlags, - ChannelFlags: msg.ChannelFlags, - TimeLockDelta: msg.TimeLockDelta, - MinHTLC: msg.HtlcMinimumMsat, - MaxHTLC: msg.HtlcMaximumMsat, - FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), - FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), - InboundFee: msg.InboundFee.ValOpt(), - ExtraOpaqueData: msg.ExtraOpaqueData, + update, err := models.ChanEdgePolicyFromWire( + msg.ShortChannelID.ToUint64(), msg, + ) + if err != nil { + log.Errorf("Unable to parse channel update: %v", err) + return false } err = b.UpdateEdge(ctx, update) @@ -1045,8 +1043,8 @@ func (b *Builder) addEdge(ctx context.Context, edge *models.ChannelEdgeInfo, // Prior to processing the announcement we first check if we // already know of this channel, if so, then we can exit early. - _, _, exists, isZombie, err := b.cfg.Graph.HasChannelEdge( - edge.ChannelID, + exists, isZombie, err := b.cfg.Graph.HasChannelEdge( + edge.Version, edge.ChannelID, ) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { return fmt.Errorf("unable to check for edge existence: %w", @@ -1147,7 +1145,7 @@ func (b *Builder) updateEdge(ctx context.Context, defer b.channelEdgeMtx.Unlock(policy.ChannelID) edge1Timestamp, edge2Timestamp, exists, isZombie, err := - b.cfg.Graph.HasChannelEdge(policy.ChannelID) + b.cfg.Graph.HasV1ChannelEdge(policy.ChannelID) if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) { return fmt.Errorf("unable to check for edge existence: %w", err) } @@ -1266,7 +1264,7 @@ func (b *Builder) GetChannelByID(chanID lnwire.ShortChannelID) ( func (b *Builder) FetchNode(ctx context.Context, node route.Vertex) (*models.Node, error) { - return b.cfg.Graph.FetchNode(ctx, node) + return b.v1Graph.FetchNode(ctx, node) } // ForAllOutgoingChannels is used to iterate over all outgoing channels owned by @@ -1278,7 +1276,7 @@ func (b *Builder) ForAllOutgoingChannels(ctx context.Context, reset func()) error { return b.cfg.Graph.ForEachNodeChannel( - ctx, b.cfg.SelfNode, + ctx, lnwire.GossipVersion1, b.cfg.SelfNode, func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -1325,7 +1323,7 @@ func (b *Builder) IsStaleNode(ctx context.Context, node route.Vertex, // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) { - return b.cfg.Graph.IsPublicNode(node) + return b.v1Graph.IsPublicNode(node) } // IsKnownEdge returns true if the graph source already knows of the passed @@ -1333,8 +1331,8 @@ func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) { // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool { - _, _, exists, isZombie, _ := b.cfg.Graph.HasChannelEdge( - chanID.ToUint64(), + exists, isZombie, _ := b.cfg.Graph.HasChannelEdge( + lnwire.GossipVersion1, chanID.ToUint64(), ) return exists || isZombie @@ -1345,7 +1343,9 @@ func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool { // // NOTE: This method is part of the ChannelGraphSource interface. func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) { - _, _, _, isZombie, err := b.cfg.Graph.HasChannelEdge(chanID.ToUint64()) + _, isZombie, err := b.cfg.Graph.HasChannelEdge( + lnwire.GossipVersion1, chanID.ToUint64(), + ) return isZombie, err } @@ -1358,7 +1358,7 @@ func (b *Builder) IsStaleEdgePolicy(chanID lnwire.ShortChannelID, timestamp time.Time, flags lnwire.ChanUpdateChanFlags) bool { edge1Timestamp, edge2Timestamp, exists, isZombie, err := - b.cfg.Graph.HasChannelEdge(chanID.ToUint64()) + b.cfg.Graph.HasV1ChannelEdge(chanID.ToUint64()) if err != nil { log.Debugf("Check stale edge policy got error: %v", err) return false diff --git a/graph/builder_test.go b/graph/builder_test.go index 72f271985..acab91ccf 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -17,10 +17,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" @@ -65,17 +65,17 @@ func TestAddProof(t *testing.T) { ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) // After utxo was recreated adding the edge without the proof. - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithFundingScript(script), + ) + require.NoError(t, err) require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) // Now we'll attempt to update the proof and check that it has been @@ -151,17 +151,22 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) { } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + pub1Vertex, err := route.NewVertexFromBytes(pub1[:]) + require.NoError(t, err) + pub2Vertex, err := route.NewVertexFromBytes(pub2[:]) + require.NoError(t, err) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + pub1Vertex, pub2Vertex, &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1Vertex, + BitcoinKey2Bytes: pub2Vertex, + }, models.WithFundingScript(script), + ) + require.NoError(t, err) + edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -272,48 +277,49 @@ func TestWakeUpOnStaleBranch(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - ChannelID: chanID1, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(fundingScript1), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1, err := route.NewVertexFromBytes( + bitcoinKey1.SerializeCompressed(), + ) + require.NoError(t, err) + btcKey2, err := route.NewVertexFromBytes( + bitcoinKey2.SerializeCompressed(), + ) + require.NoError(t, err) + + edge1, err := models.NewV1Channel( + chanID1, *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(models.NewV1ChannelAuthProof( + testSig.Serialize(), testSig.Serialize(), + testSig.Serialize(), testSig.Serialize(), + )), models.WithFundingScript(fundingScript1), + ) + require.NoError(t, err) if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } - edge2 := &models.ChannelEdgeInfo{ - ChannelID: chanID2, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(fundingScript2), - } - copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + edge2, err := models.NewV1Channel( + chanID2, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, + node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(models.NewV1ChannelAuthProof( + testSig.Serialize(), testSig.Serialize(), + testSig.Serialize(), testSig.Serialize(), + )), models.WithFundingScript(fundingScript2), + ) + require.NoError(t, err) if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { t.Fatalf("unable to add edge: %v", err) } // Check that the fundingTxs are in the graph db. - _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) + has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } @@ -324,7 +330,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { t.Fatal("edge was marked as zombie") } - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } @@ -359,7 +365,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { // Create new router with same graph database. router, err := NewBuilder(&Config{ SelfNode: selfNode.PubKeyBytes, - Graph: ctx.graph, + Graph: ctx.graph.ChannelGraph, Chain: ctx.chain, ChainView: ctx.chainView, ChannelPruneExpiry: time.Hour * 24, @@ -381,7 +387,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { // The channel with chanID2 should not be in the database anymore, // since it is not confirmed on the longest chain. chanID1 should // still be. - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) require.NoError(t, err) if !has { @@ -391,7 +397,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { t.Fatal("edge was marked as zombie") } - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } @@ -482,52 +488,46 @@ func TestDisconnectedBlocks(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - ChannelID: chanID1, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - BitcoinKey1Bytes: node1.PubKeyBytes, - BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some([]byte{}), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge1, err := models.NewV1Channel( + chanID1, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, + node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithFundingScript([]byte{}), + ) + require.NoError(t, err) if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } - edge2 := &models.ChannelEdgeInfo{ - ChannelID: chanID2, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - BitcoinKey1Bytes: node1.PubKeyBytes, - BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some([]byte{}), - } - copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + edge2, err := models.NewV1Channel( + chanID2, *chaincfg.SimNetParams.GenesisHash, node1.PubKeyBytes, + node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithFundingScript([]byte{}), + ) + require.NoError(t, err) if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { t.Fatalf("unable to add edge: %v", err) } // Check that the fundingTxs are in the graph db. - _, _, has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) + has, isZombie, err := ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } @@ -538,7 +538,7 @@ func TestDisconnectedBlocks(t *testing.T) { t.Fatal("edge was marked as zombie") } - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } @@ -580,7 +580,7 @@ func TestDisconnectedBlocks(t *testing.T) { // chanID2 should not be in the database anymore, since it is not // confirmed on the longest chain. chanID1 should still be. - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID1) if err != nil { t.Fatalf("error looking for edge: %v", chanID1) } @@ -591,7 +591,7 @@ func TestDisconnectedBlocks(t *testing.T) { t.Fatal("edge was marked as zombie") } - _, _, has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) + has, isZombie, err = ctx.graph.HasChannelEdge(chanID2) if err != nil { t.Fatalf("error looking for edge: %v", chanID2) } @@ -638,29 +638,34 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - ChannelID: chanID1.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - ChannelPoint: *chanUTXO, - Capacity: chanValue, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge1, err := models.NewV1Channel( + chanID1.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithCapacity(chanValue), + models.WithChannelPoint(*chanUTXO), + models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } // The router should now be aware of the channel we created above. - _, _, hasChan, isZombie, err := ctx.graph.HasChannelEdge( + hasChan, isZombie, err := ctx.graph.HasChannelEdge( chanID1.ToUint64(), ) if err != nil { @@ -742,7 +747,7 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { // At this point, the channel that was pruned should no longer be known // by the router. - _, _, hasChan, isZombie, err = ctx.graph.HasChannelEdge( + hasChan, isZombie, err = ctx.graph.HasChannelEdge( chanID1.ToUint64(), ) if err != nil { @@ -1061,16 +1066,15 @@ func TestIsStaleNode(t *testing.T) { } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + pub1, pub2, &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1141,16 +1145,16 @@ func TestIsKnownEdge(t *testing.T) { } ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - FundingScript: fn.Some(script), - Features: lnwire.EmptyFeatureVector(), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, pub1, + pub2, &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1201,22 +1205,22 @@ func TestIsStaleEdgePolicy(t *testing.T) { t.Fatalf("router failed to detect fresh edge policy") } - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, pub1, + pub2, &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } // We'll also add two edge policies, one for each direction. edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: updateTimeStamp, @@ -1231,6 +1235,7 @@ func TestIsStaleEdgePolicy(t *testing.T) { } edgePolicy = &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: updateTimeStamp, @@ -1513,18 +1518,27 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: edge.ChannelID, - AuthProof: &testAuthProof, - ChannelPoint: fundingPoint, - Capacity: btcutil.Amount(edge.Capacity), - Features: lnwire.EmptyFeatureVector(), - } + var node1Vertex, node2Vertex route.Vertex + copy(node1Vertex[:], node1Bytes) + copy(node2Vertex[:], node2Bytes) - copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) - copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], node1Bytes) + copy(btcKey2[:], node2Bytes) + + edgeInfo, err := models.NewV1Channel( + edge.ChannelID, *chaincfg.SimNetParams.GenesisHash, + node1Vertex, node2Vertex, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(fundingPoint), + models.WithCapacity(btcutil.Amount(edge.Capacity)), + ) + if err != nil { + return nil, err + } shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ @@ -1533,7 +1547,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -1546,6 +1560,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: lnwire.ChanUpdateMsgFlags( edge.MessageFlags, @@ -1573,12 +1588,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + node1Vertex, err = route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err := route.NewVertexFromBytes(node2Bytes) + node2Vertex, err = route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -1595,7 +1610,9 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graph, + graph: graphdb.NewVersionedGraph( + graph, lnwire.GossipVersion1, + ), aliasMap: aliasMap, privKeyMap: privKeyMap, channelIDs: channelIDs, @@ -1690,7 +1707,7 @@ func asymmetricTestChannel(alias1, alias2 string, capacity btcutil.Amount, // assertChannelsPruned ensures that only the given channels are pruned from the // graph out of the set of all channels. -func assertChannelsPruned(t *testing.T, graph *graphdb.ChannelGraph, +func assertChannelsPruned(t *testing.T, graph *graphdb.VersionedGraph, channels []*testChannel, prunedChanIDs ...uint64) { t.Helper() @@ -1702,7 +1719,7 @@ func assertChannelsPruned(t *testing.T, graph *graphdb.ChannelGraph, for _, channel := range channels { _, shouldPrune := pruned[channel.ChannelID] - _, _, exists, isZombie, err := graph.HasChannelEdge( + exists, isZombie, err := graph.HasChannelEdge( channel.ChannelID, ) if err != nil { @@ -1882,20 +1899,21 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: channelID, - AuthProof: &testAuthProof, - ChannelPoint: *fundingPoint, - Capacity: testChannel.Capacity, - - NodeKey1Bytes: node1Vertex, - BitcoinKey1Bytes: node1Vertex, - NodeKey2Bytes: node2Vertex, - BitcoinKey2Bytes: node2Vertex, - Features: lnwire.EmptyFeatureVector(), + edgeInfo, err := models.NewV1Channel( + channelID, *chaincfg.SimNetParams.GenesisHash, + node1Vertex, node2Vertex, &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Vertex, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(*fundingPoint), + models.WithCapacity(testChannel.Capacity), + ) + if err != nil { + return nil, err } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { @@ -1925,7 +1943,9 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, channelFlags |= lnwire.ChanUpdateDisabled } + //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -1956,7 +1976,9 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } channelFlags |= lnwire.ChanUpdateDirection + //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -1980,7 +2002,9 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } return &testGraphInstance{ - graph: graph, + graph: graphdb.NewVersionedGraph( + graph, lnwire.GossipVersion1, + ), aliasMap: aliasMap, privKeyMap: privKeyMap, links: links, diff --git a/graph/db/benchmark_test.go b/graph/db/benchmark_test.go index 4c9aef7c6..00cfeaf33 100644 --- a/graph/db/benchmark_test.go +++ b/graph/db/benchmark_test.go @@ -72,19 +72,19 @@ var ( // and a function to open the connection. type dbConnection struct { name string - open func(testing.TB) V1Store + open func(testing.TB) Store } // This var block defines the various database connections that we will use // for testing. Each connection is defined as a dbConnection struct that // contains a name and an open function. The open function is used to create -// a new V1Store instance for the given database type. +// a new Store instance for the given database type. var ( // kvdbBBoltConn is a connection to a kvdb-bbolt database called // channel.db. kvdbBBoltConn = dbConnection{ name: "kvdb-bbolt", - open: func(b testing.TB) V1Store { + open: func(b testing.TB) Store { return connectBBoltDB(b, bboltDBPath, kvdbBBoltFile) }, } @@ -93,7 +93,7 @@ var ( // channel.sqlite. kvdbSqliteConn = dbConnection{ name: "kvdb-sqlite", - open: func(b testing.TB) V1Store { + open: func(b testing.TB) Store { return connectKVDBSqlite( b, kvdbSqlitePath, kvdbSqliteFile, ) @@ -104,7 +104,7 @@ var ( // called lnd.sqlite. nativeSQLSqliteConn = dbConnection{ name: "native-sqlite", - open: func(b testing.TB) V1Store { + open: func(b testing.TB) Store { return connectNativeSQLite( b, sqldb.DefaultSQLiteConfig(), nativeSQLSqlitePath, nativeSQLSqliteFile, @@ -116,7 +116,7 @@ var ( // using a postgres connection string. kvdbPostgresConn = dbConnection{ name: "kvdb-postgres", - open: func(b testing.TB) V1Store { + open: func(b testing.TB) Store { return connectKVDBPostgres(b, kvdbPostgresDNS) }, } @@ -125,7 +125,7 @@ var ( // database using a postgres connection string. nativeSQLPostgresConn = dbConnection{ name: "native-postgres", - open: func(b testing.TB) V1Store { + open: func(b testing.TB) Store { return connectNativePostgres( b, sqldb.DefaultPostgresConfig(), nativeSQLPostgresDNS, @@ -134,10 +134,10 @@ var ( } ) -// connectNativePostgres creates a V1Store instance backed by a native Postgres +// connectNativePostgres creates a Store instance backed by a native Postgres // database for testing purposes. func connectNativePostgres(t testing.TB, cfg *sqldb.QueryConfig, - dsn string) V1Store { + dsn string) Store { return newSQLStore(t, cfg, sqlPostgres(t, dsn)) } @@ -157,10 +157,10 @@ func sqlPostgres(t testing.TB, dsn string) BatchedSQLQueries { return newSQLExecutor(t, store) } -// connectNativeSQLite creates a V1Store instance backed by a native SQLite +// connectNativeSQLite creates a Store instance backed by a native SQLite // database for testing purposes. func connectNativeSQLite(t testing.TB, cfg *sqldb.QueryConfig, dbPath, - file string) V1Store { + file string) Store { return newSQLStore(t, cfg, sqlSQLite(t, dbPath, file)) } @@ -205,9 +205,9 @@ func kvdbPostgres(t testing.TB, dsn string) kvdb.Backend { return kvStore } -// connectKVDBPostgres creates a V1Store instance backed by a kvdb-postgres +// connectKVDBPostgres creates a Store instance backed by a kvdb-postgres // database for testing purposes. -func connectKVDBPostgres(t testing.TB, dsn string) V1Store { +func connectKVDBPostgres(t testing.TB, dsn string) Store { return newKVStore(t, kvdbPostgres(t, dsn)) } @@ -231,14 +231,14 @@ func kvdbSqlite(t testing.TB, dbPath, fileName string) kvdb.Backend { return kvStore } -// connectKVDBSqlite creates a V1Store instance backed by a kvdb-sqlite +// connectKVDBSqlite creates a Store instance backed by a kvdb-sqlite // database for testing purposes. -func connectKVDBSqlite(t testing.TB, dbPath, fileName string) V1Store { +func connectKVDBSqlite(t testing.TB, dbPath, fileName string) Store { return newKVStore(t, kvdbSqlite(t, dbPath, fileName)) } // connectBBoltDB creates a new BBolt database connection for testing. -func connectBBoltDB(t testing.TB, dbPath, fileName string) V1Store { +func connectBBoltDB(t testing.TB, dbPath, fileName string) Store { return newKVStore(t, kvdbBBolt(t, dbPath, fileName)) } @@ -261,7 +261,7 @@ func kvdbBBolt(t testing.TB, dbPath, fileName string) kvdb.Backend { // newKVStore creates a new KVStore instance for testing using a provided // kvdb.Backend instance. -func newKVStore(t testing.TB, backend kvdb.Backend) V1Store { +func newKVStore(t testing.TB, backend kvdb.Backend) Store { store, err := NewKVStore(backend, testStoreOptions...) require.NoError(t, err) @@ -286,7 +286,7 @@ func newSQLExecutor(t testing.TB, db sqldb.DB) BatchedSQLQueries { // newSQLStore creates a new SQLStore instance for testing using a provided // sqldb.DB instance. func newSQLStore(t testing.TB, cfg *sqldb.QueryConfig, - db BatchedSQLQueries) V1Store { + db BatchedSQLQueries) Store { store, err := NewSQLStore( &SQLStoreConfig{ @@ -370,8 +370,8 @@ func TestPopulateDBs(t *testing.T) { numPolicies = 0 ) err := graph.ForEachChannel( - ctx, func(info *models.ChannelEdgeInfo, - policy, + ctx, lnwire.GossipVersion1, + func(info *models.ChannelEdgeInfo, policy, policy2 *models.ChannelEdgePolicy) error { numChans++ @@ -497,48 +497,49 @@ func syncGraph(t *testing.T, src, dest *ChannelGraph) { } var wgChans sync.WaitGroup - err = src.ForEachChannel(ctx, func(info *models.ChannelEdgeInfo, - policy1, policy2 *models.ChannelEdgePolicy) error { + err = src.ForEachChannel(ctx, lnwire.GossipVersion1, + func(info *models.ChannelEdgeInfo, + policy1, policy2 *models.ChannelEdgePolicy) error { - // Add each channel & policy. We do this in a goroutine to - // take advantage of batch processing. - wgChans.Add(1) - go func() { - defer wgChans.Done() + // Add each channel & policy. We do this in a goroutine + // to take advantage of batch processing. + wgChans.Add(1) + go func() { + defer wgChans.Done() - err := dest.AddChannelEdge( - ctx, info, batch.LazyAdd(), - ) - if !errors.Is(err, ErrEdgeAlreadyExist) { - require.NoError(t, err) - } - - if policy1 != nil { - err = dest.UpdateEdgePolicy( - ctx, policy1, batch.LazyAdd(), + err := dest.AddChannelEdge( + ctx, info, batch.LazyAdd(), ) - require.NoError(t, err) - } + if !errors.Is(err, ErrEdgeAlreadyExist) { + require.NoError(t, err) + } - if policy2 != nil { - err = dest.UpdateEdgePolicy( - ctx, policy2, batch.LazyAdd(), - ) - require.NoError(t, err) - } + if policy1 != nil { + err = dest.UpdateEdgePolicy( + ctx, policy1, batch.LazyAdd(), + ) + require.NoError(t, err) + } - mu.Lock() - total++ - chunk++ - s.Do(func() { - reportChanStats() - chunk = 0 - }) - mu.Unlock() - }() + if policy2 != nil { + err = dest.UpdateEdgePolicy( + ctx, policy2, batch.LazyAdd(), + ) + require.NoError(t, err) + } - return nil - }, func() {}) + mu.Lock() + total++ + chunk++ + s.Do(func() { + reportChanStats() + chunk = 0 + }) + mu.Unlock() + }() + + return nil + }, func() {}) require.NoError(t, err) wgChans.Wait() @@ -587,7 +588,7 @@ func BenchmarkCacheLoading(b *testing.B) { } } -// BenchmarkGraphReadMethods benchmarks various read calls of various V1Store +// BenchmarkGraphReadMethods benchmarks various read calls of various Store // implementations. // // NOTE: this is to be run against a local graph database. It can be run @@ -614,11 +615,11 @@ func BenchmarkGraphReadMethods(b *testing.B) { tests := []struct { name string - fn func(b testing.TB, store V1Store) + fn func(b testing.TB, store Store) }{ { name: "ForEachNode", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { err := store.ForEachNode( ctx, func(_ *models.Node) error { @@ -635,10 +636,11 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachChannel", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { //nolint:ll err := store.ForEachChannel( - ctx, func(_ *models.ChannelEdgeInfo, + ctx, lnwire.GossipVersion1, + func(_ *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -655,7 +657,7 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "NodeUpdatesInHorizon", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { iter := store.NodeUpdatesInHorizon( time.Unix(0, 0), time.Now(), ) @@ -665,7 +667,7 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachNodeCacheable", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { err := store.ForEachNodeCacheable( ctx, func(_ route.Vertex, _ *lnwire.FeatureVector) error { @@ -683,7 +685,7 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ForEachNodeCached", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { //nolint:ll err := store.ForEachNodeCached( ctx, false, func(context.Context, @@ -704,7 +706,7 @@ func BenchmarkGraphReadMethods(b *testing.B) { }, { name: "ChanUpdatesInHorizon", - fn: func(b testing.TB, store V1Store) { + fn: func(b testing.TB, store Store) { iter := store.ChanUpdatesInHorizon( time.Unix(0, 0), time.Now(), ) @@ -821,7 +823,7 @@ func BenchmarkFindOptimalSQLQueryConfig(b *testing.B) { //nolint:ll err = store.ForEachChannel( - ctx, + ctx, lnwire.GossipVersion1, func(_ *models.ChannelEdgeInfo, _, _ *models.ChannelEdgePolicy) error { diff --git a/graph/db/channel_cache_test.go b/graph/db/channel_cache_test.go index 767958d9a..27ff65432 100644 --- a/graph/db/channel_cache_test.go +++ b/graph/db/channel_cache_test.go @@ -4,7 +4,9 @@ import ( "reflect" "testing" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/routing/route" ) // TestChannelCache checks the behavior of the channelCache with respect to @@ -99,9 +101,14 @@ func assertHasChanEntries(t *testing.T, c *channelCache, start, end uint64) { // channelForInt generates a unique ChannelEdge given an integer. func channelForInt(i uint64) ChannelEdge { + info, err := models.NewV1Channel( + i, chainhash.Hash{}, route.Vertex{}, route.Vertex{}, + &models.ChannelV1Fields{}, + ) + if err != nil { + panic(err) + } return ChannelEdge{ - Info: &models.ChannelEdgeInfo{ - ChannelID: i, - }, + Info: info, } } diff --git a/graph/db/graph.go b/graph/db/graph.go index fc1ffa300..baee3eccb 100644 --- a/graph/db/graph.go +++ b/graph/db/graph.go @@ -4,12 +4,14 @@ import ( "context" "errors" "fmt" + "iter" "net" "sync" "sync/atomic" "testing" "time" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/batch" @@ -30,7 +32,7 @@ type ChannelGraph struct { graphCache *GraphCache - V1Store + db Store *topologyManager quit chan struct{} @@ -38,7 +40,7 @@ type ChannelGraph struct { } // NewChannelGraph creates a new ChannelGraph instance with the given backend. -func NewChannelGraph(v1Store V1Store, +func NewChannelGraph(v1Store Store, options ...ChanGraphOption) (*ChannelGraph, error) { opts := defaultChanGraphOptions() @@ -47,7 +49,7 @@ func NewChannelGraph(v1Store V1Store, } g := &ChannelGraph{ - V1Store: v1Store, + db: v1Store, topologyManager: newTopologyManager(), quit: make(chan struct{}), } @@ -161,7 +163,7 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error { log.Info("Populating in-memory channel graph, this might take a " + "while...") - err := c.V1Store.ForEachNodeCacheable(ctx, func(node route.Vertex, + err := c.db.ForEachNodeCacheable(ctx, func(node route.Vertex, features *lnwire.FeatureVector) error { c.graphCache.AddNodeFeatures(node, features) @@ -172,7 +174,7 @@ func (c *ChannelGraph) populateCache(ctx context.Context) error { return err } - err = c.V1Store.ForEachChannelCacheable( + err = c.db.ForEachChannelCacheable( func(info *models.CachedEdgeInfo, policy1, policy2 *models.CachedEdgePolicy) error { @@ -208,7 +210,7 @@ func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex, return c.graphCache.ForEachChannel(node, cb) } - return c.V1Store.ForEachNodeDirectedChannel(node, cb, reset) + return c.db.ForEachNodeDirectedChannel(node, cb, reset) } // FetchNodeFeatures returns the features of the given node. If no features are @@ -224,7 +226,7 @@ func (c *ChannelGraph) FetchNodeFeatures(node route.Vertex) ( return c.graphCache.GetFeatures(node), nil } - return c.V1Store.FetchNodeFeatures(node) + return c.db.FetchNodeFeatures(lnwire.GossipVersion1, node) } // GraphSession will provide the call-back with access to a NodeTraverser @@ -238,7 +240,7 @@ func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error, return cb(c) } - return c.V1Store.GraphSession(cb, reset) + return c.db.GraphSession(cb, reset) } // ForEachNodeCached iterates through all the stored vertices/nodes in the @@ -259,7 +261,7 @@ func (c *ChannelGraph) ForEachNodeCached(ctx context.Context, withAddrs bool, ) } - return c.V1Store.ForEachNodeCached(ctx, withAddrs, cb, reset) + return c.db.ForEachNodeCached(ctx, withAddrs, cb, reset) } // AddNode adds a vertex/node to the graph database. If the node is not @@ -271,7 +273,7 @@ func (c *ChannelGraph) ForEachNodeCached(ctx context.Context, withAddrs bool, func (c *ChannelGraph) AddNode(ctx context.Context, node *models.Node, op ...batch.SchedulerOption) error { - err := c.V1Store.AddNode(ctx, node, op...) + err := c.db.AddNode(ctx, node, op...) if err != nil { return err } @@ -291,23 +293,6 @@ func (c *ChannelGraph) AddNode(ctx context.Context, return nil } -// DeleteNode starts a new database transaction to remove a vertex/node -// from the database according to the node's public key. -func (c *ChannelGraph) DeleteNode(ctx context.Context, - nodePub route.Vertex) error { - - err := c.V1Store.DeleteNode(ctx, nodePub) - if err != nil { - return err - } - - if c.graphCache != nil { - c.graphCache.RemoveNode(nodePub) - } - - return nil -} - // AddChannelEdge adds a new (undirected, blank) edge to the graph database. An // undirected edge from the two target nodes are created. The information stored // denotes the static attributes of the channel, such as the channelID, the keys @@ -317,7 +302,7 @@ func (c *ChannelGraph) DeleteNode(ctx context.Context, func (c *ChannelGraph) AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error { - err := c.V1Store.AddChannelEdge(ctx, edge, op...) + err := c.db.AddChannelEdge(ctx, edge, op...) if err != nil { return err } @@ -339,7 +324,7 @@ func (c *ChannelGraph) AddChannelEdge(ctx context.Context, // If the cache is enabled, the edge will be added back to the graph cache if // we still have a record of this channel in the DB. func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { - err := c.V1Store.MarkEdgeLive(chanID) + err := c.db.MarkEdgeLive(chanID) if err != nil { return err } @@ -347,7 +332,7 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { if c.graphCache != nil { // We need to add the channel back into our graph cache, // otherwise we won't use it for path finding. - infos, err := c.V1Store.FetchChanInfos([]uint64{chanID}) + infos, err := c.db.FetchChanInfos([]uint64{chanID}) if err != nil { return err } @@ -385,8 +370,9 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, chanIDs ...uint64) error { - infos, err := c.V1Store.DeleteChannelEdges( - strictZombiePruning, markZombie, chanIDs..., + infos, err := c.db.DeleteChannelEdges( + lnwire.GossipVersion1, strictZombiePruning, markZombie, + chanIDs..., ) if err != nil { return err @@ -414,7 +400,7 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) ( []*models.ChannelEdgeInfo, error) { - edges, err := c.V1Store.DisconnectBlockAtHeight(height) + edges, err := c.db.DisconnectBlockAtHeight(height) if err != nil { return nil, err } @@ -442,7 +428,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, error) { - edges, nodes, err := c.V1Store.PruneGraph( + edges, nodes, err := c.db.PruneGraph( spentOutputs, blockHash, blockHeight, ) if err != nil { @@ -487,7 +473,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, // that we only maintain a graph of reachable nodes. In the event that a pruned // node gains more channels, it will be re-added back to the graph. func (c *ChannelGraph) PruneGraphNodes() error { - nodes, err := c.V1Store.PruneGraphNodes() + nodes, err := c.db.PruneGraphNodes() if err != nil { return err } @@ -509,7 +495,7 @@ func (c *ChannelGraph) PruneGraphNodes() error { func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) { - unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo) + unknown, knownZombies, err := c.db.FilterKnownChanIDs(chansInfo) if err != nil { return nil, err } @@ -538,7 +524,7 @@ func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, // timestamps could bring it back from the dead, then we mark it // alive, and we let it be added to the set of IDs to query our // peer for. - err := c.V1Store.MarkEdgeLive( + err := c.db.MarkEdgeLive( info.ShortChannelID.ToUint64(), ) // Since there is a chance that the edge could have been marked @@ -559,7 +545,7 @@ func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, func (c *ChannelGraph) MarkEdgeZombie(chanID uint64, pubKey1, pubKey2 [33]byte) error { - err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2) + err := c.db.MarkEdgeZombie(chanID, pubKey1, pubKey2) if err != nil { return err } @@ -581,7 +567,7 @@ func (c *ChannelGraph) MarkEdgeZombie(chanID uint64, func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context, edge *models.ChannelEdgePolicy, op ...batch.SchedulerOption) error { - from, to, err := c.V1Store.UpdateEdgePolicy(ctx, edge, op...) + from, to, err := c.db.UpdateEdgePolicy(ctx, edge, op...) if err != nil { return err } @@ -601,12 +587,353 @@ func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context, return nil } +// ForEachSourceNodeChannel iterates through all channels of the source node. +func (c *ChannelGraph) ForEachSourceNodeChannel(ctx context.Context, + cb func(chanPoint wire.OutPoint, havePolicy bool, + otherNode *models.Node) error, reset func()) error { + + return c.db.ForEachSourceNodeChannel(ctx, cb, reset) +} + +// ForEachNodeChannel iterates through all channels of the given node. +func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context, + v lnwire.GossipVersion, nodePub route.Vertex, + cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error { + + return c.db.ForEachNodeChannel(ctx, v, nodePub, cb, reset) +} + +// ForEachNode iterates through all stored vertices/nodes in the graph. +func (c *ChannelGraph) ForEachNode(ctx context.Context, + cb func(*models.Node) error, reset func()) error { + + return c.db.ForEachNode(ctx, cb, reset) +} + +// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph. +func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context, + cb func(route.Vertex, *lnwire.FeatureVector) error, + reset func()) error { + + return c.db.ForEachNodeCacheable(ctx, cb, reset) +} + +// NodeUpdatesInHorizon returns all known lightning nodes with updates in the +// range. +func (c *ChannelGraph) NodeUpdatesInHorizon(startTime, endTime time.Time, + opts ...IteratorOption) iter.Seq2[*models.Node, error] { + + return c.db.NodeUpdatesInHorizon(startTime, endTime, opts...) +} + +// HasV1Node determines if the graph has a vertex identified by the target node +// in the V1 graph. +func (c *ChannelGraph) HasV1Node(ctx context.Context, + nodePub [33]byte) (time.Time, bool, error) { + + return c.db.HasV1Node(ctx, nodePub) +} + +// IsPublicNode determines whether the node is seen as public in the graph. +func (c *ChannelGraph) IsPublicNode(pubKey [33]byte) (bool, error) { + return c.db.IsPublicNode(lnwire.GossipVersion1, pubKey) +} + +// ForEachChannel iterates through all channel edges stored within the graph. +func (c *ChannelGraph) ForEachChannel(ctx context.Context, + v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, + reset func()) error { + + return c.db.ForEachChannel(ctx, v, cb, reset) +} + +// ForEachChannelCacheable iterates through all channel edges for the cache. +func (c *ChannelGraph) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, + *models.CachedEdgePolicy, *models.CachedEdgePolicy) error, + reset func()) error { + + return c.db.ForEachChannelCacheable(cb, reset) +} + +// DisabledChannelIDs returns the channel ids of disabled channels. +func (c *ChannelGraph) DisabledChannelIDs() ([]uint64, error) { + return c.db.DisabledChannelIDs() +} + +// HasV1ChannelEdge returns true if the database knows of a channel edge. +func (c *ChannelGraph) HasV1ChannelEdge(chanID uint64) (time.Time, + time.Time, bool, bool, error) { + + return c.db.HasV1ChannelEdge(chanID) +} + +// HasChannelEdge returns true if the database knows of a channel edge. +func (c *ChannelGraph) HasChannelEdge(v lnwire.GossipVersion, + chanID uint64) (bool, bool, error) { + + return c.db.HasChannelEdge(v, chanID) +} + +// AddEdgeProof sets the proof of an existing edge in the graph database. +func (c *ChannelGraph) AddEdgeProof(chanID lnwire.ShortChannelID, + proof *models.ChannelAuthProof) error { + + return c.db.AddEdgeProof(chanID, proof) +} + +// ChannelID attempts to lookup the 8-byte compact channel ID. +func (c *ChannelGraph) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { + return c.db.ChannelID(chanPoint) +} + +// HighestChanID returns the "highest" known channel ID in the channel graph. +func (c *ChannelGraph) HighestChanID(ctx context.Context) (uint64, error) { + return c.db.HighestChanID(ctx) +} + +// ChanUpdatesInHorizon returns all known channel edges with updates in the +// horizon. +func (c *ChannelGraph) ChanUpdatesInHorizon(startTime, endTime time.Time, + opts ...IteratorOption) iter.Seq2[ChannelEdge, error] { + + return c.db.ChanUpdatesInHorizon(startTime, endTime, opts...) +} + +// FilterChannelRange returns channel IDs within the passed block height range. +func (c *ChannelGraph) FilterChannelRange(startHeight, endHeight uint32, + withTimestamps bool) ([]BlockChannelRange, error) { + + return c.db.FilterChannelRange(startHeight, endHeight, withTimestamps) +} + +// FetchChanInfos returns the set of channel edges for the passed channel IDs. +func (c *ChannelGraph) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { + return c.db.FetchChanInfos(chanIDs) +} + +// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding +// outpoint. +func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( + *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy, error) { + + return c.db.FetchChannelEdgesByOutpoint( + lnwire.GossipVersion1, op, + ) +} + +// FetchChannelEdgesByID attempts to lookup directed edges by channel ID. +func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) ( + *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy, error) { + + return c.db.FetchChannelEdgesByID( + lnwire.GossipVersion1, chanID, + ) +} + +// ChannelView returns the verifiable edge information for each active channel. +func (c *ChannelGraph) ChannelView() ([]EdgePoint, error) { + return c.db.ChannelView() +} + +// IsZombieEdge returns whether the edge is considered zombie. +func (c *ChannelGraph) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, + error) { + + return c.db.IsZombieEdge(lnwire.GossipVersion1, chanID) +} + +// NumZombies returns the current number of zombie channels in the graph. +func (c *ChannelGraph) NumZombies() (uint64, error) { + return c.db.NumZombies() +} + +// PutClosedScid stores a SCID for a closed channel in the database. +func (c *ChannelGraph) PutClosedScid(scid lnwire.ShortChannelID) error { + return c.db.PutClosedScid(scid) +} + +// IsClosedScid checks whether a channel identified by the scid is closed. +func (c *ChannelGraph) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { + return c.db.IsClosedScid(scid) +} + +// SetSourceNode sets the source node within the graph database. +func (c *ChannelGraph) SetSourceNode(ctx context.Context, + node *models.Node) error { + + return c.db.SetSourceNode(ctx, node) +} + +// PruneTip returns the block height and hash of the latest pruning block. +func (c *ChannelGraph) PruneTip() (*chainhash.Hash, uint32, error) { + return c.db.PruneTip() +} + +// VersionedGraph is a wrapper around ChannelGraph that will call underlying +// Store methods with a specific gossip version. +type VersionedGraph struct { + *ChannelGraph + v lnwire.GossipVersion +} + +// NewVersionedGraph creates a new VersionedGraph. +func NewVersionedGraph(c *ChannelGraph, + v lnwire.GossipVersion) *VersionedGraph { + + return &VersionedGraph{ + ChannelGraph: c, + v: v, + } +} + +// FetchNode attempts to look up a target node by its identity public key. +func (c *VersionedGraph) FetchNode(ctx context.Context, + nodePub route.Vertex) (*models.Node, error) { + + return c.db.FetchNode(ctx, c.v, nodePub) +} + +// FetchChannelEdgesByID attempts to lookup directed edges by channel ID. +func (c *VersionedGraph) FetchChannelEdgesByID(chanID uint64) ( + *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy, error) { + + return c.db.FetchChannelEdgesByID(c.v, chanID) +} + +// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding +// outpoint. +func (c *VersionedGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( + *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy, error) { + + return c.db.FetchChannelEdgesByOutpoint(c.v, op) +} + +// IsZombieEdge returns whether the edge is considered zombie for this version. +func (c *VersionedGraph) IsZombieEdge(chanID uint64) (bool, [33]byte, + [33]byte, error) { + + return c.db.IsZombieEdge(c.v, chanID) +} + +// AddrsForNode returns all known addresses for the target node public key. +func (c *VersionedGraph) AddrsForNode(ctx context.Context, + nodePub *btcec.PublicKey) (bool, []net.Addr, error) { + + return c.db.AddrsForNode(ctx, c.v, nodePub) +} + +// DeleteNode starts a new database transaction to remove a vertex/node +// from the database according to the node's public key. +func (c *VersionedGraph) DeleteNode(ctx context.Context, + nodePub route.Vertex) error { + + err := c.db.DeleteNode(ctx, c.v, nodePub) + if err != nil { + return err + } + + if c.graphCache != nil { + c.graphCache.RemoveNode(nodePub) + } + + return nil +} + +// HasNode determines if the graph has a vertex identified by the target node +// in the V1 graph. +func (c *VersionedGraph) HasNode(ctx context.Context, nodePub [33]byte) (bool, + error) { + + return c.db.HasNode(ctx, c.v, nodePub) +} + +// LookupAlias attempts to return the alias as advertised by the target node. +func (c *VersionedGraph) LookupAlias(ctx context.Context, + pub *btcec.PublicKey) (string, error) { + + return c.db.LookupAlias(ctx, c.v, pub) +} + +// SourceNode returns the source node of the graph. +func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node, + error) { + + return c.db.SourceNode(ctx, c.v) +} + +// DeleteChannelEdges removes edges with the given channel IDs from the +// database and marks them as zombies. This ensures that we're unable to re-add +// it to our database once again. If an edge does not exist within the +// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is +// true, then when we mark these edges as zombies, we'll set up the keys such +// that we require the node that failed to send the fresh update to be the one +// that resurrects the channel from its zombie state. The markZombie bool +// denotes whether to mark the channel as a zombie. +func (c *VersionedGraph) DeleteChannelEdges(strictZombiePruning, + markZombie bool, chanIDs ...uint64) error { + + infos, err := c.db.DeleteChannelEdges( + c.v, strictZombiePruning, markZombie, chanIDs..., + ) + if err != nil { + return err + } + + if c.graphCache != nil { + for _, info := range infos { + c.graphCache.RemoveChannel( + info.NodeKey1Bytes, info.NodeKey2Bytes, + info.ChannelID, + ) + } + } + + return err +} + +// HasChannelEdge returns true if the database knows of a channel edge with the +// passed channel ID and this graph's gossip version, and false otherwise. If it +// is not found, then the zombie index is checked and its result is returned as +// the second boolean. +func (c *VersionedGraph) HasChannelEdge(chanID uint64) (bool, bool, error) { + return c.db.HasChannelEdge(c.v, chanID) +} + +// ForEachNodeChannel iterates through all channels of the given node. +func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context, + nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error { + + return c.db.ForEachNodeChannel(ctx, c.v, nodePub, cb, reset) +} + +// ForEachChannel iterates through all channel edges stored within the graph. +func (c *VersionedGraph) ForEachChannel(ctx context.Context, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error { + + return c.db.ForEachChannel(ctx, c.v, cb, reset) +} + +// IsPublicNode determines whether the node is seen as public in the graph. +func (c *VersionedGraph) IsPublicNode(pubKey [33]byte) (bool, error) { + return c.db.IsPublicNode(c.v, pubKey) +} + // MakeTestGraph creates a new instance of the ChannelGraph for testing -// purposes. The backing V1Store implementation depends on the version of +// purposes. The backing Store implementation depends on the version of // NewTestDB included in the current build. // // NOTE: this is currently unused, but is left here for future use to show how -// NewTestDB can be used. As the SQL implementation of the V1Store is +// NewTestDB can be used. As the SQL implementation of the Store is // implemented, unit tests will be switched to use this function instead of // the existing MakeTestGraph helper. Once only this function is used, the // existing MakeTestGraph function will be removed and this one will be renamed. diff --git a/graph/db/graph_cache.go b/graph/db/graph_cache.go index 4a3a3b0f9..7cf101fac 100644 --- a/graph/db/graph_cache.go +++ b/graph/db/graph_cache.go @@ -142,8 +142,8 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo, // Skip adding policies if both are disabled, as the channel is // currently unusable for routing. However, we still add the channel // structure above so that policy updates can later enable it. - if policy1 != nil && policy1.IsDisabled() && - policy2 != nil && policy2.IsDisabled() { + if policy1 != nil && policy1.IsDisabled && + policy2 != nil && policy2.IsDisabled { log.Debugf("Skipping policies for channel %v: both "+ "policies are disabled (channel structure still "+ @@ -156,14 +156,14 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo, // of node 2 then we have the policy 1 as seen from node 1. if policy1 != nil { fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes - if !policy1.IsNode1() { + if !policy1.IsNode1 { fromNode, toNode = toNode, fromNode } c.UpdatePolicy(policy1, fromNode, toNode) } if policy2 != nil { fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes - if policy2.IsNode1() { + if policy2.IsNode1 { fromNode, toNode = toNode, fromNode } c.UpdatePolicy(policy2, fromNode, toNode) @@ -210,7 +210,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode, switch { // This is node 1, and it is edge 1, so this is the outgoing // policy for node 1. - case channel.IsNode1 && policy.IsNode1(): + case channel.IsNode1 && policy.IsNode1: channel.OutPolicySet = true policy.InboundFee.WhenSome(func(fee lnwire.Fee) { channel.InboundFee = fee @@ -218,7 +218,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode, // This is node 2, and it is edge 2, so this is the outgoing // policy for node 2. - case !channel.IsNode1 && !policy.IsNode1(): + case !channel.IsNode1 && !policy.IsNode1: channel.OutPolicySet = true policy.InboundFee.WhenSome(func(fee lnwire.Fee) { channel.InboundFee = fee diff --git a/graph/db/graph_cache_test.go b/graph/db/graph_cache_test.go index 89e3a7e87..3d5fba85d 100644 --- a/graph/db/graph_cache_test.go +++ b/graph/db/graph_cache_test.go @@ -33,9 +33,9 @@ func TestGraphCacheAddNode(t *testing.T) { runTest := func(nodeA, nodeB route.Vertex) { t.Helper() - channelFlagA, channelFlagB := 0, 1 + isNode1A, isNode1B := true, false if nodeA == pubKey2 { - channelFlagA, channelFlagB = 1, 0 + isNode1A, isNode1B = false, true } inboundFee := lnwire.Fee{ @@ -44,8 +44,9 @@ func TestGraphCacheAddNode(t *testing.T) { } outPolicy1 := &models.CachedEdgePolicy{ - ChannelID: 1000, - ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA), + ChannelID: 1000, + IsNode1: isNode1A, + IsDisabled: false, ToNodePubKey: func() route.Vertex { return nodeB }, @@ -53,8 +54,9 @@ func TestGraphCacheAddNode(t *testing.T) { InboundFee: fn.Some(inboundFee), } inPolicy1 := &models.CachedEdgePolicy{ - ChannelID: 1000, - ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagB), + ChannelID: 1000, + IsNode1: isNode1B, + IsDisabled: false, ToNodePubKey: func() route.Vertex { return nodeA }, @@ -125,8 +127,9 @@ func assertCachedPolicyEqual(t *testing.T, original, cached *models.CachedEdgePolicy) { require.Equal(t, original.ChannelID, cached.ChannelID) - require.Equal(t, original.MessageFlags, cached.MessageFlags) - require.Equal(t, original.ChannelFlags, cached.ChannelFlags) + require.Equal(t, original.HasMaxHTLC, cached.HasMaxHTLC) + require.Equal(t, original.IsNode1, cached.IsNode1) + require.Equal(t, original.IsDisabled, cached.IsDisabled) require.Equal(t, original.TimeLockDelta, cached.TimeLockDelta) require.Equal(t, original.MinHTLC, cached.MinHTLC) require.Equal(t, original.MaxHTLC, cached.MaxHTLC) @@ -171,13 +174,14 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) { // Create two disabled policies. disabledPolicy1 := &models.CachedEdgePolicy{ - ChannelID: chanID, - ChannelFlags: lnwire.ChanUpdateDisabled, + ChannelID: chanID, + IsNode1: true, + IsDisabled: true, } disabledPolicy2 := &models.CachedEdgePolicy{ - ChannelID: chanID, - ChannelFlags: lnwire.ChanUpdateDisabled | - lnwire.ChanUpdateDirection, + ChannelID: chanID, + IsNode1: false, + IsDisabled: true, } // Add the channel with both policies disabled (simulating @@ -207,7 +211,8 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) { // Now simulate receiving a fresh update enabling one direction. enabledPolicy1 := &models.CachedEdgePolicy{ ChannelID: chanID, - ChannelFlags: 0, // NOT disabled anymore + IsNode1: true, + IsDisabled: false, TimeLockDelta: 40, MinHTLC: lnwire.MilliSatoshi(1000), } diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 3e5c93173..2edfc064a 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -69,47 +69,135 @@ var ( } ) -func createNode(priv *btcec.PrivateKey) *models.Node { +func createNode(t testing.TB, v lnwire.GossipVersion, + priv *btcec.PrivateKey) *models.Node { + pubKey := route.NewVertex(priv.PubKey()) - 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(), - }, - ) + switch v { + case lnwire.GossipVersion1: + 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(), + }, + ) + case lnwire.GossipVersion2: + return models.NewV2Node( + pubKey, &models.NodeV2Fields{ + Signature: testSig.Serialize(), + LastBlockHeight: nextBlockHeight(), + Color: fn.Some( + color.RGBA{1, 2, 3, 0}, + ), + Alias: fn.Some( + "kek" + hex.EncodeToString(pubKey[:]), + ), + Features: testFeatures. + RawFeatureVector, + Addresses: testAddrs, + }, + ) + } + + t.Fatalf("unknown gossip version: %v", v) + + return nil } -func createTestVertex(t testing.TB) *models.Node { +func createTestVertex(t testing.TB, v lnwire.GossipVersion) *models.Node { t.Helper() priv, err := btcec.NewPrivateKey() require.NoError(t, err) - return createNode(priv) + return createNode(t, v, priv) } -// TestNodeInsertionAndDeletion tests the CRUD operations for a Node. -func TestNodeInsertionAndDeletion(t *testing.T) { +type versionedTest struct { + name string + test func(t *testing.T, v lnwire.GossipVersion) +} + +var versionedTests = []versionedTest{ + { + name: "node crud", + test: testNodeInsertionAndDeletion, + }, + { + name: "source node", + test: testSourceNode, + }, + { + name: "alias lookup", + test: testAliasLookup, + }, + { + name: "add edge proof", + test: testAddEdgeProof, + }, + { + name: "edge insertion deletion", + test: testEdgeInsertionDeletion, + }, + { + name: "edge policy crud", + test: testEdgePolicyCRUD, + }, + { + name: "incomplete channel policies", + test: testIncompleteChannelPolicies, + }, + { + name: "partial node", + test: testPartialNode, + }, + { + name: "node is public", + test: testNodeIsPublic, + }, + { + name: "node is public empty channel signature", + test: testIsPublicNodeEmptyChannelSignature, + }, +} + +// TestVersionedDBs runs various tests against both v1 and v2 versioned +// backends. +func TestVersionedDBs(t *testing.T) { t.Parallel() - ctx := t.Context() - graph := MakeTestGraph(t) + // Run all v1 tests. + for _, vt := range versionedTests { + vt := vt - // We'd like to test basic insertion/deletion for vertexes from the - // graph, so we'll create a test vertex to start with. - timeStamp := int64(1232342) + t.Run(vt.name+"/v1", func(t *testing.T) { + vt.test(t, lnwire.GossipVersion1) + }) + + if !isSQLDB { + continue + } + + t.Run(vt.name+"/v2", func(t *testing.T) { + vt.test(t, lnwire.GossipVersion2) + }) + } +} + +// testNodeInsertionAndDeletion tests the CRUD operations for a Node. +func testNodeInsertionAndDeletion(t *testing.T, v lnwire.GossipVersion) { nodeWithAddrs := func(addrs []net.Addr) *models.Node { - timeStamp++ - return models.NewV1Node( testPub, &models.NodeV1Fields{ AuthSigBytes: testSig.Serialize(), - LastUpdate: time.Unix(timeStamp, 0), + LastUpdate: nextUpdateTime(), Color: color.RGBA{1, 2, 3, 0}, Alias: "kek", Features: testFeatures.RawFeatureVector, @@ -119,11 +207,36 @@ func TestNodeInsertionAndDeletion(t *testing.T) { ) } + if v == lnwire.GossipVersion2 { + nodeWithAddrs = func(addrs []net.Addr) *models.Node { + return models.NewV2Node( + testPub, &models.NodeV2Fields{ + Signature: testSig.Serialize(), + LastBlockHeight: nextBlockHeight(), + Color: fn.Some( + color.RGBA{1, 2, 3, 0}, + ), + Alias: fn.Some("kek"), + Features: testFeatures. + RawFeatureVector, + Addresses: addrs, + ExtraSignedFields: map[uint64][]byte{ + 20: {0x1, 0x2, 0x3}, + 21: {0x4, 0x5, 0x6, 0x7}, + }, + }, + ) + } + } + + ctx := t.Context() + graph := NewVersionedGraph(MakeTestGraph(t), v) + // First, insert the node into the graph DB. This should succeed // without any errors. node := nodeWithAddrs(testAddrs) require.NoError(t, graph.AddNode(ctx, node)) - assertNodeInCache(t, graph, node, testFeatures) + assertNodeInCache(t, graph.ChannelGraph, node, testFeatures) // Our AddNode implementation uses the batcher meaning that it is // possible that two updates for the same node announcement may be @@ -138,7 +251,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) { dbNode, err := graph.FetchNode(ctx, testPub) require.NoError(t, err, "unable to locate node") - _, exists, err := graph.HasNode(ctx, dbNode.PubKeyBytes) + exists, err := graph.HasNode(ctx, dbNode.PubKeyBytes) require.NoError(t, err) require.True(t, exists) @@ -153,14 +266,14 @@ func TestNodeInsertionAndDeletion(t *testing.T) { // Check that the node's features are fetched correctly. This check // will check the database directly. - features, err = graph.V1Store.FetchNodeFeatures(node.PubKeyBytes) + features, err = graph.FetchNodeFeatures(node.PubKeyBytes) require.NoError(t, err) require.Equal(t, testFeatures, features) // Next, delete the node from the graph, this should purge all data // related to the node. require.NoError(t, graph.DeleteNode(ctx, testPub)) - assertNodeNotInCache(t, graph, testPub) + assertNodeNotInCache(t, graph.ChannelGraph, testPub) // Attempting to delete the node again should return an error since // the node is no longer known. @@ -279,58 +392,58 @@ func TestNodeInsertionAndDeletion(t *testing.T) { require.NoError(t, err) } -// TestPartialNode checks that we can add and retrieve a Node where -// only the pubkey is known to the database. -func TestPartialNode(t *testing.T) { +// testPartialNode tests that partial/shell nodes are correctly created when +// a channel edge is added referencing nodes we are not yet aware of. +func testPartialNode(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // To insert a partial node, we need to add a channel edge that has - // node keys for nodes we are not yet aware + // node keys for nodes we are not yet aware of. var node1, node2 models.Node copy(node1.PubKeyBytes[:], pubKey1Bytes) copy(node2.PubKeyBytes[:], pubKey2Bytes) // Create an edge attached to these nodes and add it to the graph. - edgeInfo, _ := createEdge(140, 0, 0, 0, &node1, &node2) - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) + edgeInfo, _ := createEdge(v, 140, 0, 0, 0, &node1, &node2) + require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) // Both of the nodes should now be in both the graph (as partial/shell) // nodes _and_ the cache should also have an awareness of both nodes. - assertNodeInCache(t, graph, &node1, nil) - assertNodeInCache(t, graph, &node2, nil) + assertNodeInCache(t, graph.ChannelGraph, &node1, nil) + assertNodeInCache(t, graph.ChannelGraph, &node2, nil) - // Next, fetch the node2 from the database to ensure everything was + // Next, fetch the nodes from the database to ensure everything was // serialized properly. dbNode1, err := graph.FetchNode(ctx, pubKey1) require.NoError(t, err) dbNode2, err := graph.FetchNode(ctx, pubKey2) require.NoError(t, err) - _, exists, err := graph.HasNode(ctx, dbNode1.PubKeyBytes) + exists, err := graph.HasNode(ctx, dbNode1.PubKeyBytes) require.NoError(t, err) require.True(t, exists) // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode1 := models.NewV1ShellNode(pubKey1) + expectedNode1 := models.NewShellNode(v, pubKey1) compareNodes(t, expectedNode1, dbNode1) - _, exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) + exists, err = graph.HasNode(ctx, dbNode2.PubKeyBytes) require.NoError(t, err) require.True(t, exists) // The two nodes should match exactly! (with default values for // LastUpdate and db set to satisfy compareNodes()) - expectedNode2 := models.NewV1ShellNode(pubKey2) + expectedNode2 := models.NewShellNode(v, pubKey2) compareNodes(t, expectedNode2, dbNode2) // Next, delete the node from the graph, this should purge all data // related to the node. require.NoError(t, graph.DeleteNode(ctx, pubKey1)) - assertNodeNotInCache(t, graph, testPub) + assertNodeNotInCache(t, graph.ChannelGraph, testPub) // Finally, attempt to fetch the node again. This should fail as the // node should have been deleted from the database. @@ -338,16 +451,15 @@ func TestPartialNode(t *testing.T) { require.ErrorIs(t, err, ErrGraphNodeNotFound) } -// TestAliasLookup tests the alias lookup functionality of the graph store. -func TestAliasLookup(t *testing.T) { - t.Parallel() +// testAliasLookup tests the alias lookup functionality of the graph store. +func testAliasLookup(t *testing.T, v lnwire.GossipVersion) { ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // We'd like to test the alias index within the database, so first // create a new test node. - testNode := createTestVertex(t) + testNode := createTestVertex(t, v) // Add the node to the graph's database, this should also insert an // entry into the alias index for this node. @@ -362,23 +474,23 @@ func TestAliasLookup(t *testing.T) { require.Equal(t, testNode.Alias.UnwrapOr(""), dbAlias) // Ensure that looking up a non-existent alias results in an error. - node := createTestVertex(t) + node := createTestVertex(t, v) nodePub, err = node.PubKey() require.NoError(t, err, "unable to generate pubkey") _, err = graph.LookupAlias(ctx, nodePub) require.ErrorIs(t, err, ErrNodeAliasNotFound) } -// TestSourceNode tests the source node functionality of the graph store. -func TestSourceNode(t *testing.T) { +// testSourceNode tests the source node functionality of the graph store. +func testSourceNode(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // We'd like to test the setting/getting of the source node, so we // first create a fake node to use within the test. - testNode := createTestVertex(t) + testNode := createTestVertex(t, v) // Attempt to fetch the source node, this should return an error as the // source node hasn't yet been set. @@ -407,10 +519,10 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) // Create and set the initial source node. - testNode := createTestVertex(t) + testNode := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.SetSourceNode(ctx, testNode)) // Verify the source node was set correctly. @@ -453,68 +565,104 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { require.Equal(t, testNode.LastUpdate, updatedNode.LastUpdate) } -// TestEdgeInsertionDeletion tests the basic CRUD operations for channel edges. -func TestEdgeInsertionDeletion(t *testing.T) { +// testEdgeInsertionDeletion tests the basic CRUD operations for channel edges. +func testEdgeInsertionDeletion(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, v) + node2 := createTestVertex(t, v) - // In addition to the fake vertexes we create some fake channel - // identifiers. - chanID := uint64(prand.Int63()) + // Create a fake channel and add it to the graph. + const ( + blockHeight = 1234 + txIndex = 1 + txPosition = 0 + outPointIndex = 9 + ) + + edgeInfo, shortChanID := createEdge( + v, blockHeight, txIndex, txPosition, outPointIndex, node1, + node2, + ) + chanID := shortChanID.ToUint64() outpoint := wire.OutPoint{ Hash: rev, - Index: 9, + Index: outPointIndex, } - // Add the new edge to the database, this should proceed without any - // errors. - node1Pub, err := node1.PubKey() - require.NoError(t, err, "unable to generate node key") - node2Pub, err := node2.PubKey() - require.NoError(t, err, "unable to generate node key") - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: outpoint, - Capacity: 9000, - } - copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) - - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) + require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) + assertEdgeWithNoPoliciesInCache(t, graph.ChannelGraph, edgeInfo) // Show that trying to insert the same channel again will return the // expected error. - err = graph.AddChannelEdge(ctx, &edgeInfo) + err := graph.AddChannelEdge(ctx, edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) - // Ensure that both policies are returned as unknown (nil). - _, e1, e2, err := graph.FetchChannelEdgesByID(chanID) + // Ensure that both policies are returned as unknown (nil) and that + // the edge info round-trips correctly. + dbEdge, e1, e2, err := graph.FetchChannelEdgesByID(chanID) require.NoError(t, err) require.Nil(t, e1) require.Nil(t, e2) + // Verify core fields match. + require.Equal(t, edgeInfo.ChannelID, dbEdge.ChannelID) + require.Equal(t, edgeInfo.Version, dbEdge.Version) + require.Equal(t, edgeInfo.NodeKey1Bytes, dbEdge.NodeKey1Bytes) + require.Equal(t, edgeInfo.NodeKey2Bytes, dbEdge.NodeKey2Bytes) + require.Equal(t, edgeInfo.ChainHash, dbEdge.ChainHash) + require.Equal(t, edgeInfo.ChannelPoint, dbEdge.ChannelPoint) + require.Equal(t, edgeInfo.Capacity, dbEdge.Capacity) + + // Verify auth proof round-trips. + require.NotNil(t, dbEdge.AuthProof) + require.Equal(t, edgeInfo.AuthProof.Version, dbEdge.AuthProof.Version) + + // Verify version-specific fields. + switch v { + case lnwire.GossipVersion1: + require.Equal(t, + edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, + ) + require.Equal(t, + edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, + ) + require.Equal(t, + edgeInfo.ExtraOpaqueData, dbEdge.ExtraOpaqueData, + ) + + case lnwire.GossipVersion2: + require.Equal(t, + edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, + ) + require.Equal(t, + edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, + ) + require.Equal(t, + edgeInfo.MerkleRootHash, dbEdge.MerkleRootHash, + ) + require.Equal(t, + edgeInfo.FundingScript, dbEdge.FundingScript, + ) + require.Equal(t, + edgeInfo.ExtraSignedFields, dbEdge.ExtraSignedFields, + ) + } + + // Also verify fetching by outpoint returns the same data. + dbEdge2, _, _, err := graph.FetchChannelEdgesByOutpoint(&outpoint) + require.NoError(t, err) + require.Equal(t, dbEdge.ChannelID, dbEdge2.ChannelID) + // Next, attempt to delete the edge from the database, again this // should proceed without any issues. require.NoError(t, graph.DeleteChannelEdges(false, true, chanID)) - assertNoEdge(t, graph, chanID) + assertNoEdge(t, graph.ChannelGraph, chanID) // Ensure that any query attempts to lookup the delete channel edge are // properly deleted. @@ -538,9 +686,11 @@ func TestEdgeInsertionDeletion(t *testing.T) { require.ErrorIs(t, err, ErrEdgeNotFound) } -func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, - node1, node2 *models.Node) (models.ChannelEdgeInfo, - lnwire.ShortChannelID) { +func createEdge(version lnwire.GossipVersion, height, txIndex uint32, + txPosition uint16, outPointIndex uint32, node1, node2 *models.Node, + skipProof ...bool) (*models.ChannelEdgeInfo, lnwire.ShortChannelID) { + + shouldSkipProof := len(skipProof) > 0 && skipProof[0] shortChanID := lnwire.ShortChannelID{ BlockHeight: height, @@ -554,25 +704,95 @@ func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, node1Pub, _ := node1.PubKey() node2Pub, _ := node2.PubKey() - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: shortChanID.ToUint64(), - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - ChannelPoint: outpoint, - Capacity: 9000, - ExtraOpaqueData: make([]byte, 0), - Features: lnwire.EmptyFeatureVector(), - } - copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) + node1Vertex, _ := route.NewVertexFromBytes( + node1Pub.SerializeCompressed(), + ) + node2Vertex, _ := route.NewVertexFromBytes( + node2Pub.SerializeCompressed(), + ) + + var edgeInfo *models.ChannelEdgeInfo + switch version { + case lnwire.GossipVersion1: + btcKey1, _ := route.NewVertexFromBytes( + node1Pub.SerializeCompressed(), + ) + btcKey2, _ := route.NewVertexFromBytes( + node2Pub.SerializeCompressed(), + ) + + opts := []models.EdgeModifier{ + models.WithChannelPoint(outpoint), + models.WithCapacity(9000), + } + if !shouldSkipProof { + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + opts = append(opts, models.WithChanProof(proof)) + } + + edgeInfo, _ = models.NewV1Channel( + shortChanID.ToUint64(), + *chaincfg.MainNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + ExtraOpaqueData: make([]byte, 0), + }, + opts..., + ) + + case lnwire.GossipVersion2: + btcKey1, _ := route.NewVertexFromBytes( + node1Pub.SerializeCompressed(), + ) + btcKey2, _ := route.NewVertexFromBytes( + node2Pub.SerializeCompressed(), + ) + + // Create a test merkle root hash. + var merkleRoot chainhash.Hash + copy(merkleRoot[:], bytes.Repeat([]byte{0xaa}, 32)) + + // Create a test funding script. + fundingScript := []byte{0x00, 0x20} + fundingScript = append( + fundingScript, bytes.Repeat([]byte{0xbb}, 32)..., + ) + + opts := []models.EdgeModifier{ + models.WithChannelPoint(outpoint), + models.WithCapacity(9000), + } + if !shouldSkipProof { + proof := models.NewV2ChannelAuthProof( + testSig.Serialize(), + ) + opts = append(opts, models.WithChanProof(proof)) + } + + edgeInfo, _ = models.NewV2Channel( + shortChanID.ToUint64(), + *chaincfg.MainNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV2Fields{ + BitcoinKey1Bytes: fn.Some(btcKey1), + BitcoinKey2Bytes: fn.Some(btcKey2), + MerkleRootHash: fn.Some(merkleRoot), + FundingScript: fn.Some(fundingScript), + ExtraSignedFields: make(map[uint64][]byte), + }, + opts..., + ) + } return edgeInfo, shortChanID } @@ -585,15 +805,15 @@ func TestDisconnectBlockAtHeight(t *testing.T) { graph := MakeTestGraph(t) - sourceNode := createTestVertex(t) + sourceNode := createTestVertex(t, lnwire.GossipVersion1) if err := graph.SetSourceNode(ctx, sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t, lnwire.GossipVersion1) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -616,35 +836,40 @@ func TestDisconnectBlockAtHeight(t *testing.T) { // Create an edge which has its block height at 156. height := uint32(156) - edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2) + edgeInfo, _ := createEdge( + lnwire.GossipVersion1, height, 0, 0, 0, node1, node2, + ) // Create an edge with block height 157. We give it // maximum values for tx index and position, to make // sure our database range scan get edges from the // entire range. edgeInfo2, _ := createEdge( - height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1, - node1, node2, + lnwire.GossipVersion1, height+1, + math.MaxUint32&0x00ffffff, math.MaxUint16, 1, node1, + node2, ) // Create a third edge, this with a block height of 155. - edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) + edgeInfo3, _ := createEdge( + lnwire.GossipVersion1, height-1, 0, 0, 2, node1, node2, + ) // Now add all these new edges to the database. - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edgeInfo2); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo2); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edgeInfo3); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo3); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo2) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo2) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) // Call DisconnectBlockAtHeight, which should prune every channel // that has a funding height of 'height' or greater. @@ -654,7 +879,7 @@ func TestDisconnectBlockAtHeight(t *testing.T) { } assertNoEdge(t, graph, edgeInfo.ChannelID) assertNoEdge(t, graph, edgeInfo2.ChannelID) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) // The two edges should have been removed. if len(removed) != 2 { @@ -669,7 +894,9 @@ func TestDisconnectBlockAtHeight(t *testing.T) { } // The two first edges should be removed from the db. - _, _, has, isZombie, err := graph.HasChannelEdge(edgeInfo.ChannelID) + has, isZombie, err := graph.HasChannelEdge( + lnwire.GossipVersion1, edgeInfo.ChannelID, + ) require.NoError(t, err, "unable to query for edge") if has { t.Fatalf("edge1 was not pruned from the graph") @@ -677,7 +904,9 @@ func TestDisconnectBlockAtHeight(t *testing.T) { if isZombie { t.Fatal("reorged edge1 should not be marked as zombie") } - _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo2.ChannelID) + has, isZombie, err = graph.HasChannelEdge( + lnwire.GossipVersion1, edgeInfo2.ChannelID, + ) require.NoError(t, err, "unable to query for edge") if has { t.Fatalf("edge2 was not pruned from the graph") @@ -687,7 +916,9 @@ func TestDisconnectBlockAtHeight(t *testing.T) { } // Edge 3 should not be removed. - _, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo3.ChannelID) + has, isZombie, err = graph.HasChannelEdge( + lnwire.GossipVersion1, edgeInfo3.ChannelID, + ) require.NoError(t, err, "unable to query for edge") if !has { t.Fatalf("edge3 was pruned from the graph") @@ -723,10 +954,14 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, if !bytes.Equal(e1.NodeKey2Bytes[:], e2.NodeKey2Bytes[:]) { t.Fatalf("nodekey2 doesn't match") } - if !bytes.Equal(e1.BitcoinKey1Bytes[:], e2.BitcoinKey1Bytes[:]) { + btcKey1E1 := e1.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) + btcKey1E2 := e2.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) + if !bytes.Equal(btcKey1E1[:], btcKey1E2[:]) { t.Fatalf("bitcoinkey1 doesn't match") } - if !bytes.Equal(e1.BitcoinKey2Bytes[:], e2.BitcoinKey2Bytes[:]) { + btcKey2E1 := e1.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) + btcKey2E2 := e2.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) + if !bytes.Equal(btcKey2E1[:], btcKey2E2[:]) { t.Fatalf("bitcoinkey2 doesn't match") } @@ -736,17 +971,20 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, } require.True(t, bytes.Equal( - e1.AuthProof.NodeSig1Bytes, e2.AuthProof.NodeSig1Bytes, + e1.AuthProof.NodeSig1(), + e2.AuthProof.NodeSig1(), )) require.True(t, bytes.Equal( - e1.AuthProof.NodeSig2Bytes, e2.AuthProof.NodeSig2Bytes, + e1.AuthProof.NodeSig2(), + e2.AuthProof.NodeSig2(), )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig1Bytes, - e2.AuthProof.BitcoinSig1Bytes, + e1.AuthProof.BitcoinSig1(), + e2.AuthProof.BitcoinSig1(), )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig2Bytes, e2.AuthProof.BitcoinSig2Bytes, + e1.AuthProof.BitcoinSig2(), + e2.AuthProof.BitcoinSig2(), )) if e1.ChannelPoint != e2.ChannelPoint { @@ -765,29 +1003,9 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, } } -type createEdgeConfig struct { - skipProofs bool -} - -type createEdgeOpt func(*createEdgeConfig) - -// withSkipProofs will let createChannelEdge create an edge without auth -// proofs. In this case, createChannelEdge will then also not create policies. -func withSkipProofs() createEdgeOpt { - return func(cfg *createEdgeConfig) { - cfg.skipProofs = true - } -} - -func createChannelEdge(node1, node2 *models.Node, - options ...createEdgeOpt) (*models.ChannelEdgeInfo, +func createChannelEdge(node1, node2 *models.Node) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) { - var opts createEdgeConfig - for _, o := range options { - o(&opts) - } - var ( firstNode [33]byte secondNode [33]byte @@ -810,38 +1028,41 @@ func createChannelEdge(node1, node2 *models.Node, // Add the new edge to the database, this should proceed without any // errors. - edgeInfo := &models.ChannelEdgeInfo{ - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - ChannelPoint: outpoint, - Capacity: 1000, - ExtraOpaqueData: []byte{ - 1, 1, 1, - 2, 2, 2, 2, - 3, 3, 3, 3, 3, + var node1Key, node2Key route.Vertex + copy(node1Key[:], firstNode[:]) + copy(node2Key[:], secondNode[:]) + + extraData := []byte{ + 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, 3, + } + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edgeInfo, _ := models.NewV1Channel( + chanID, *chaincfg.MainNetParams.GenesisHash, node1Key, node2Key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + ExtraOpaqueData: extraData, }, - Features: lnwire.EmptyFeatureVector(), - } - copy(edgeInfo.NodeKey1Bytes[:], firstNode[:]) - copy(edgeInfo.NodeKey2Bytes[:], secondNode[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], firstNode[:]) - copy(edgeInfo.BitcoinKey2Bytes[:], secondNode[:]) - - if opts.skipProofs { - return edgeInfo, nil, nil - } - - edgeInfo.AuthProof = &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + models.WithChanProof(proof), + models.WithChannelPoint(outpoint), + models.WithCapacity(1000), + ) edge1 := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID, LastUpdate: nextUpdateTime(), + SecondPeer: false, MessageFlags: 1, ChannelFlags: 0, TimeLockDelta: 99, @@ -853,8 +1074,10 @@ func createChannelEdge(node1, node2 *models.Node, ExtraOpaqueData: []byte{1, 0}, } edge2 := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID, + SecondPeer: true, LastUpdate: nextUpdateTime(), MessageFlags: 1, ChannelFlags: 1, @@ -878,12 +1101,12 @@ func TestEdgeInfoUpdates(t *testing.T) { // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } assertNodeInCache(t, graph, node1, testFeatures) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -920,7 +1143,9 @@ func TestEdgeInfoUpdates(t *testing.T) { // Check for existence of the edge within the database, it should be // found. - _, _, found, isZombie, err := graph.HasChannelEdge(chanID) + found, isZombie, err := graph.HasChannelEdge( + lnwire.GossipVersion1, chanID, + ) require.NoError(t, err, "unable to query for edge") if !found { t.Fatalf("graph should have of inserted edge") @@ -965,18 +1190,28 @@ func TestEdgeInfoUpdates(t *testing.T) { assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo) } -// TestEdgePolicyCRUD tests basic CRUD operations for edge policies. -func TestEdgePolicyCRUD(t *testing.T) { +// testEdgePolicyCRUD tests basic CRUD operations for edge policies. +func testEdgePolicyCRUD(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, v) + node2 := createTestVertex(t, v) // Create an edge. Don't add it to the DB yet. - edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) + edgeInfo, shortChanID := createEdge( + v, 100, 1, 0, 0, node1, node2, + ) + chanID := shortChanID.ToUint64() + + edge1 := newEdgePolicy(v, chanID, nextUpdateTime().Unix(), true) + edge2 := newEdgePolicy(v, chanID, nextUpdateTime().Unix(), false) + edge1.ToNode = edgeInfo.NodeKey2Bytes + edge2.ToNode = edgeInfo.NodeKey1Bytes + edge1.SigBytes = testSig.Serialize() + edge2.SigBytes = testSig.Serialize() updateAndAssertPolicies := func() { // Make copies of the policies before calling UpdateEdgePolicy @@ -985,8 +1220,14 @@ func TestEdgePolicyCRUD(t *testing.T) { edge1 := copyEdgePolicy(edge1) edge2 := copyEdgePolicy(edge2) - edge1.LastUpdate = nextUpdateTime() - edge2.LastUpdate = nextUpdateTime() + switch v { + case lnwire.GossipVersion1: + edge1.LastUpdate = nextUpdateTime() + edge2.LastUpdate = nextUpdateTime() + case lnwire.GossipVersion2: + edge1.LastBlockHeight = nextBlockHeight() + edge2.LastBlockHeight = nextBlockHeight() + } require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1)) require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2)) @@ -1002,7 +1243,8 @@ func TestEdgePolicyCRUD(t *testing.T) { // assert that the deserialized policies match the original // ones. err := graph.ForEachChannel( - ctx, func(info *models.ChannelEdgeInfo, + ctx, + func(info *models.ChannelEdgeInfo, policy1 *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) error { @@ -1033,13 +1275,26 @@ func TestEdgePolicyCRUD(t *testing.T) { updateAndAssertPolicies() - // Update one of the edges to have ChannelFlags include a bit unknown - // to us. - edge1.ChannelFlags |= 1 << 6 + switch v { + case lnwire.GossipVersion1: + // Update one of the edges to have ChannelFlags include a bit + // unknown to us. + edge1.ChannelFlags |= 1 << 6 - // Update the other edge to have MessageFlags include a bit unknown to - // us. - edge2.MessageFlags |= 1 << 4 + // Update the other edge to have MessageFlags include a bit + // unknown to us. + edge2.MessageFlags |= 1 << 4 + + case lnwire.GossipVersion2: + // Update one of the edges to have DisableFlags include a bit + // unknown to us. + edge1.DisableFlags |= 1 << 6 + + // Update the other edge to have a modified extra signed field. + edge2.ExtraSignedFields = map[uint64][]byte{ + 200: {0x4, 0x5}, + } + } updateAndAssertPolicies() } @@ -1232,16 +1487,20 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, func randEdgePolicy(chanID uint64) *models.ChannelEdgePolicy { update := prand.Int63() - return newEdgePolicy(chanID, update) + return newEdgePolicy(lnwire.GossipVersion1, chanID, update, true) } func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { return &models.ChannelEdgePolicy{ + Version: p.Version, SigBytes: p.SigBytes, ChannelID: p.ChannelID, LastUpdate: p.LastUpdate, + LastBlockHeight: p.LastBlockHeight, + SecondPeer: p.SecondPeer, MessageFlags: p.MessageFlags, ChannelFlags: p.ChannelFlags, + DisableFlags: p.DisableFlags, TimeLockDelta: p.TimeLockDelta, MinHTLC: p.MinHTLC, MaxHTLC: p.MaxHTLC, @@ -1249,49 +1508,74 @@ func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { FeeProportionalMillionths: p.FeeProportionalMillionths, ToNode: p.ToNode, ExtraOpaqueData: p.ExtraOpaqueData, + ExtraSignedFields: p.ExtraSignedFields, } } -func newEdgePolicy(chanID uint64, updateTime int64) *models.ChannelEdgePolicy { - return &models.ChannelEdgePolicy{ +func newEdgePolicy(v lnwire.GossipVersion, chanID uint64, + updateTime int64, isNode1 bool) *models.ChannelEdgePolicy { + + policy := &models.ChannelEdgePolicy{ + Version: v, + SecondPeer: !isNode1, ChannelID: chanID, - LastUpdate: time.Unix(updateTime, 0), - MessageFlags: 1, - ChannelFlags: 0, TimeLockDelta: uint16(prand.Int63()), MinHTLC: lnwire.MilliSatoshi(prand.Int63()), MaxHTLC: lnwire.MilliSatoshi(prand.Int63()), FeeBaseMSat: lnwire.MilliSatoshi(prand.Int63()), FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int63()), } + + if v == lnwire.GossipVersion1 { + policy.LastUpdate = time.Unix(updateTime, 0) + policy.MessageFlags = 1 + if !isNode1 { + policy.ChannelFlags = lnwire.ChanUpdateDirection + } + policy.ExtraOpaqueData = []byte{1, 0} + } else { + policy.LastBlockHeight = nextBlockHeight() + policy.DisableFlags = 0 + policy.ExtraSignedFields = map[uint64][]byte{ + 100: {0x1, 0x2, 0x3}, + } + } + + return policy } -// TestAddEdgeProof tests the ability to add an edge proof to an existing edge. -func TestAddEdgeProof(t *testing.T) { +// testAddEdgeProof tests the ability to add an edge proof to an existing edge. +func testAddEdgeProof(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // Add an edge with no proof. - node1 := createTestVertex(t) - node2 := createTestVertex(t) - edge1, _, _ := createChannelEdge(node1, node2, withSkipProofs()) + node1 := createTestVertex(t, v) + node2 := createTestVertex(t, v) + + // Create edge without proof (skipProof = true). + edge1, _ := createEdge(v, 100, 0, 0, 0, node1, node2, true) require.NoError(t, graph.AddChannelEdge(ctx, edge1)) - // Fetch the edge and assert that the proof is nil and that the rest - // of the edge info is correct. + // Fetch the edge and assert that the proof is nil. dbEdge, _, _, err := graph.FetchChannelEdgesByID(edge1.ChannelID) require.NoError(t, err) require.Nil(t, dbEdge.AuthProof) - require.Equal(t, edge1, dbEdge) - // Now, add the edge proof. - proof := &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), + // Create a proof appropriate for the version. + var proof *models.ChannelAuthProof + switch v { + case lnwire.GossipVersion1: + proof = models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + case lnwire.GossipVersion2: + proof = models.NewV2ChannelAuthProof(testSig.Serialize()) } // First, add the proof to the rest of the channel edge info and try @@ -1299,9 +1583,9 @@ func TestAddEdgeProof(t *testing.T) { // already existing. edge1.AuthProof = proof err = graph.AddChannelEdge(ctx, edge1) - require.Error(t, err, ErrEdgeAlreadyExist) + require.ErrorIs(t, err, ErrEdgeAlreadyExist) - // Now add just the proof. + // Now add just the proof via AddEdgeProof. scid1 := lnwire.NewShortChanIDFromInt(edge1.ChannelID) require.NoError(t, graph.AddEdgeProof(scid1, proof)) @@ -1309,19 +1593,16 @@ func TestAddEdgeProof(t *testing.T) { dbEdge, _, _, err = graph.FetchChannelEdgesByID(edge1.ChannelID) require.NoError(t, err) require.NotNil(t, dbEdge.AuthProof) - require.Equal(t, edge1, dbEdge) // For completeness, also test the case where we insert a new edge with - // an edge proof. Show that the proof is present from the get go. - edge2, _, _ := createChannelEdge(node1, node2) + // an edge proof from the start. Show that the proof is present. + edge2, _ := createEdge(v, 200, 0, 0, 1, node1, node2) require.NoError(t, graph.AddChannelEdge(ctx, edge2)) - // Fetch the edge and assert that the proof is nil and that the rest - // of the edge info is correct. + // Fetch the edge and assert that the proof is set. dbEdge2, _, _, err := graph.FetchChannelEdgesByID(edge2.ChannelID) require.NoError(t, err) require.NotNil(t, dbEdge2.AuthProof) - require.Equal(t, edge2, dbEdge2) } // TestForEachSourceNodeChannel tests that the ForEachSourceNodeChannel @@ -1333,7 +1614,7 @@ func TestForEachSourceNodeChannel(t *testing.T) { graph := MakeTestGraph(t) // Create a source node (A) and set it as such in the DB. - nodeA := createTestVertex(t) + nodeA := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.SetSourceNode(ctx, nodeA)) // Now, create a few more nodes (B, C, D) along with some channels @@ -1349,9 +1630,9 @@ func TestForEachSourceNodeChannel(t *testing.T) { // outgoing policy but for the A-C channel, we will set only an incoming // policy. - nodeB := createTestVertex(t) - nodeC := createTestVertex(t) - nodeD := createTestVertex(t) + nodeB := createTestVertex(t, lnwire.GossipVersion1) + nodeC := createTestVertex(t, lnwire.GossipVersion1) + nodeD := createTestVertex(t, lnwire.GossipVersion1) abEdge, abPolicy1, abPolicy2 := createChannelEdge(nodeA, nodeB) require.NoError(t, graph.AddChannelEdge(ctx, abEdge)) @@ -1460,13 +1741,14 @@ func TestGraphTraversal(t *testing.T) { // Iterate through all the known channels within the graph DB, once // again if the map is empty that indicates that all edges have // properly been reached. - err = graph.ForEachChannel(ctx, func(ei *models.ChannelEdgeInfo, - _ *models.ChannelEdgePolicy, - _ *models.ChannelEdgePolicy) error { + err = graph.ForEachChannel(ctx, lnwire.GossipVersion1, + func(ei *models.ChannelEdgeInfo, + _ *models.ChannelEdgePolicy, + _ *models.ChannelEdgePolicy) error { - delete(chanIndex, ei.ChannelID) - return nil - }, func() {}) + delete(chanIndex, ei.ChannelID) + return nil + }, func() {}) require.NoError(t, err) require.Len(t, chanIndex, 0) @@ -1475,7 +1757,7 @@ func TestGraphTraversal(t *testing.T) { numNodeChans := 0 firstNode, secondNode := nodeList[0], nodeList[1] err = graph.ForEachNodeChannel( - ctx, firstNode.PubKeyBytes, + ctx, lnwire.GossipVersion1, firstNode.PubKeyBytes, func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { @@ -1575,7 +1857,7 @@ func TestGraphTraversalCacheable(t *testing.T) { require.NoError(t, err) // Now skip the cache and query the DB directly. - err = graph.V1Store.ForEachNodeDirectedChannel( + err = graph.db.ForEachNodeDirectedChannel( node, func(d *DirectedChannel) error { delete(chanIndex2, d.ChannelID) return nil @@ -1650,7 +1932,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, nodes := make([]*models.Node, numNodes) nodeIndex := map[string]struct{}{} for i := 0; i < numNodes; i++ { - node := createTestVertex(t) + node := createTestVertex(t, lnwire.GossipVersion1) nodes[i] = node nodeIndex[node.Alias.UnwrapOr("")] = struct{}{} @@ -1691,24 +1973,29 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, Index: 0, } - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), + var node1Key, node2Key route.Vertex + copy(node1Key[:], node1.PubKeyBytes[:]) + copy(node2Key[:], node2.PubKeyBytes[:]) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edgeInfo, err := models.NewV1Channel( + chanID, *chaincfg.MainNetParams.GenesisHash, + node1Key, node2Key, &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: op, - Capacity: 1000, - } - copy(edgeInfo.NodeKey1Bytes[:], node1.PubKeyBytes[:]) - copy(edgeInfo.NodeKey2Bytes[:], node2.PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], node1.PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey2Bytes[:], node2.PubKeyBytes[:]) - err := graph.AddChannelEdge(ctx, &edgeInfo) + models.WithChanProof(proof), + models.WithChannelPoint(op), + models.WithCapacity(1000), + ) + require.NoError(t, err) + err = graph.AddChannelEdge(ctx, edgeInfo) require.NoError(t, err) // Create and add an edge with random data that points @@ -1757,7 +2044,8 @@ func assertPruneTip(t *testing.T, graph *ChannelGraph, func assertNumChans(t *testing.T, graph *ChannelGraph, n int) { numChans := 0 err := graph.ForEachChannel( - t.Context(), func(*models.ChannelEdgeInfo, + t.Context(), lnwire.GossipVersion1, + func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error { @@ -1839,7 +2127,7 @@ func TestGraphPruning(t *testing.T) { graph := MakeTestGraph(t) - sourceNode := createTestVertex(t) + sourceNode := createTestVertex(t, lnwire.GossipVersion1) if err := graph.SetSourceNode(ctx, sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } @@ -1850,7 +2138,7 @@ func TestGraphPruning(t *testing.T) { const numNodes = 5 graphNodes := make([]*models.Node, numNodes) for i := 0; i < numNodes; i++ { - node := createTestVertex(t) + node := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node); err != nil { t.Fatalf("unable to add node: %v", err) @@ -1873,37 +2161,37 @@ func TestGraphPruning(t *testing.T) { channelPoints = append(channelPoints, &op) - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: op, - Capacity: 1000, - } - copy(edgeInfo.NodeKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) - copy(edgeInfo.NodeKey2Bytes[:], graphNodes[i+1].PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) - copy( - edgeInfo.BitcoinKey2Bytes[:], - graphNodes[i+1].PubKeyBytes[:], + var node1Key, node2Key route.Vertex + copy(node1Key[:], graphNodes[i].PubKeyBytes[:]) + copy(node2Key[:], graphNodes[i+1].PubKeyBytes[:]) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), ) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + + edgeInfo, err := models.NewV1Channel( + chanID, *chaincfg.MainNetParams.GenesisHash, + node1Key, node2Key, &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + }, + models.WithChanProof(proof), + models.WithChannelPoint(op), + models.WithCapacity(1000), + ) + if err != nil { + t.Fatalf("unable to create edge: %v", err) + } + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add node: %v", err) } - pkScript, err := genMultiSigP2WSH( - edgeInfo.BitcoinKey1Bytes[:], - edgeInfo.BitcoinKey2Bytes[:], - ) - if err != nil { - t.Fatalf("unable to gen multi-sig p2wsh: %v", err) - } + pkScript, err := edgeInfo.FundingPKScript() + require.NoError(t, err) + edgePoints = append(edgePoints, EdgePoint{ FundingPkScript: pkScript, OutPoint: op, @@ -2041,18 +2329,22 @@ func TestHighestChanID(t *testing.T) { // Next, we'll insert two channels into the database, with each channel // connecting the same two nodes. - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t, lnwire.GossipVersion1) // The first channel with be at height 10, while the other will be at // height 100. - edge1, _ := createEdge(10, 0, 0, 0, node1, node2) - edge2, chanID2 := createEdge(100, 0, 0, 0, node1, node2) + edge1, _ := createEdge( + lnwire.GossipVersion1, 10, 0, 0, 0, node1, node2, + ) + edge2, chanID2 := createEdge( + lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, + ) - if err := graph.AddChannelEdge(ctx, &edge1); err != nil { + if err := graph.AddChannelEdge(ctx, edge1); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edge2); err != nil { + if err := graph.AddChannelEdge(ctx, edge2); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2068,8 +2360,10 @@ func TestHighestChanID(t *testing.T) { // If we add another edge, then the current best chan ID should be // updated as well. - edge3, chanID3 := createEdge(1000, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edge3); err != nil { + edge3, chanID3 := createEdge( + lnwire.GossipVersion1, 1000, 0, 0, 0, node1, node2, + ) + if err := graph.AddChannelEdge(ctx, edge3); err != nil { t.Fatalf("unable to create channel edge: %v", err) } bestID, err = graph.HighestChanID(ctx) @@ -2105,11 +2399,11 @@ func TestChanUpdatesInHorizon(t *testing.T) { } // We'll start by creating two nodes which will seed our test graph. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2122,10 +2416,11 @@ func TestChanUpdatesInHorizon(t *testing.T) { edges := make([]ChannelEdge, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - uint32(i*10), 0, 0, 0, node1, node2, + lnwire.GossipVersion1, uint32(i*10), 0, 0, 0, + node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2134,7 +2429,8 @@ func TestChanUpdatesInHorizon(t *testing.T) { endTime = endTime.Add(time.Second * 10) edge1 := newEdgePolicy( - chanID.ToUint64(), edge1UpdateTime.Unix(), + lnwire.GossipVersion1, chanID.ToUint64(), + edge1UpdateTime.Unix(), true, ) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes @@ -2144,7 +2440,8 @@ func TestChanUpdatesInHorizon(t *testing.T) { } edge2 := newEdgePolicy( - chanID.ToUint64(), edge2UpdateTime.Unix(), + lnwire.GossipVersion1, chanID.ToUint64(), + edge2UpdateTime.Unix(), false, ) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes @@ -2154,7 +2451,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { } edges = append(edges, ChannelEdge{ - Info: &channel, + Info: channel, Policy1: edge1, Policy2: edge2, }) @@ -2270,7 +2567,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { const numNodes = 10 nodeAnns := make([]models.Node, 0, numNodes) for i := 0; i < numNodes; i++ { - nodeAnn := createTestVertex(t) + nodeAnn := createTestVertex(t, lnwire.GossipVersion1) // The node ann will use the current end time as its last // update them, then we'll add 10 seconds in order to create @@ -2361,7 +2658,7 @@ func testNodeUpdatesWithBatchSize(t *testing.T, ctx context.Context, var nodeAnns []models.Node for i := 0; i < 25; i++ { - nodeAnn := createTestVertex(t) + nodeAnn := createTestVertex(t, lnwire.GossipVersion1) nodeAnn.LastUpdate = startTime.Add( time.Duration(i) * time.Hour, ) @@ -2524,7 +2821,7 @@ func TestNodeUpdatesInHorizonEarlyTermination(t *testing.T) { // one hour apart. startTime := time.Unix(1234567890, 0) for i := 0; i < 100; i++ { - nodeAnn := createTestVertex(t) + nodeAnn := createTestVertex(t, lnwire.GossipVersion1) nodeAnn.LastUpdate = startTime.Add(time.Duration(i) * time.Hour) require.NoError(t, graph.AddNode(ctx, nodeAnn)) } @@ -2573,8 +2870,8 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { // Create a fresh graph for each test, then add two new // nodes to the graph. graph := MakeTestGraph(t) - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node1)) require.NoError(t, graph.AddNode(ctx, node2)) @@ -2589,14 +2886,17 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { ) channel, chanID := createEdge( - uint32(i*10), 0, 0, 0, node1, node2, + lnwire.GossipVersion1, uint32(i*10), 0, + 0, 0, node1, node2, ) require.NoError( - t, graph.AddChannelEdge(ctx, &channel), + t, graph.AddChannelEdge(ctx, channel), ) edge1 := newEdgePolicy( + lnwire.GossipVersion1, chanID.ToUint64(), updateTime.Unix(), + true, ) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes @@ -2606,7 +2906,9 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { ) edge2 := newEdgePolicy( + lnwire.GossipVersion1, chanID.ToUint64(), updateTime.Unix(), + false, ) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes @@ -2742,11 +3044,11 @@ func TestFilterKnownChanIDs(t *testing.T) { }, filteredIDs) // We'll start by creating two nodes which will seed our test graph. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2757,10 +3059,11 @@ func TestFilterKnownChanIDs(t *testing.T) { chanIDs := make([]ChannelUpdateInfo, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - uint32(i*10), 0, 0, 0, node1, node2, + lnwire.GossipVersion1, uint32(i*10), 0, 0, 0, + node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2773,9 +3076,10 @@ func TestFilterKnownChanIDs(t *testing.T) { zombieIDs := make([]ChannelUpdateInfo, 0, numZombies) for i := 0; i < numZombies; i++ { channel, chanID := createEdge( - uint32(i*10+1), 0, 0, 0, node1, node2, + lnwire.GossipVersion1, uint32(i*10+1), 0, 0, 0, + node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } err := graph.DeleteChannelEdges(false, true, channel.ChannelID) @@ -2897,10 +3201,10 @@ func TestStressTestChannelGraphAPI(t *testing.T) { graph := MakeTestGraph(t) - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node2)) // We need to update the node's timestamp since this call to @@ -2931,12 +3235,13 @@ func TestStressTestChannelGraphAPI(t *testing.T) { defer mu.Unlock() channel, chanID := createEdge( - newBlockHeight(), rand.Uint32(), uint16(rand.Int()), - rand.Uint32(), node1, node2, + lnwire.GossipVersion1, newBlockHeight(), + rand.Uint32(), uint16(rand.Int()), rand.Uint32(), + node1, node2, ) newChan := &chanInfo{ - info: channel, + info: *channel, id: chanID, } chans = append(chans, newChan) @@ -3060,7 +3365,8 @@ func TestStressTestChannelGraphAPI(t *testing.T) { return nil } - _, _, _, _, err := graph.HasChannelEdge( + _, _, err := graph.HasChannelEdge( + lnwire.GossipVersion1, channel.id.ToUint64(), ) @@ -3189,10 +3495,10 @@ func TestFilterChannelRange(t *testing.T) { // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node2)) // If we try to filter a channel range before we have any channels @@ -3230,6 +3536,7 @@ func TestFilterChannelRange(t *testing.T) { updateTime = time.Unix(updateTimeSeed, 0) err = graph.UpdateEdgePolicy( ctx, &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ToNode: node.PubKeyBytes, ChannelFlags: chanFlags, ChannelID: chanID, @@ -3246,14 +3553,16 @@ func TestFilterChannelRange(t *testing.T) { for i := 0; i < numChans/2; i++ { chanHeight := endHeight channel1, chanID1 := createEdge( - chanHeight, uint32(i+1), 0, 0, node1, node2, + lnwire.GossipVersion1, chanHeight, uint32(i+1), 0, + 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, &channel1)) + require.NoError(t, graph.AddChannelEdge(ctx, channel1)) channel2, chanID2 := createEdge( - chanHeight, uint32(i+2), 0, 0, node1, node2, + lnwire.GossipVersion1, chanHeight, uint32(i+2), 0, + 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, &channel2)) + require.NoError(t, graph.AddChannelEdge(ctx, channel2)) chanInfo1 := NewChannelUpdateInfo( chanID1, time.Time{}, time.Time{}, @@ -3408,11 +3717,11 @@ func TestFetchChanInfos(t *testing.T) { // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3426,26 +3735,31 @@ func TestFetchChanInfos(t *testing.T) { edgeQuery := make([]uint64, 0, numChans) for i := 0; i < numChans; i++ { channel, chanID := createEdge( - uint32(i*10), 0, 0, 0, node1, node2, + lnwire.GossipVersion1, uint32(i*10), 0, 0, 0, + node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } updateTime := endTime endTime = updateTime.Add(time.Second * 10) - edge1 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) - edge1.ChannelFlags = 0 + edge1 := newEdgePolicy( + lnwire.GossipVersion1, chanID.ToUint64(), + updateTime.Unix(), true, + ) edge1.ToNode = node2.PubKeyBytes edge1.SigBytes = testSig.Serialize() if err := graph.UpdateEdgePolicy(ctx, edge1); err != nil { t.Fatalf("unable to update edge: %v", err) } - edge2 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) - edge2.ChannelFlags = 1 + edge2 := newEdgePolicy( + lnwire.GossipVersion1, chanID.ToUint64(), + updateTime.Unix(), false, + ) edge2.ToNode = node1.PubKeyBytes edge2.SigBytes = testSig.Serialize() if err := graph.UpdateEdgePolicy(ctx, edge2); err != nil { @@ -3453,7 +3767,7 @@ func TestFetchChanInfos(t *testing.T) { } edges = append(edges, ChannelEdge{ - Info: &channel, + Info: channel, Policy1: edge1, Policy2: edge2, }) @@ -3468,9 +3782,9 @@ func TestFetchChanInfos(t *testing.T) { // Add an another edge to the query that has been marked as a zombie // edge. The query should also skip this channel. zombieChan, zombieChanID := createEdge( - 666, 0, 0, 0, node1, node2, + lnwire.GossipVersion1, 666, 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &zombieChan); err != nil { + if err := graph.AddChannelEdge(ctx, zombieChan); err != nil { t.Fatalf("unable to create channel edge: %v", err) } err := graph.DeleteChannelEdges(false, true, zombieChan.ChannelID) @@ -3500,32 +3814,26 @@ func TestFetchChanInfos(t *testing.T) { } } -// TestIncompleteChannelPolicies tests that a channel that only has a policy +// testIncompleteChannelPolicies tests that a channel that only has a policy // specified on one end is properly returned in ForEachChannel calls from // both sides. -func TestIncompleteChannelPolicies(t *testing.T) { +func testIncompleteChannelPolicies(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // Create two nodes. - node1 := createTestVertex(t) - if err := graph.AddNode(ctx, node1); err != nil { - t.Fatalf("unable to add node: %v", err) - } - node2 := createTestVertex(t) - if err := graph.AddNode(ctx, node2); err != nil { - t.Fatalf("unable to add node: %v", err) - } + node1 := createTestVertex(t, v) + require.NoError(t, graph.AddNode(ctx, node1)) + node2 := createTestVertex(t, v) + require.NoError(t, graph.AddNode(ctx, node2)) channel, chanID := createEdge( - uint32(0), 0, 0, 0, node1, node2, + v, uint32(0), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { - t.Fatalf("unable to create channel edge: %v", err) - } + require.NoError(t, graph.AddChannelEdge(ctx, channel)) // Ensure that channel is reported with unknown policies. checkPolicies := func(node *models.Node, expectedIn, @@ -3537,21 +3845,8 @@ func TestIncompleteChannelPolicies(t *testing.T) { func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { - if !expectedOut && outEdge != nil { - t.Fatalf("Expected no outgoing policy") - } - - if expectedOut && outEdge == nil { - t.Fatalf("Expected an outgoing policy") - } - - if !expectedIn && inEdge != nil { - t.Fatalf("Expected no incoming policy") - } - - if expectedIn && inEdge == nil { - t.Fatalf("Expected an incoming policy") - } + require.Equal(t, expectedOut, outEdge != nil) + require.Equal(t, expectedIn, inEdge != nil) calls++ @@ -3564,30 +3859,30 @@ func TestIncompleteChannelPolicies(t *testing.T) { checkPolicies(node2, false, false) + newTestEdgePolicy := func(isNode1 bool, + toNode route.Vertex) *models.ChannelEdgePolicy { + + policy := newEdgePolicy( + v, chanID.ToUint64(), nextUpdateTime().Unix(), isNode1, + ) + policy.ToNode = toNode + policy.SigBytes = testSig.Serialize() + + return policy + } + // Only create an edge policy for node1 and leave the policy for node2 // unknown. - updateTime := time.Unix(1234, 0) - - edgePolicy := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) - edgePolicy.ChannelFlags = 0 - edgePolicy.ToNode = node2.PubKeyBytes - edgePolicy.SigBytes = testSig.Serialize() - if err := graph.UpdateEdgePolicy(ctx, edgePolicy); err != nil { - t.Fatalf("unable to update edge: %v", err) - } + edgePolicy := newTestEdgePolicy(true, node2.PubKeyBytes) + require.NoError(t, graph.UpdateEdgePolicy(ctx, edgePolicy)) checkPolicies(node1, false, true) checkPolicies(node2, true, false) // Create second policy and assert that both policies are reported // as present. - edgePolicy = newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) - edgePolicy.ChannelFlags = 1 - edgePolicy.ToNode = node1.PubKeyBytes - edgePolicy.SigBytes = testSig.Serialize() - if err := graph.UpdateEdgePolicy(ctx, edgePolicy); err != nil { - t.Fatalf("unable to update edge: %v", err) - } + edgePolicy = newTestEdgePolicy(false, node1.PubKeyBytes) + require.NoError(t, graph.UpdateEdgePolicy(ctx, edgePolicy)) checkPolicies(node1, true, true) checkPolicies(node2, true, true) @@ -3603,31 +3898,33 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { graph := MakeTestGraph(t) // The update index only applies to the bbolt graph. - boltStore, ok := graph.V1Store.(*KVStore) + boltStore, ok := graph.db.(*KVStore) if !ok { t.Skipf("skipping test that is aimed at a bbolt graph DB") } - sourceNode := createTestVertex(t) + sourceNode := createTestVertex(t, lnwire.GossipVersion1) if err := graph.SetSourceNode(ctx, sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } // With the two nodes created, we'll now create a random channel, as // well as two edges in the database with distinct update times. - edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + edgeInfo, chanID := createEdge( + lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, + ) + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -3748,11 +4045,11 @@ func TestPruneGraphNodes(t *testing.T) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) // We'll start off by inserting our source node, to ensure that it's // the only node left after we prune the graph. - sourceNode := createTestVertex(t) + sourceNode := createTestVertex(t, lnwire.GossipVersion1) if err := graph.SetSourceNode(ctx, sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } @@ -3760,23 +4057,25 @@ func TestPruneGraphNodes(t *testing.T) { // With the source node inserted, we'll now add three nodes to the // channel graph, at the end of the scenario, only two of these nodes // should still be in the graph. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } - node3 := createTestVertex(t) + node3 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node3); err != nil { t.Fatalf("unable to add node: %v", err) } // We'll now add a new edge to the graph, but only actually advertise // the edge of *one* of the nodes. - edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + edgeInfo, chanID := createEdge( + lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, + ) + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -3799,7 +4098,7 @@ func TestPruneGraphNodes(t *testing.T) { // source node (which can't be pruned), and node 1+2. Nodes 1 and two // should still be left in the graph as there's half of an advertised // edge between them. - assertNumNodes(t, graph, 3) + assertNumNodes(t, graph.ChannelGraph, 3) // Finally, we'll ensure that node3, the only fully unconnected node as // properly deleted from the graph and not another node in its place. @@ -3814,18 +4113,20 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) // To start, we'll create two nodes, and only add one of them to the // channel graph. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.SetSourceNode(ctx, node1)) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) // We'll now create an edge between the two nodes, as a result, node2 // should be inserted into the database as a shell node. - edgeInfo, _ := createEdge(100, 0, 0, 0, node1, node2) - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) + edgeInfo, _ := createEdge( + lnwire.GossipVersion1, 100, 0, 0, 0, node1, node2, + ) + require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) // Ensure that node1 was inserted as a full node, while node2 only has // a shell node present. @@ -3839,7 +4140,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { // Show that attempting to add the channel again will result in an // error. - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) // Show that updating the shell node to a full node record works. @@ -3853,11 +4154,11 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) // We'll first populate our graph with a single node that will be // removed shortly. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3899,6 +4200,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) { var ( updateTime = prand.Int63() updateTimeMu sync.Mutex + updateBlock = prand.Uint32() ) func nextUpdateTime() time.Time { @@ -3910,9 +4212,18 @@ func nextUpdateTime() time.Time { return time.Unix(updateTime, 0) } -// TestNodeIsPublic ensures that we properly detect nodes that are seen as +func nextBlockHeight() uint32 { + updateTimeMu.Lock() + defer updateTimeMu.Unlock() + + updateBlock++ + + return updateBlock +} + +// testNodeIsPublic ensures that we properly detect nodes that are seen as // public within the network graph. -func TestNodeIsPublic(t *testing.T) { +func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() @@ -3924,32 +4235,29 @@ func TestNodeIsPublic(t *testing.T) { // We'll need to create a separate database and channel graph for each // participant to replicate real-world scenarios (private edges being in // some graphs but not others, etc.). - aliceGraph := MakeTestGraph(t) - aliceNode := createTestVertex(t) - if err := aliceGraph.SetSourceNode(ctx, aliceNode); err != nil { - t.Fatalf("unable to set source node: %v", err) - } + aliceGraph := NewVersionedGraph(MakeTestGraph(t), v) + aliceNode := createTestVertex(t, v) + err := aliceGraph.SetSourceNode(ctx, aliceNode) + require.NoError(t, err, "unable to set source node") - bobGraph := MakeTestGraph(t) - bobNode := createTestVertex(t) - if err := bobGraph.SetSourceNode(ctx, bobNode); err != nil { - t.Fatalf("unable to set source node: %v", err) - } + bobGraph := NewVersionedGraph(MakeTestGraph(t), v) + bobNode := createTestVertex(t, v) + err = bobGraph.SetSourceNode(ctx, bobNode) + require.NoError(t, err, "unable to set source node") - carolGraph := MakeTestGraph(t) - carolNode := createTestVertex(t) - if err := carolGraph.SetSourceNode(ctx, carolNode); err != nil { - t.Fatalf("unable to set source node: %v", err) - } + carolGraph := NewVersionedGraph(MakeTestGraph(t), v) + carolNode := createTestVertex(t, v) + err = carolGraph.SetSourceNode(ctx, carolNode) + require.NoError(t, err, "unable to set source node") - aliceBobEdge, _ := createEdge(10, 0, 0, 0, aliceNode, bobNode) - bobCarolEdge, _ := createEdge(10, 1, 0, 1, bobNode, carolNode) + aliceBobEdge, _ := createEdge(v, 10, 0, 0, 0, aliceNode, bobNode) + bobCarolEdge, _ := createEdge(v, 10, 1, 0, 1, bobNode, carolNode) // After creating all of our nodes and edges, we'll add them to each // participant's graph. nodes := []*models.Node{aliceNode, bobNode, carolNode} - edges := []*models.ChannelEdgeInfo{&aliceBobEdge, &bobCarolEdge} - graphs := []*ChannelGraph{aliceGraph, bobGraph, carolGraph} + edges := []*models.ChannelEdgeInfo{aliceBobEdge, bobCarolEdge} + graphs := []*VersionedGraph{aliceGraph, bobGraph, carolGraph} for _, graph := range graphs { for _, node := range nodes { node.LastUpdate = nextUpdateTime() @@ -3965,7 +4273,7 @@ func TestNodeIsPublic(t *testing.T) { // checkNodes is a helper closure that will be used to assert that the // given nodes are seen as public/private within the given graphs. checkNodes := func(nodes []*models.Node, - graphs []*ChannelGraph, public bool) { + graphs []*VersionedGraph, public bool) { t.Helper() @@ -3974,19 +4282,9 @@ func TestNodeIsPublic(t *testing.T) { isPublic, err := graph.IsPublicNode( node.PubKeyBytes, ) - if err != nil { - t.Fatalf("unable to determine if "+ - "pivot is public: %v", err) - } + require.NoError(t, err) - switch { - case isPublic && !public: - t.Fatalf("expected %x to be private", - node.PubKeyBytes) - case !isPublic && public: - t.Fatalf("expected %x to be public", - node.PubKeyBytes) - } + require.Equal(t, public, isPublic) } } } @@ -4002,13 +4300,11 @@ func TestNodeIsPublic(t *testing.T) { err := graph.DeleteChannelEdges( false, true, aliceBobEdge.ChannelID, ) - if err != nil { - t.Fatalf("unable to remove edge: %v", err) - } + require.NoError(t, err, "unable to remove edge") } checkNodes( []*models.Node{aliceNode}, - []*ChannelGraph{bobGraph, carolGraph}, + []*VersionedGraph{bobGraph, carolGraph}, false, ) @@ -4021,29 +4317,79 @@ func TestNodeIsPublic(t *testing.T) { err := graph.DeleteChannelEdges( false, true, bobCarolEdge.ChannelID, ) - if err != nil { - t.Fatalf("unable to remove edge: %v", err) - } + require.NoError(t, err, "unable to remove edge") if graph == aliceGraph { continue } bobCarolEdge.AuthProof = nil - if err := graph.AddChannelEdge(ctx, &bobCarolEdge); err != nil { - t.Fatalf("unable to add edge: %v", err) - } + err = graph.AddChannelEdge(ctx, bobCarolEdge) + require.NoError(t, err, "unable to add edge") } // With the modifications above, Bob should now be seen as a private // node from both Alice's and Carol's perspective. checkNodes( []*models.Node{bobNode}, - []*ChannelGraph{aliceGraph, carolGraph}, + []*VersionedGraph{aliceGraph, carolGraph}, false, ) } +// testIsPublicNodeEmptyChannelSignature ensures empty channel signatures don't +// mark nodes as public. +func testIsPublicNodeEmptyChannelSignature(t *testing.T, + v lnwire.GossipVersion) { + + t.Parallel() + ctx := t.Context() + + testGraph := MakeTestGraph(t) + graph := NewVersionedGraph(testGraph, v) + + // Set a source node as it's required for IsPublicNode. + sourceNode := createTestVertex(t, v) + err := graph.SetSourceNode(ctx, sourceNode) + require.NoError(t, err) + + node1 := createTestVertex(t, v) + + node1.LastUpdate = nextUpdateTime() + + err = graph.AddNode(ctx, node1) + require.NoError(t, err) + + // Create an edge between source node and node1, with + // empty signatures. This tests that empty signatures + // don't mark nodes as public. + edgeInfo, _ := createEdge( + v, 10, 0, 0, 0, sourceNode, node1, + true, + ) + + switch v { + case lnwire.GossipVersion1: + edgeInfo.AuthProof = + models.NewV1ChannelAuthProof( + []byte{}, []byte{}, + []byte{}, []byte{}, + ) + case lnwire.GossipVersion2: + edgeInfo.AuthProof = + models.NewV2ChannelAuthProof([]byte{}) + } + + err = graph.AddChannelEdge(ctx, edgeInfo) + require.NoError(t, err) + + // node1 should NOT be considered public because the + // channel announcement has empty signatures. + isPublic, err := graph.IsPublicNode(node1.PubKeyBytes) + require.NoError(t, err) + require.False(t, isPublic) +} + // BenchmarkIsPublicNode measures the performance of IsPublicNode when checking // a large number of nodes. func BenchmarkIsPublicNode(b *testing.B) { @@ -4076,13 +4422,13 @@ func TestDisabledChannelIDs(t *testing.T) { graph := MakeTestGraph(t) // Create first node and add it to the graph. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } // Create second node and add it to the graph. - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -4161,18 +4507,18 @@ func TestEdgePolicyMissingMaxHTLC(t *testing.T) { graph := MakeTestGraph(t) // This test currently directly edits the bytes stored in the bbolt DB. - boltStore, ok := graph.V1Store.(*KVStore) + boltStore, ok := graph.db.(*KVStore) if !ok { t.Skipf("skipping test that is aimed at a bbolt graph DB") } // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) if err := graph.AddNode(ctx, node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) if err := graph.AddNode(ctx, node2); err != nil { @@ -4304,8 +4650,8 @@ func TestGraphZombieIndex(t *testing.T) { // We'll start by creating our test graph along with a test edge. graph := MakeTestGraph(t) - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t, lnwire.GossipVersion1) // Swap the nodes if the second's pubkey is smaller than the first. // Without this, the comparisons at the end will fail probabilistically. @@ -4378,53 +4724,47 @@ func compareNodes(t *testing.T, a, b *models.Node) { require.Equal(t, a, b) } -// compareEdgePolicies is used to compare two ChannelEdgePolices using -// compareNodes, so as to exclude comparisons of the Nodes' Features struct. +// compareEdgePolicies compares two ChannelEdgePolicy values for semantic +// equality after normalizing version-specific/backend-specific differences. func compareEdgePolicies(a, b *models.ChannelEdgePolicy) error { - if a.ChannelID != b.ChannelID { - return fmt.Errorf("ChannelID doesn't match: expected %v, "+ - "got %v", a.ChannelID, b.ChannelID) + //nolint:ll + normalize := func(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { + if p == nil { + return nil + } + + policy := copyEdgePolicy(p) + if len(policy.ExtraOpaqueData) == 0 { + policy.ExtraOpaqueData = nil + } + if len(policy.ExtraSignedFields) == 0 { + policy.ExtraSignedFields = nil + } + + switch policy.Version { + case lnwire.GossipVersion1: + // SecondPeer is v2-specific; derive canonical direction + // for v1. + policy.SecondPeer = !policy.IsNode1() + policy.LastBlockHeight = 0 + policy.DisableFlags = 0 + policy.ExtraSignedFields = nil + + case lnwire.GossipVersion2: + policy.LastUpdate = time.Time{} + policy.MessageFlags = 0 + policy.ChannelFlags = 0 + policy.ExtraOpaqueData = nil + } + + return policy } - if !reflect.DeepEqual(a.LastUpdate, b.LastUpdate) { - return fmt.Errorf("edge LastUpdate doesn't match: "+ - "expected %#v, got %#v", a.LastUpdate, b.LastUpdate) - } - if a.MessageFlags != b.MessageFlags { - return fmt.Errorf("MessageFlags doesn't match: expected %v, "+ - "got %v", a.MessageFlags, b.MessageFlags) - } - if a.ChannelFlags != b.ChannelFlags { - return fmt.Errorf("ChannelFlags doesn't match: expected %v, "+ - "got %v", a.ChannelFlags, b.ChannelFlags) - } - if a.TimeLockDelta != b.TimeLockDelta { - return fmt.Errorf("TimeLockDelta doesn't match: expected %v, "+ - "got %v", a.TimeLockDelta, b.TimeLockDelta) - } - if a.MinHTLC != b.MinHTLC { - return fmt.Errorf("MinHTLC doesn't match: expected %v, "+ - "got %v", a.MinHTLC, b.MinHTLC) - } - if a.MaxHTLC != b.MaxHTLC { - return fmt.Errorf("MaxHTLC doesn't match: expected %v, "+ - "got %v", a.MaxHTLC, b.MaxHTLC) - } - if a.FeeBaseMSat != b.FeeBaseMSat { - return fmt.Errorf("FeeBaseMSat doesn't match: expected %v, "+ - "got %v", a.FeeBaseMSat, b.FeeBaseMSat) - } - if a.FeeProportionalMillionths != b.FeeProportionalMillionths { - return fmt.Errorf("FeeProportionalMillionths doesn't match: "+ - "expected %v, got %v", a.FeeProportionalMillionths, - b.FeeProportionalMillionths) - } - if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) { - return fmt.Errorf("extra data doesn't match: %v vs %v", - a.ExtraOpaqueData, b.ExtraOpaqueData) - } - if !bytes.Equal(a.ToNode[:], b.ToNode[:]) { - return fmt.Errorf("ToNode doesn't match: expected %x, got %x", - a.ToNode, b.ToNode) + + normalizedA := normalize(a) + normalizedB := normalize(b) + if !reflect.DeepEqual(normalizedA, normalizedB) { + return fmt.Errorf("expected %v, got %v", normalizedA, + normalizedB) } return nil @@ -4453,7 +4793,7 @@ func TestLightningNodeSigVerification(t *testing.T) { } // Create a Node from the same private key. - node := createNode(priv) + node := createNode(t, lnwire.GossipVersion1, priv) // And finally check that we can verify the same signature from the // pubkey returned from the lightning node. @@ -4469,6 +4809,7 @@ func TestLightningNodeSigVerification(t *testing.T) { func TestComputeFee(t *testing.T) { var ( policy = models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, FeeBaseMSat: 10000, FeeProportionalMillionths: 30000, } @@ -4490,13 +4831,13 @@ func TestBatchedAddChannelEdge(t *testing.T) { graph := MakeTestGraph(t) - sourceNode := createTestVertex(t) + sourceNode := createTestVertex(t, lnwire.GossipVersion1) require.Nil(t, graph.SetSourceNode(ctx, sourceNode)) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t) - node2 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) + node2 := createTestVertex(t, lnwire.GossipVersion1) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -4519,21 +4860,26 @@ func TestBatchedAddChannelEdge(t *testing.T) { // Create an edge which has its block height at 156. height := uint32(156) - edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2) + edgeInfo, _ := createEdge( + lnwire.GossipVersion1, height, 0, 0, 0, node1, node2, + ) // Create an edge with block height 157. We give it // maximum values for tx index and position, to make // sure our database range scan get edges from the // entire range. edgeInfo2, _ := createEdge( - height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1, - node1, node2, + lnwire.GossipVersion1, height+1, + math.MaxUint32&0x00ffffff, math.MaxUint16, 1, node1, + node2, ) // Create a third edge, this with a block height of 155. - edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) + edgeInfo3, _ := createEdge( + lnwire.GossipVersion1, height-1, 0, 0, 2, node1, node2, + ) - edges := []models.ChannelEdgeInfo{edgeInfo, edgeInfo2, edgeInfo3} + edges := []models.ChannelEdgeInfo{*edgeInfo, *edgeInfo2, *edgeInfo3} errChan := make(chan error, len(edges)) errTimeout := errors.New("timeout adding batched channel") @@ -4569,9 +4915,9 @@ func TestBatchedUpdateEdgePolicy(t *testing.T) { // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node2)) // Create an edge and add it to the db. @@ -4659,7 +5005,9 @@ func BenchmarkForEachChannel(b *testing.B) { return nil } - err := graph.ForEachNodeChannel(ctx, n, cb, func() {}) + err := graph.ForEachNodeChannel( + ctx, lnwire.GossipVersion1, n, cb, func() {}, + ) require.NoError(b, err) } } @@ -4677,9 +5025,9 @@ func TestGraphCacheForEachNodeChannel(t *testing.T) { // option turned off. graph.graphCache = nil - node1 := createTestVertex(t) + node1 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node1)) - node2 := createTestVertex(t) + node2 := createTestVertex(t, lnwire.GossipVersion1) require.NoError(t, graph.AddNode(ctx, node2)) // Create an edge and add it to the db. @@ -4834,7 +5182,7 @@ func TestLightningNodePersistence(t *testing.T) { ctx := t.Context() // Create a new test graph instance. - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), lnwire.GossipVersion1) nodeAnnBytes, err := hex.DecodeString(testNodeAnn) require.NoError(t, err) diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go index 2d4da91f5..0936386be 100644 --- a/graph/db/interfaces.go +++ b/graph/db/interfaces.go @@ -28,10 +28,17 @@ type NodeTraverser interface { FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error) } -// V1Store represents the main interface for the channel graph database for all +// Store represents the main interface for the channel graph database for all // channels and nodes gossiped via the V1 gossip protocol as defined in BOLT 7. -type V1Store interface { //nolint:interfacebloat - NodeTraverser +type Store interface { //nolint:interfacebloat + // ForEachNodeDirectedChannel calls the callback for every channel of + // the given node. + ForEachNodeDirectedChannel(nodePub route.Vertex, + cb func(channel *DirectedChannel) error, reset func()) error + + // FetchNodeFeatures returns the features of the given node. + FetchNodeFeatures(v lnwire.GossipVersion, + nodePub route.Vertex) (*lnwire.FeatureVector, error) // AddNode adds a vertex/node to the graph database. If the // node is not in the database from before, this will add a new, @@ -45,7 +52,7 @@ type V1Store interface { //nolint:interfacebloat // AddrsForNode returns all known addresses for the target node public // key that the graph DB is aware of. The returned boolean indicates if // the given node is unknown to the graph DB or not. - AddrsForNode(ctx context.Context, + AddrsForNode(ctx context.Context, v lnwire.GossipVersion, nodePub *btcec.PublicKey) (bool, []net.Addr, error) // ForEachSourceNodeChannel iterates through all channels of the source @@ -66,8 +73,9 @@ type V1Store interface { //nolint:interfacebloat // to the caller. // // Unknown policies are passed into the callback as nil values. - ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + ForEachNodeChannel(ctx context.Context, v lnwire.GossipVersion, + nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error // ForEachNodeCached is similar to forEachNode, but it returns @@ -100,11 +108,13 @@ type V1Store interface { //nolint:interfacebloat // LookupAlias attempts to return the alias as advertised by the target // node. - LookupAlias(ctx context.Context, pub *btcec.PublicKey) (string, error) + LookupAlias(ctx context.Context, v lnwire.GossipVersion, + pub *btcec.PublicKey) (string, error) // DeleteNode starts a new database transaction to remove a // vertex/node from the database according to the node's public key. - DeleteNode(ctx context.Context, nodePub route.Vertex) error + DeleteNode(ctx context.Context, v lnwire.GossipVersion, + nodePub route.Vertex) error // NodeUpdatesInHorizon returns all the known lightning node which have // an update timestamp within the passed range. This method can be used @@ -116,20 +126,28 @@ type V1Store interface { //nolint:interfacebloat // FetchNode attempts to look up a target node by its identity // public key. If the node isn't found in the database, then // ErrGraphNodeNotFound is returned. - FetchNode(ctx context.Context, nodePub route.Vertex) (*models.Node, + FetchNode(ctx context.Context, v lnwire.GossipVersion, + nodePub route.Vertex) (*models.Node, error) + + // HasV1Node determines if the graph has a vertex identified by + // the target node identity public key in the V1 graph. If the node + // exists in the database, a timestamp of when the data for the node + // was lasted updated is returned along with a true boolean. Otherwise, + // an empty time.Time is returned with a false boolean. + // This is specific to the V1 graph since only V1 node announcements + // use timestamps for their latest update timestamp. + HasV1Node(ctx context.Context, nodePub [33]byte) (time.Time, bool, error) // HasNode determines if the graph has a vertex identified by - // the target node identity public key. If the node exists in the - // database, a timestamp of when the data for the node was lasted - // updated is returned along with a true boolean. Otherwise, an empty - // time.Time is returned with a false boolean. - HasNode(ctx context.Context, nodePub [33]byte) (time.Time, bool, error) + // the target node identity public key. + HasNode(ctx context.Context, v lnwire.GossipVersion, + nodePub [33]byte) (bool, error) // IsPublicNode is a helper method that determines whether the node with // the given public key is seen as a public node in the graph from the // graph's source node's point of view. - IsPublicNode(pubKey [33]byte) (bool, error) + IsPublicNode(v lnwire.GossipVersion, pubKey [33]byte) (bool, error) // GraphSession will provide the call-back with access to a // NodeTraverser instance which can be used to perform queries against @@ -145,9 +163,12 @@ type V1Store interface { //nolint:interfacebloat // NOTE: If an edge can't be found, or wasn't advertised, then a nil // pointer for that particular channel edge routing policy will be // passed into the callback. - ForEachChannel(ctx context.Context, cb func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, - reset func()) error + // + // TODO(elle): add a cross-version iteration API and make this iterate + // over all versions. + ForEachChannel(ctx context.Context, v lnwire.GossipVersion, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error, reset func()) error // ForEachChannelCacheable iterates through all the channel edges stored // within the graph and invokes the passed callback for each edge. The @@ -180,13 +201,20 @@ type V1Store interface { //nolint:interfacebloat AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error - // HasChannelEdge returns true if the database knows of a channel edge + // HasV1ChannelEdge returns true if the database knows of a channel edge // with the passed channel ID, and false otherwise. If an edge with that // ID is found within the graph, then two time stamps representing the // last time the edge was updated for both directed edges are returned // along with the boolean. If it is not found, then the zombie index is // checked and its result is returned as the second boolean. - HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, + HasV1ChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, + error) + + // HasChannelEdge returns true if the database knows of a channel edge + // with the passed channel ID and gossip version, and false otherwise. + // If it is not found, then the zombie index is checked and its result + // is returned as the second boolean. + HasChannelEdge(v lnwire.GossipVersion, chanID uint64) (bool, bool, error) // DeleteChannelEdges removes edges with the given channel IDs from the @@ -198,8 +226,9 @@ type V1Store interface { //nolint:interfacebloat // failed to send the fresh update to be the one that resurrects the // channel from its zombie state. The markZombie bool denotes whether // to mark the channel as a zombie. - DeleteChannelEdges(strictZombiePruning, markZombie bool, - chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) + DeleteChannelEdges(v lnwire.GossipVersion, strictZombiePruning, + markZombie bool, chanIDs ...uint64) ( + []*models.ChannelEdgeInfo, error) // AddEdgeProof sets the proof of an existing edge in the graph // database. @@ -258,7 +287,7 @@ type V1Store interface { //nolint:interfacebloat // houses the general information for the channel itself is returned as // well as two structs that contain the routing policies for the channel // in either direction. - FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( + FetchChannelEdgesByOutpoint(v lnwire.GossipVersion, op *wire.OutPoint) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) @@ -273,7 +302,7 @@ type V1Store interface { //nolint:interfacebloat // zombie within the database. In this case, the ChannelEdgePolicy's // will be nil, and the ChannelEdgeInfo will only include the public // keys of each node. - FetchChannelEdgesByID(chanID uint64) ( + FetchChannelEdgesByID(v lnwire.GossipVersion, chanID uint64) ( *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) @@ -296,7 +325,8 @@ type V1Store interface { //nolint:interfacebloat // IsZombieEdge returns whether the edge is considered zombie. If it is // a zombie, then the two node public keys corresponding to this edge // are also returned. - IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, error) + IsZombieEdge(v lnwire.GossipVersion, chanID uint64) (bool, [33]byte, + [33]byte, error) // NumZombies returns the current number of zombie channels in the // graph. @@ -327,7 +357,8 @@ type V1Store interface { //nolint:interfacebloat // treated as the center node within a star-graph. This method may be // used to kick off a path finding algorithm in order to explore the // reachability of another node based off the source node. - SourceNode(ctx context.Context) (*models.Node, error) + SourceNode(ctx context.Context, v lnwire.GossipVersion) (*models.Node, + error) // SetSourceNode sets the source node within the graph database. The // source node is to be used as the center of a star-graph within path diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 34b21c9ab..22c87f0ee 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -164,6 +164,11 @@ var ( // // maps: scid -> []byte{} closedScidBucket = []byte("closed-scid") + + // ErrVersionNotSupportedForKVDB is returned with KVStore queries are + // made using a gossip version other than V1. + ErrVersionNotSupportedForKVDB = errors.New("only gossip v1 is " + + "supported for kvdb graph store") ) const ( @@ -199,8 +204,8 @@ type KVStore struct { } // A compile-time assertion to ensure that the KVStore struct implements the -// V1Store interface. -var _ V1Store = (*KVStore)(nil) +// Store interface. +var _ Store = (*KVStore)(nil) // NewKVStore allocates a new KVStore backed by a DB instance. The // returned instance has its own unique reject cache and channel cache. @@ -376,7 +381,7 @@ func initKVStore(db kvdb.Backend) error { // unknown to the graph DB or not. // // NOTE: this is part of the channeldb.AddrSource interface. -func (c *KVStore) AddrsForNode(ctx context.Context, +func (c *KVStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, nodePub *btcec.PublicKey) (bool, []net.Addr, error) { pubKey, err := route.NewVertexFromBytes(nodePub.SerializeCompressed()) @@ -384,7 +389,7 @@ func (c *KVStore) AddrsForNode(ctx context.Context, return false, nil, err } - node, err := c.FetchNode(ctx, pubKey) + node, err := c.FetchNode(ctx, v, pubKey) // We don't consider it an error if the graph is unaware of the node. switch { case err != nil && !errors.Is(err, ErrGraphNodeNotFound): @@ -406,10 +411,14 @@ func (c *KVStore) AddrsForNode(ctx context.Context, // NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer // for that particular channel edge routing policy will be passed into the // callback. -func (c *KVStore) ForEachChannel(_ context.Context, +func (c *KVStore) ForEachChannel(_ context.Context, v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { + if v != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + return forEachChannel(c.db, cb, reset) } @@ -666,8 +675,12 @@ func (c *KVStore) ForEachNodeDirectedChannel(nodePub route.Vertex, // known for the node, an empty feature vector is returned. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (c *KVStore) FetchNodeFeatures(nodePub route.Vertex) ( - *lnwire.FeatureVector, error) { +func (c *KVStore) FetchNodeFeatures(v lnwire.GossipVersion, + nodePub route.Vertex) (*lnwire.FeatureVector, error) { + + if v != lnwire.GossipVersion1 { + return nil, ErrVersionNotSupportedForKVDB + } return c.fetchNodeFeatures(nil, nodePub) } @@ -804,7 +817,7 @@ func (c *KVStore) DisabledChannelIDs() ([]uint64, error) { // returns an error, then the transaction is aborted and the iteration stops // early. // -// NOTE: this is part of the V1Store interface. +// NOTE: this is part of the Store interface. func (c *KVStore) ForEachNode(_ context.Context, cb func(*models.Node) error, reset func()) error { @@ -901,7 +914,13 @@ func (c *KVStore) ForEachNodeCacheable(_ context.Context, // as the center node within a star-graph. This method may be used to kick off // a path finding algorithm in order to explore the reachability of another // node based off the source node. -func (c *KVStore) SourceNode(_ context.Context) (*models.Node, error) { +func (c *KVStore) SourceNode(_ context.Context, + v lnwire.GossipVersion) (*models.Node, error) { + + if v != lnwire.GossipVersion1 { + return nil, ErrVersionNotSupportedForKVDB + } + return sourceNode(c.db) } @@ -955,6 +974,10 @@ func sourceNodeWithTx(nodes kvdb.RBucket) (*models.Node, error) { func (c *KVStore) SetSourceNode(_ context.Context, node *models.Node) error { + if node.Version != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + nodePubBytes := node.PubKeyBytes[:] return kvdb.Update(c.db, func(tx kvdb.RwTx) error { @@ -1021,9 +1044,13 @@ func addLightningNode(tx kvdb.RwTx, node *models.Node) error { // LookupAlias attempts to return the alias as advertised by the target node. // TODO(roasbeef): currently assumes that aliases are unique... -func (c *KVStore) LookupAlias(_ context.Context, +func (c *KVStore) LookupAlias(_ context.Context, v lnwire.GossipVersion, pub *btcec.PublicKey) (string, error) { + if v != lnwire.GossipVersion1 { + return "", ErrVersionNotSupportedForKVDB + } + var alias string err := kvdb.View(c.db, func(tx kvdb.RTx) error { @@ -1060,9 +1087,13 @@ func (c *KVStore) LookupAlias(_ context.Context, // DeleteNode starts a new database transaction to remove a vertex/node // from the database according to the node's public key. -func (c *KVStore) DeleteNode(_ context.Context, +func (c *KVStore) DeleteNode(_ context.Context, v lnwire.GossipVersion, nodePub route.Vertex) error { + if v != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + // TODO(roasbeef): ensure dangling edges are removed... return kvdb.Update(c.db, func(tx kvdb.RwTx) error { nodes := tx.ReadWriteBucket(nodeBucket) @@ -1152,8 +1183,11 @@ func (c *KVStore) AddChannelEdge(ctx context.Context, case alreadyExists: return ErrEdgeAlreadyExist default: - c.rejectCache.remove(edge.ChannelID) + c.rejectCache.remove( + lnwire.GossipVersion1, edge.ChannelID, + ) c.chanCache.remove(edge.ChannelID) + return nil } }, @@ -1235,9 +1269,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, // Mark edge policies for both sides as unknown. This is to enable // efficient incoming channel lookup for a node. - keys := []*[33]byte{ - &edge.NodeKey1Bytes, - &edge.NodeKey2Bytes, + keys := []route.Vertex{ + edge.NodeKey1Bytes, + edge.NodeKey2Bytes, } for _, key := range keys { err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:]) @@ -1256,13 +1290,13 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, return chanIndex.Put(b.Bytes(), chanKey[:]) } -// HasChannelEdge returns true if the database knows of a channel edge with the -// passed channel ID, and false otherwise. If an edge with that ID is found -// within the graph, then two time stamps representing the last time the edge -// was updated for both directed edges are returned along with the boolean. If -// it is not found, then the zombie index is checked and its result is returned -// as the second boolean. -func (c *KVStore) HasChannelEdge( +// HasV1ChannelEdge returns true if the database knows of a channel edge +// with the passed channel ID, and false otherwise. If an edge with that ID +// is found within the graph, then two time stamps representing the last time +// the edge was updated for both directed edges are returned along with the +// boolean. If it is not found, then the zombie index is checked and its +// result is returned as the second boolean. +func (c *KVStore) HasV1ChannelEdge( chanID uint64) (time.Time, time.Time, bool, bool, error) { var ( @@ -1275,7 +1309,7 @@ func (c *KVStore) HasChannelEdge( // We'll query the cache with the shared lock held to allow multiple // readers to access values in the cache concurrently if they exist. c.cacheMu.RLock() - if entry, ok := c.rejectCache.get(chanID); ok { + if entry, ok := c.rejectCache.get(lnwire.GossipVersion1, chanID); ok { c.cacheMu.RUnlock() upd1Time = time.Unix(entry.upd1Time, 0) upd2Time = time.Unix(entry.upd2Time, 0) @@ -1291,7 +1325,7 @@ func (c *KVStore) HasChannelEdge( // The item was not found with the shared lock, so we'll acquire the // exclusive lock and check the cache again in case another method added // the entry to the cache while no lock was held. - if entry, ok := c.rejectCache.get(chanID); ok { + if entry, ok := c.rejectCache.get(lnwire.GossipVersion1, chanID); ok { upd1Time = time.Unix(entry.upd1Time, 0) upd2Time = time.Unix(entry.upd2Time, 0) exists, isZombie = entry.flags.unpack() @@ -1358,7 +1392,7 @@ func (c *KVStore) HasChannelEdge( return time.Time{}, time.Time{}, exists, isZombie, err } - c.rejectCache.insert(chanID, rejectCacheEntry{ + c.rejectCache.insert(lnwire.GossipVersion1, chanID, rejectCacheEntry{ upd1Time: upd1Time.Unix(), upd2Time: upd2Time.Unix(), flags: packRejectFlags(exists, isZombie), @@ -1367,10 +1401,32 @@ func (c *KVStore) HasChannelEdge( return upd1Time, upd2Time, exists, isZombie, nil } +// HasChannelEdge returns true if the database knows of a channel edge with the +// passed channel ID and gossip version, and false otherwise. If it is not +// found, then the zombie index is checked and its result is returned as the +// second boolean. +func (c *KVStore) HasChannelEdge(v lnwire.GossipVersion, + chanID uint64) (bool, bool, error) { + + if v != lnwire.GossipVersion1 { + return false, false, ErrVersionNotSupportedForKVDB + } + + _, _, exists, isZombie, err := c.HasV1ChannelEdge(chanID) + + return exists, isZombie, err +} + // AddEdgeProof sets the proof of an existing edge in the graph database. func (c *KVStore) AddEdgeProof(chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { + // We only support v1 channel proofs in the KVStore. + if proof.Version != lnwire.GossipVersion1 { + return fmt.Errorf("only v1 channel proofs supported, got v%d", + proof.Version) + } + // Construct the channel's primary key which is the 8-byte channel ID. var chanKey [8]byte binary.BigEndian.PutUint64(chanKey[:], chanID.ToUint64()) @@ -1531,7 +1587,7 @@ func (c *KVStore) PruneGraph(spentOutputs []*wire.OutPoint, } for _, channel := range chansClosed { - c.rejectCache.remove(channel.ChannelID) + c.rejectCache.remove(lnwire.GossipVersion1, channel.ChannelID) c.chanCache.remove(channel.ChannelID) } @@ -1798,7 +1854,7 @@ func (c *KVStore) DisconnectBlockAtHeight(height uint32) ( } for _, channel := range removedChans { - c.rejectCache.remove(channel.ChannelID) + c.rejectCache.remove(lnwire.GossipVersion1, channel.ChannelID) c.chanCache.remove(channel.ChannelID) } @@ -1856,8 +1912,13 @@ func (c *KVStore) PruneTip() (*chainhash.Hash, uint32, error) { // that we require the node that failed to send the fresh update to be the one // that resurrects the channel from its zombie state. The markZombie bool // denotes whether or not to mark the channel as a zombie. -func (c *KVStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, - chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) { +func (c *KVStore) DeleteChannelEdges(v lnwire.GossipVersion, + strictZombiePruning, markZombie bool, chanIDs ...uint64) ( + []*models.ChannelEdgeInfo, error) { + + if v != lnwire.GossipVersion1 { + return nil, ErrVersionNotSupportedForKVDB + } // TODO(roasbeef): possibly delete from node bucket if node has no more // channels @@ -1912,7 +1973,7 @@ func (c *KVStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, } for _, chanID := range chanIDs { - c.rejectCache.remove(chanID) + c.rejectCache.remove(lnwire.GossipVersion1, chanID) c.chanCache.remove(chanID) } @@ -3227,13 +3288,14 @@ func (c *KVStore) updateEdgeCache(e *models.ChannelEdgePolicy, // the entry with the updated timestamp for the direction that was just // written. If the edge doesn't exist, we'll load the cache entry lazily // during the next query for this edge. - if entry, ok := c.rejectCache.get(e.ChannelID); ok { + entry, ok := c.rejectCache.get(lnwire.GossipVersion1, e.ChannelID) + if ok { if isUpdate1 { entry.upd1Time = e.LastUpdate.Unix() } else { entry.upd2Time = e.LastUpdate.Unix() } - c.rejectCache.insert(e.ChannelID, entry) + c.rejectCache.insert(lnwire.GossipVersion1, e.ChannelID, entry) } // If an entry for this channel is found in channel cache, we'll modify @@ -3258,6 +3320,9 @@ func updateEdgePolicy(tx kvdb.RwTx, edge *models.ChannelEdgePolicy) ( route.Vertex, route.Vertex, bool, error) { var noVertex route.Vertex + if edge.Version != lnwire.GossipVersion1 { + return noVertex, noVertex, false, ErrVersionNotSupportedForKVDB + } edges := tx.ReadWriteBucket(edgeBucket) if edges == nil { @@ -3339,8 +3404,11 @@ func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex, } // Since the edge _does_ extend to the source node, we'll also - // need to ensure that this is a public edge. - if info.AuthProof != nil { + // need to ensure that this is a public edge with valid + // signatures (not empty). + if info.AuthProof != nil && !info.AuthProof.IsEmpty() && + len(info.AuthProof.BitcoinSig1()) > 0 { + nodeIsPublic = true return errDone } @@ -3370,9 +3438,13 @@ func (c *KVStore) fetchNodeTx(tx kvdb.RTx, nodePub route.Vertex) (*models.Node, // FetchNode attempts to look up a target node by its identity public // key. If the node isn't found in the database, then ErrGraphNodeNotFound is // returned. -func (c *KVStore) FetchNode(_ context.Context, +func (c *KVStore) FetchNode(_ context.Context, v lnwire.GossipVersion, nodePub route.Vertex) (*models.Node, error) { + if v != lnwire.GossipVersion1 { + return nil, ErrVersionNotSupportedForKVDB + } + return c.fetchLightningNode(nil, nodePub) } @@ -3433,11 +3505,12 @@ func (c *KVStore) fetchLightningNode(tx kvdb.RTx, return node, nil } -// HasNode determines if the graph has a vertex identified by the target node -// identity public key. If the node exists in the database, a timestamp of when -// the data for the node was lasted updated is returned along with a true -// boolean. Otherwise, an empty time.Time is returned with a false boolean. -func (c *KVStore) HasNode(_ context.Context, +// HasV1Node determines if the graph has a vertex identified by the +// target node identity public key. If the node exists in the database, a +// timestamp of when the data for the node was lasted updated is returned along +// with a true boolean. Otherwise, an empty time.Time is returned with a false +// boolean. +func (c *KVStore) HasV1Node(_ context.Context, nodePub [33]byte) (time.Time, bool, error) { var ( @@ -3457,7 +3530,6 @@ func (c *KVStore) HasNode(_ context.Context, // exit early. nodeBytes := nodes.Get(nodePub[:]) if nodeBytes == nil { - exists = false return nil } @@ -3485,6 +3557,44 @@ func (c *KVStore) HasNode(_ context.Context, return updateTime, exists, nil } +// HasNode determines if the graph has a vertex identified by the target node +// identity public key. +func (c *KVStore) HasNode(_ context.Context, v lnwire.GossipVersion, + nodePub [33]byte) (bool, error) { + + if v != lnwire.GossipVersion1 { + return false, ErrVersionNotSupportedForKVDB + } + + var exists bool + err := kvdb.View(c.db, func(tx kvdb.RTx) error { + // First grab the nodes bucket which stores the mapping from + // pubKey to node information. + nodes := tx.ReadBucket(nodeBucket) + if nodes == nil { + return ErrGraphNotFound + } + + // If a key for this serialized public key isn't found, we can + // exit early. + nodeBytes := nodes.Get(nodePub[:]) + if nodeBytes == nil { + return nil + } + + exists = true + + return nil + }, func() { + exists = false + }) + if err != nil { + return exists, err + } + + return exists, nil +} + // nodeTraversal is used to traverse all channels of a node given by its // public key and passes channel information into the specified callback. // @@ -3580,10 +3690,15 @@ func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend, // halted with the error propagated back up to the caller. // // Unknown policies are passed into the callback as nil values. -func (c *KVStore) ForEachNodeChannel(_ context.Context, nodePub route.Vertex, +func (c *KVStore) ForEachNodeChannel(_ context.Context, + v lnwire.GossipVersion, nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { + if v != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + return nodeTraversal( nil, nodePub[:], c.db, func(_ kvdb.RTx, info *models.ChannelEdgeInfo, policy, @@ -3731,8 +3846,8 @@ func computeEdgePolicyKeys(info *models.ChannelEdgeInfo) ([]byte, []byte) { // found, then ErrEdgeNotFound is returned. A struct which houses the general // information for the channel itself is returned as well as two structs that // contain the routing policies for the channel in either direction. -func (c *KVStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, +func (c *KVStore) FetchChannelEdgesByOutpoint(v lnwire.GossipVersion, + op *wire.OutPoint) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { var ( @@ -3741,6 +3856,10 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( policy2 *models.ChannelEdgePolicy ) + if v != lnwire.GossipVersion1 { + return nil, nil, nil, ErrVersionNotSupportedForKVDB + } + err := kvdb.View(c.db, func(tx kvdb.RTx) error { // First, grab the node bucket. This will be used to populate // the Node pointers in each edge read from disk. @@ -3817,10 +3936,14 @@ func (c *KVStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( // ErrZombieEdge an be returned if the edge is currently marked as a zombie // within the database. In this case, the ChannelEdgePolicy's will be nil, and // the ChannelEdgeInfo will only include the public keys of each node. -func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, +func (c *KVStore) FetchChannelEdgesByID(v lnwire.GossipVersion, + chanID uint64) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { + if v != lnwire.GossipVersion1 { + return nil, nil, nil, ErrVersionNotSupportedForKVDB + } + var ( edgeInfo *models.ChannelEdgeInfo policy1 *models.ChannelEdgePolicy @@ -3875,10 +3998,14 @@ func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( // populate the edge info with the public keys of each // party as this is the only information we have about // it and return an error signaling so. - edgeInfo = &models.ChannelEdgeInfo{ - NodeKey1Bytes: pubKey1, - NodeKey2Bytes: pubKey2, + zombieEdge, err := models.NewV1Channel( + 0, chainhash.Hash{}, pubKey1, pubKey2, + &models.ChannelV1Fields{}, + ) + if err != nil { + return err } + edgeInfo = zombieEdge return ErrZombieEdge } @@ -3921,7 +4048,13 @@ func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( // IsPublicNode is a helper method that determines whether the node with the // given public key is seen as a public node in the graph from the graph's // source node's point of view. -func (c *KVStore) IsPublicNode(pubKey [33]byte) (bool, error) { +func (c *KVStore) IsPublicNode(v lnwire.GossipVersion, pubKey [33]byte) (bool, + error) { + + if v != lnwire.GossipVersion1 { + return false, ErrVersionNotSupportedForKVDB + } + var nodeIsPublic bool err := kvdb.View(c.db, func(tx kvdb.RTx) error { nodes := tx.ReadBucket(nodeBucket) @@ -4033,10 +4166,7 @@ func (c *KVStore) ChannelView() ([]EdgePoint, error) { return err } - pkScript, err := genMultiSigP2WSH( - edgeInfo.BitcoinKey1Bytes[:], - edgeInfo.BitcoinKey2Bytes[:], - ) + pkScript, err := edgeInfo.FundingPKScript() if err != nil { return err } @@ -4084,7 +4214,7 @@ func (c *KVStore) MarkEdgeZombie(chanID uint64, return err } - c.rejectCache.remove(chanID) + c.rejectCache.remove(lnwire.GossipVersion1, chanID) c.chanCache.remove(chanID) return nil @@ -4153,7 +4283,7 @@ func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error { return err } - c.rejectCache.remove(chanID) + c.rejectCache.remove(lnwire.GossipVersion1, chanID) c.chanCache.remove(chanID) return nil @@ -4162,14 +4292,19 @@ func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error { // IsZombieEdge returns whether the edge is considered zombie. If it is a // zombie, then the two node public keys corresponding to this edge are also // returned. -func (c *KVStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, - error) { +func (c *KVStore) IsZombieEdge(v lnwire.GossipVersion, + chanID uint64) (bool, [33]byte, [33]byte, error) { var ( isZombie bool pubKey1, pubKey2 [33]byte ) + if v != lnwire.GossipVersion1 { + return false, [33]byte{}, [33]byte{}, + ErrVersionNotSupportedForKVDB + } + err := kvdb.View(c.db, func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { @@ -4334,6 +4469,10 @@ func (c *nodeTraverserSession) FetchNodeFeatures(nodePub route.Vertex) ( func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket, node *models.Node) error { + if node.Version != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + var ( scratch [16]byte b bytes.Buffer @@ -4631,6 +4770,12 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { func putChanEdgeInfo(edgeIndex kvdb.RwBucket, edgeInfo *models.ChannelEdgeInfo, chanID [8]byte) error { + // We only support V1 channel edges in the KV store. + if edgeInfo.Version != lnwire.GossipVersion1 { + return fmt.Errorf("only V1 channel edges supported, got V%d", + edgeInfo.Version) + } + var b bytes.Buffer if _, err := b.Write(edgeInfo.NodeKey1Bytes[:]); err != nil { @@ -4639,10 +4784,24 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if _, err := b.Write(edgeInfo.NodeKey2Bytes[:]); err != nil { return err } - if _, err := b.Write(edgeInfo.BitcoinKey1Bytes[:]); err != nil { + + btc1Key, err := edgeInfo.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("edge missing bitcoin key 1"), + ) + if err != nil { return err } - if _, err := b.Write(edgeInfo.BitcoinKey2Bytes[:]); err != nil { + btc2Key, err := edgeInfo.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("edge missing bitcoin key 2"), + ) + if err != nil { + return err + } + + if _, err := b.Write(btc1Key[:]); err != nil { + return err + } + if _, err := b.Write(btc2Key[:]); err != nil { return err } @@ -4658,10 +4817,10 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, authProof := edgeInfo.AuthProof var nodeSig1, nodeSig2, bitcoinSig1, bitcoinSig2 []byte if authProof != nil { - nodeSig1 = authProof.NodeSig1Bytes - nodeSig2 = authProof.NodeSig2Bytes - bitcoinSig1 = authProof.BitcoinSig1Bytes - bitcoinSig2 = authProof.BitcoinSig2Bytes + nodeSig1 = authProof.NodeSig1() + nodeSig2 = authProof.NodeSig2() + bitcoinSig1 = authProof.BitcoinSig1() + bitcoinSig2 = authProof.BitcoinSig2() } if err := wire.WriteVarBytes(&b, 0, nodeSig1); err != nil { @@ -4680,7 +4839,7 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { return err } - err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) + err = binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) if err != nil { return err } @@ -4793,18 +4952,26 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { edgeInfo models.ChannelEdgeInfo ) + // All channel edges in the KV store are V1. + edgeInfo.Version = lnwire.GossipVersion1 + if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil { return nil, err } if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil { return nil, err } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil { + + var btcKey1, btcKey2 route.Vertex + if _, err := io.ReadFull(r, btcKey1[:]); err != nil { return nil, err } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil { + edgeInfo.BitcoinKey1Bytes = fn.Some(btcKey1) + + if _, err := io.ReadFull(r, btcKey2[:]); err != nil { return nil, err } + edgeInfo.BitcoinKey2Bytes = fn.Some(btcKey2) featureBytes, err := wire.ReadVarBytes(r, 0, 900, "features") if err != nil { @@ -4816,24 +4983,42 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { return nil, err } - proof := &models.ChannelAuthProof{} + proof := &models.ChannelAuthProof{ + // KV store always uses v1. + Version: lnwire.GossipVersion1, + } - proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + nodeSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(nodeSig1) > 0 { + proof.NodeSig1Bytes = fn.Some(nodeSig1) + } + + nodeSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(nodeSig2) > 0 { + proof.NodeSig2Bytes = fn.Some(nodeSig2) + } + + bitcoinSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(bitcoinSig1) > 0 { + proof.BitcoinSig1Bytes = fn.Some(bitcoinSig1) + } + + bitcoinSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } + if len(bitcoinSig2) > 0 { + proof.BitcoinSig2Bytes = fn.Some(bitcoinSig2) + } if !proof.IsEmpty() { edgeInfo.AuthProof = proof @@ -5073,6 +5258,10 @@ func fetchChanEdgePolicies(edgeIndex kvdb.RBucket, edges kvdb.RBucket, func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy, to []byte) error { + if edge.Version != lnwire.GossipVersion1 { + return ErrVersionNotSupportedForKVDB + } + err := wire.WriteVarBytes(w, 0, edge.SigBytes) if err != nil { return err @@ -5161,7 +5350,9 @@ func deserializeChanEdgePolicy(r io.Reader) (*models.ChannelEdgePolicy, error) { func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy, error) { - edge := &models.ChannelEdgePolicy{} + edge := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, + } var err error edge.SigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig") diff --git a/graph/db/models/cached_edge_policy.go b/graph/db/models/cached_edge_policy.go index 40b0d9212..90c8d56c3 100644 --- a/graph/db/models/cached_edge_policy.go +++ b/graph/db/models/cached_edge_policy.go @@ -20,13 +20,15 @@ type CachedEdgePolicy struct { // and the last 2 bytes are the output index for the channel. ChannelID uint64 - // MessageFlags is a bitfield which indicates the presence of optional - // fields (like max_htlc) in the policy. - MessageFlags lnwire.ChanUpdateMsgFlags + // HasMaxHTLC indicates whether the policy has a max HTLC value. + HasMaxHTLC bool - // ChannelFlags is a bitfield which signals the capabilities of the - // channel as well as the directed edge this update applies to. - ChannelFlags lnwire.ChanUpdateChanFlags + // IsNode1 indicates whether this policy was announced by the channel's + // node_1. + IsNode1 bool + + // IsDisabled indicates whether the policy disables forwarding. + IsDisabled bool // TimeLockDelta is the number of blocks this node will subtract from // the expiry of an incoming HTLC. This value expresses the time buffer @@ -75,24 +77,31 @@ func (c *CachedEdgePolicy) ComputeFee( return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts } -// IsDisabled returns true if the channel is disabled in the direction from the -// advertising node. -func (c *CachedEdgePolicy) IsDisabled() bool { - return c.ChannelFlags&lnwire.ChanUpdateDisabled != 0 -} - -// IsNode1 returns true if this policy was announced by the channel's node_1 -// node. -func (c *CachedEdgePolicy) IsNode1() bool { - return c.ChannelFlags&lnwire.ChanUpdateDirection == 0 -} - // NewCachedPolicy turns a full policy into a minimal one that can be cached. func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy { + if policy.Version != lnwire.GossipVersion2 { + return &CachedEdgePolicy{ + ChannelID: policy.ChannelID, + HasMaxHTLC: policy.MessageFlags.HasMaxHtlc(), + IsDisabled: policy.ChannelFlags& + lnwire.ChanUpdateDisabled != 0, + IsNode1: policy.ChannelFlags& + lnwire.ChanUpdateDirection == 0, + TimeLockDelta: policy.TimeLockDelta, + MinHTLC: policy.MinHTLC, + MaxHTLC: policy.MaxHTLC, + FeeBaseMSat: policy.FeeBaseMSat, + FeeProportionalMillionths: policy. + FeeProportionalMillionths, + InboundFee: policy.InboundFee, + } + } + return &CachedEdgePolicy{ ChannelID: policy.ChannelID, - MessageFlags: policy.MessageFlags, - ChannelFlags: policy.ChannelFlags, + HasMaxHTLC: true, + IsNode1: !policy.SecondPeer, + IsDisabled: !policy.DisableFlags.IsEnabled(), TimeLockDelta: policy.TimeLockDelta, MinHTLC: policy.MinHTLC, MaxHTLC: policy.MaxHTLC, diff --git a/graph/db/models/channel_auth_proof.go b/graph/db/models/channel_auth_proof.go index daf120b10..e4acc2f8b 100644 --- a/graph/db/models/channel_auth_proof.go +++ b/graph/db/models/channel_auth_proof.go @@ -1,35 +1,117 @@ package models +import ( + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/lnwire" +) + // ChannelAuthProof is the authentication proof (the signature portion) for a -// channel. Using the four signatures contained in the struct, and some +// channel. +// +// For v1 channels: +// Using the four node and bitcoin signatures contained in the struct, and some // auxiliary knowledge (the funding script, node identities, and outpoint) nodes // on the network are able to validate the authenticity and existence of a // channel. Each of these signatures signs the following digest: chanID || // nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len || // features. +// +// For v2 channels: +// The single schnorr signature signs the tlv fields of the v2 channel +// announcement message which are in the signed range. type ChannelAuthProof struct { + // Version is the version of the channel announcement. + Version lnwire.GossipVersion + // NodeSig1Bytes are the raw bytes of the first node signature encoded // in DER format. - NodeSig1Bytes []byte + // + // NOTE: v1 channel announcements only. + NodeSig1Bytes fn.Option[[]byte] // NodeSig2Bytes are the raw bytes of the second node signature // encoded in DER format. - NodeSig2Bytes []byte + // + // NOTE: v1 channel announcements only. + NodeSig2Bytes fn.Option[[]byte] // BitcoinSig1Bytes are the raw bytes of the first bitcoin signature // encoded in DER format. - BitcoinSig1Bytes []byte + // + // NOTE: v1 channel announcements only. + BitcoinSig1Bytes fn.Option[[]byte] // BitcoinSig2Bytes are the raw bytes of the second bitcoin signature // encoded in DER format. - BitcoinSig2Bytes []byte + // + // NOTE: v1 channel announcements only. + BitcoinSig2Bytes fn.Option[[]byte] + + // Signature is the raw bytes of the single schnorr signature for v2 + // channel announcements. + // + // NOTE: v2 channel announcements only. + Signature fn.Option[[]byte] } -// IsEmpty check is the authentication proof is empty Proof is empty if at -// least one of the signatures are equal to nil. +// IsEmpty check is the authentication proof is empty Proof is empty. func (c *ChannelAuthProof) IsEmpty() bool { - return len(c.NodeSig1Bytes) == 0 || - len(c.NodeSig2Bytes) == 0 || - len(c.BitcoinSig1Bytes) == 0 || - len(c.BitcoinSig2Bytes) == 0 + // For v1 channel announcements, we either have all four signatures or + // none. + if c.Version == lnwire.GossipVersion1 { + return c.NodeSig1Bytes.IsNone() + } + + // For v2 channel announcements, we only have a single signature. + return c.Signature.IsNone() +} + +// NewV1ChannelAuthProof creates a new ChannelAuthProof for a v1 channel +// announcement. +func NewV1ChannelAuthProof(nodeSig1, nodeSig2, bitcoinSig1, + bitcoinSig2 []byte) *ChannelAuthProof { + + return &ChannelAuthProof{ + Version: lnwire.GossipVersion1, + NodeSig1Bytes: fn.Some(nodeSig1), + NodeSig2Bytes: fn.Some(nodeSig2), + BitcoinSig1Bytes: fn.Some(bitcoinSig1), + BitcoinSig2Bytes: fn.Some(bitcoinSig2), + } +} + +// NewV2ChannelAuthProof creates a new ChannelAuthProof for a v2 channel +// announcement. +func NewV2ChannelAuthProof(signature []byte) *ChannelAuthProof { + return &ChannelAuthProof{ + Version: lnwire.GossipVersion2, + Signature: fn.Some(signature), + } +} + +// NodeSig1 returns the first node signature bytes, or nil if not present. +func (c *ChannelAuthProof) NodeSig1() []byte { + return c.NodeSig1Bytes.UnwrapOr(nil) +} + +// NodeSig2 returns the second node signature bytes, or nil if not present. +func (c *ChannelAuthProof) NodeSig2() []byte { + return c.NodeSig2Bytes.UnwrapOr(nil) +} + +// BitcoinSig1 returns the first bitcoin signature bytes, or nil if not +// present. +func (c *ChannelAuthProof) BitcoinSig1() []byte { + return c.BitcoinSig1Bytes.UnwrapOr(nil) +} + +// BitcoinSig2 returns the second bitcoin signature bytes, or nil if not +// present. +func (c *ChannelAuthProof) BitcoinSig2() []byte { + return c.BitcoinSig2Bytes.UnwrapOr(nil) +} + +// Sig returns the v2 signature bytes, or nil if not present. +func (c *ChannelAuthProof) Sig() []byte { + return c.Signature.UnwrapOr(nil) } diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index 0d6cd04f0..aae7b91f0 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -5,11 +5,14 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // ChannelEdgeInfo represents a fully authenticated channel along with all its @@ -19,6 +22,9 @@ import ( // policy of a channel are stored within a ChannelEdgePolicy for each direction // of the channel. type ChannelEdgeInfo struct { + // Version is the gossip version that this channel was advertised on. + Version lnwire.GossipVersion + // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, // and the last 2 bytes are the output index for the channel. @@ -29,16 +35,22 @@ type ChannelEdgeInfo struct { ChainHash chainhash.Hash // NodeKey1Bytes is the raw public key of the first node. - NodeKey1Bytes [33]byte + NodeKey1Bytes route.Vertex - // NodeKey2Bytes is the raw public key of the first node. - NodeKey2Bytes [33]byte + // NodeKey2Bytes is the raw public key of the second node. + NodeKey2Bytes route.Vertex // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes [33]byte + // + // NOTE: this must be set for v1 channels but is optional for v2 and + // beyond. + BitcoinKey1Bytes fn.Option[route.Vertex] - // BitcoinKey2Bytes is the raw public key of the first node. - BitcoinKey2Bytes [33]byte + // BitcoinKey2Bytes is the raw public key of the second node. + // + // NOTE: this must be set for v1 channels but is optional for v2 and + // beyond. + BitcoinKey2Bytes fn.Option[route.Vertex] // Features is the list of protocol features supported by this channel // edge. @@ -63,6 +75,84 @@ type ChannelEdgeInfo struct { // the edge object is loaded from the database. FundingScript fn.Option[[]byte] + // MerkleRootHash is an optional root hash of a Merkle tree that the + // funding output is committed to. This is then used to compute the + // final funding output script. + // + // NOTE: only used for version 2 channels and beyond. + MerkleRootHash fn.Option[chainhash.Hash] + + // 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. + // + // NOTE: only used for version 1 channels. + ExtraOpaqueData []byte + + // ExtraSignedFields is a map of extra fields that are covered by the + // node announcement's signature that we have not explicitly parsed. + // + // NOTE: This is only used for version 2 node announcements and beyond. + ExtraSignedFields map[uint64][]byte +} + +// EdgeModifier is a functional option that modifies a ChannelEdgeInfo. +type EdgeModifier func(*ChannelEdgeInfo) + +// WithChannelPoint sets the channel point (funding outpoint) on the edge. +func WithChannelPoint(cp wire.OutPoint) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.ChannelPoint = cp + } +} + +// WithFeatures sets the feature vector on the edge. +func WithFeatures(f *lnwire.RawFeatureVector) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.Features = lnwire.NewFeatureVector(f, lnwire.Features) + } +} + +// WithCapacity sets the capacity on the edge. +func WithCapacity(c btcutil.Amount) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.Capacity = c + } +} + +// WithChanProof sets the authentication proof on the edge. +func WithChanProof(proof *ChannelAuthProof) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.AuthProof = proof + } +} + +// WithFundingScript sets the funding script on the edge. +func WithFundingScript(script []byte) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.FundingScript = fn.Some(script) + } +} + +// WithMerkleRootHash sets the merkle root hash on the edge. +func WithMerkleRootHash(hash chainhash.Hash) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.MerkleRootHash = fn.Some(hash) + } +} + +// ChannelV1Fields contains the fields that are specific to v1 channel +// announcements. +type ChannelV1Fields struct { + // BitcoinKey1Bytes is the raw public key of the first node. + BitcoinKey1Bytes route.Vertex + + // BitcoinKey2Bytes is the raw public key of the second node. + BitcoinKey2Bytes route.Vertex + // 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 @@ -72,6 +162,100 @@ type ChannelEdgeInfo struct { ExtraOpaqueData []byte } +// NewV1Channel creates a new ChannelEdgeInfo for a v1 channel announcement. +// It takes the required fields for all channels (chanID, chainHash, node keys) +// and v1-specific fields, along with optional modifiers for setting additional +// fields like capacity, channel point, features, and auth proof. +// +// The constructor validates that if an AuthProof is provided via modifiers, its +// version matches the channel version (v1). +func NewV1Channel(chanID uint64, chainHash chainhash.Hash, node1, + node2 route.Vertex, v1Fields *ChannelV1Fields, + opts ...EdgeModifier) (*ChannelEdgeInfo, error) { + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, + NodeKey1Bytes: node1, + NodeKey2Bytes: node2, + BitcoinKey1Bytes: fn.Some(v1Fields.BitcoinKey1Bytes), + BitcoinKey2Bytes: fn.Some(v1Fields.BitcoinKey2Bytes), + ChannelID: chanID, + ChainHash: chainHash, + Features: lnwire.EmptyFeatureVector(), + ExtraOpaqueData: v1Fields.ExtraOpaqueData, + } + + for _, opt := range opts { + opt(edge) + } + + // Validate some fields after the options have been applied. + if edge.AuthProof != nil && edge.AuthProof.Version != edge.Version { + return nil, fmt.Errorf("channel auth proof version %d does "+ + "not match channel version %d", edge.AuthProof.Version, + edge.Version) + } + + return edge, nil +} + +// ChannelV2Fields contains the fields that are specific to v2 channel +// announcements. +type ChannelV2Fields struct { + // BitcoinKey1Bytes is the raw public key of the first node. + BitcoinKey1Bytes fn.Option[route.Vertex] + + // BitcoinKey2Bytes is the raw public key of the second node. + BitcoinKey2Bytes fn.Option[route.Vertex] + + // FundingScript is the funding output's pkScript. This is required for + // v2 channels when the bitcoin keys are not provided. + FundingScript fn.Option[[]byte] + + // MerkleRootHash is an optional root hash of a Merkle tree that the + // funding output is committed to. + MerkleRootHash fn.Option[chainhash.Hash] + + // ExtraSignedFields is a map of extra fields that are covered by the + // node announcement's signature that we have not explicitly parsed. + // + // NOTE: This is only used for version 2 node announcements and beyond. + ExtraSignedFields map[uint64][]byte +} + +// NewV2Channel creates a new ChannelEdgeInfo for a v2 channel announcement. +func NewV2Channel(chanID uint64, chainHash chainhash.Hash, node1, + node2 route.Vertex, v2Fields *ChannelV2Fields, + opts ...EdgeModifier) (*ChannelEdgeInfo, error) { + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + NodeKey1Bytes: node1, + NodeKey2Bytes: node2, + BitcoinKey1Bytes: v2Fields.BitcoinKey1Bytes, + BitcoinKey2Bytes: v2Fields.BitcoinKey2Bytes, + FundingScript: v2Fields.FundingScript, + MerkleRootHash: v2Fields.MerkleRootHash, + ChannelID: chanID, + ChainHash: chainHash, + Features: lnwire.EmptyFeatureVector(), + ExtraSignedFields: v2Fields.ExtraSignedFields, + } + + for _, opt := range opts { + opt(edge) + } + + // Validate some fields after the options have been applied. + if edge.AuthProof != nil && edge.AuthProof.Version != edge.Version { + return nil, fmt.Errorf("channel auth proof version %d does "+ + "not match channel version %d", edge.AuthProof.Version, + edge.Version) + } + + return edge, nil +} + // NodeKey1 is the identity public key of the "first" node that was involved in // the creation of this channel. A node is considered "first" if the // lexicographical ordering the its serialized public key is "smaller" than @@ -90,7 +274,7 @@ func (c *ChannelEdgeInfo) NodeKey2() (*btcec.PublicKey, error) { // OtherNodeKeyBytes returns the node key bytes of the other end of the channel. func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( - [33]byte, error) { + route.Vertex, error) { switch { case bytes.Equal(c.NodeKey1Bytes[:], thisNodeKey): @@ -98,7 +282,173 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( case bytes.Equal(c.NodeKey2Bytes[:], thisNodeKey): return c.NodeKey1Bytes, nil default: - return [33]byte{}, fmt.Errorf("node not participating in " + + return route.Vertex{}, fmt.Errorf("node not participating in " + "this channel") } } + +// FundingPKScript returns the funding output's pkScript for the channel. +func (c *ChannelEdgeInfo) FundingPKScript() ([]byte, error) { + switch c.Version { + case lnwire.GossipVersion1: + btc1Key, err := c.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("missing bitcoin key 1"), + ) + if err != nil { + return nil, err + } + btc2Key, err := c.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("missing bitcoin key 2"), + ) + if err != nil { + return nil, err + } + + witnessScript, err := input.GenMultiSigScript( + btc1Key[:], btc2Key[:], + ) + if err != nil { + return nil, err + } + + return input.WitnessScriptHash(witnessScript) + + case lnwire.GossipVersion2: + var ( + pubKey1 *btcec.PublicKey + pubKey2 *btcec.PublicKey + err error + ) + c.BitcoinKey1Bytes.WhenSome(func(key route.Vertex) { + pubKey1, err = btcec.ParsePubKey(key[:]) + }) + if err != nil { + return nil, err + } + + c.BitcoinKey2Bytes.WhenSome(func(key route.Vertex) { + pubKey2, err = btcec.ParsePubKey(key[:]) + }) + if err != nil { + return nil, err + } + + // If both bitcoin keys are not present in the announcement, + // then we should previously have stored the funding script + // found on-chain. + if pubKey1 == nil || pubKey2 == nil { + return c.FundingScript.UnwrapOrErr(fmt.Errorf( + "expected a funding pk script since no " + + "bitcoin keys were provided", + )) + } + + // By default, the tweak is empty which results in a BIP86 + // output. If we have a merkle root, we'll use that as the + // tweak. + muSig2Opt := musig2.WithBIP86KeyTweak() + c.MerkleRootHash.WhenSome(func(hash chainhash.Hash) { + muSig2Opt = musig2.WithTaprootKeyTweak(hash[:]) + }) + + // Compute the output key. + combinedKey, _, _, err := musig2.AggregateKeys( + []*btcec.PublicKey{pubKey1, pubKey2}, true, muSig2Opt, + ) + if err != nil { + return nil, err + } + + // Now that we have the combined key, we can create a taproot + // pkScript from this, and then make the txout given the amount. + fundingScript, err := input.PayToTaprootScript( + combinedKey.FinalKey, + ) + if err != nil { + return nil, fmt.Errorf("unable to make taproot "+ + "pkscript: %w", err) + } + + return fundingScript, nil + + default: + return nil, fmt.Errorf("unsupported channel version: %d", + c.Version) + } +} + +// ToChannelAnnouncement converts the ChannelEdgeInfo to a +// lnwire.ChannelAnnouncement1 message. Returns an error if AuthProof is nil +// or if the version is not v1. +func (c *ChannelEdgeInfo) ToChannelAnnouncement() ( + *lnwire.ChannelAnnouncement1, error) { + + // We currently only support v1 channel announcements. + if c.Version != lnwire.GossipVersion1 { + return nil, fmt.Errorf("unsupported channel version: %d", + c.Version) + } + + // If there's no auth proof, we can't create a full channel + // announcement. + if c.AuthProof == nil { + return nil, fmt.Errorf("cannot create channel announcement " + + "without auth proof") + } + + btc1, err := c.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("bitcoin key 1 missing for v1 channel announcement"), + ) + if err != nil { + return nil, err + } + + btc2, err := c.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("bitcoin key 2 missing for v1 channel announcement"), + ) + if err != nil { + return nil, err + } + + chanID := lnwire.NewShortChanIDFromInt(c.ChannelID) + chanAnn := &lnwire.ChannelAnnouncement1{ + ShortChannelID: chanID, + NodeID1: c.NodeKey1Bytes, + NodeID2: c.NodeKey2Bytes, + ChainHash: c.ChainHash, + BitcoinKey1: btc1, + BitcoinKey2: btc2, + Features: c.Features.RawFeatureVector, + ExtraOpaqueData: c.ExtraOpaqueData, + } + + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.NodeSig1(), + ) + if err != nil { + return nil, err + } + + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.NodeSig2(), + ) + if err != nil { + return nil, err + } + + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.BitcoinSig1(), + ) + if err != nil { + return nil, err + } + + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.BitcoinSig2(), + ) + if err != nil { + return nil, err + } + + return chanAnn, nil +} diff --git a/graph/db/models/channel_edge_info_test.go b/graph/db/models/channel_edge_info_test.go new file mode 100644 index 000000000..464ca212d --- /dev/null +++ b/graph/db/models/channel_edge_info_test.go @@ -0,0 +1,147 @@ +package models + +import ( + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" +) + +// TestFundingPKScriptV2 tests the FundingPKScript method for v2 channels +// which uses MuSig2 key aggregation. +func TestFundingPKScriptV2(t *testing.T) { + t.Parallel() + + // Generate two test keys for bitcoin keys. + privKey1, err := btcec.NewPrivateKey() + require.NoError(t, err) + pubKey1 := privKey1.PubKey() + + privKey2, err := btcec.NewPrivateKey() + require.NoError(t, err) + pubKey2 := privKey2.PubKey() + + // Convert to route.Vertex format. + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], pubKey1.SerializeCompressed()) + copy(btcKey2[:], pubKey2.SerializeCompressed()) + + // Create a test merkle root. + var merkleRoot chainhash.Hash + copy(merkleRoot[:], []byte("test-merkle-root-hash-32-bytes!")) + + t.Run("v2 with btc keys, no merkle root (BIP86)", func(t *testing.T) { + t.Parallel() + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + BitcoinKey1Bytes: fn.Some(btcKey1), + BitcoinKey2Bytes: fn.Some(btcKey2), + } + + pkScript, err := edge.FundingPKScript() + require.NoError(t, err) + require.NotEmpty(t, pkScript) + + // Verify it's a valid taproot script (OP_1 <32-byte-key>). + require.Len(t, pkScript, 34) + require.Equal(t, byte(0x51), pkScript[0]) // OP_1 + + // Manually compute expected script using BIP86 tweak. + combinedKey, _, _, err := musig2.AggregateKeys( + []*btcec.PublicKey{pubKey1, pubKey2}, true, + musig2.WithBIP86KeyTweak(), + ) + require.NoError(t, err) + + expectedScript, err := input.PayToTaprootScript( + combinedKey.FinalKey, + ) + require.NoError(t, err) + require.Equal(t, expectedScript, pkScript) + }) + + t.Run("v2 with bitcoin keys and merkle root", func(t *testing.T) { + t.Parallel() + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + BitcoinKey1Bytes: fn.Some(btcKey1), + BitcoinKey2Bytes: fn.Some(btcKey2), + MerkleRootHash: fn.Some(merkleRoot), + } + + pkScript, err := edge.FundingPKScript() + require.NoError(t, err) + require.NotEmpty(t, pkScript) + + // Verify it's a valid taproot script. + require.Len(t, pkScript, 34) + require.Equal(t, byte(0x51), pkScript[0]) // OP_1 + + // Manually compute expected script with taproot tweak. + combinedKey, _, _, err := musig2.AggregateKeys( + []*btcec.PublicKey{pubKey1, pubKey2}, true, + musig2.WithTaprootKeyTweak(merkleRoot[:]), + ) + require.NoError(t, err) + + expectedScript, err := input.PayToTaprootScript( + combinedKey.FinalKey, + ) + require.NoError(t, err) + require.Equal(t, expectedScript, pkScript) + }) + + t.Run("v2 no btc keys returns stored script", func(t *testing.T) { + t.Parallel() + + storedScript := []byte{0x51, 0x20} // OP_1 + push 32 + storedScript = append(storedScript, make([]byte, 32)...) + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + FundingScript: fn.Some(storedScript), + } + + pkScript, err := edge.FundingPKScript() + require.NoError(t, err) + require.Equal(t, storedScript, pkScript) + }) + + t.Run("v2 no btc keys and no stored script errors", func(t *testing.T) { + t.Parallel() + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + } + + _, err := edge.FundingPKScript() + require.Error(t, err) + require.Contains(t, err.Error(), "expected a funding pk script") + }) + + t.Run("v2 one btc key returns stored script", func(t *testing.T) { + t.Parallel() + + storedScript := []byte{0x51, 0x20} + storedScript = append(storedScript, make([]byte, 32)...) + + // Only key1 set, key2 missing. + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion2, + BitcoinKey1Bytes: fn.Some(btcKey1), + FundingScript: fn.Some(storedScript), + } + + pkScript, err := edge.FundingPKScript() + require.NoError(t, err) + require.Equal(t, storedScript, pkScript) + }) +} diff --git a/graph/db/models/channel_edge_policy.go b/graph/db/models/channel_edge_policy.go index 48d748ee0..067c7861a 100644 --- a/graph/db/models/channel_edge_policy.go +++ b/graph/db/models/channel_edge_policy.go @@ -4,7 +4,6 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -15,15 +14,15 @@ import ( // information concerning fees, and minimum time-lock information which is // utilized during path finding. type ChannelEdgePolicy struct { + // Version is the gossip version of the channel update that produced + // this policy. + Version lnwire.GossipVersion + // SigBytes is the raw bytes of the signature of the channel edge // policy. We'll only parse these if the caller needs to access the - // signature for validation purposes. Do not set SigBytes directly, but - // use SetSigBytes instead to make sure that the cache is invalidated. + // signature for validation purposes. SigBytes []byte - // sig is a cached fully parsed signature. - sig *ecdsa.Signature - // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, // and the last 2 bytes are the output index for the channel. @@ -33,6 +32,14 @@ type ChannelEdgePolicy struct { // was received. LastUpdate time.Time + // LastBlockHeight is the block height that timestamps the last update + // for v2 channel updates. + LastBlockHeight uint32 + + // SecondPeer indicates whether this policy was announced by the second + // peer in the channel for v2 channel updates. + SecondPeer bool + // MessageFlags is a bitfield which indicates the presence of optional // fields (like max_htlc) in the policy. MessageFlags lnwire.ChanUpdateMsgFlags @@ -41,6 +48,10 @@ type ChannelEdgePolicy struct { // channel as well as the directed edge this update applies to. ChannelFlags lnwire.ChanUpdateChanFlags + // DisableFlags is a v2-specific bitfield which signals whether the + // channel is disabled for incoming or outgoing traffic. + DisableFlags lnwire.ChanUpdateDisableFlags + // TimeLockDelta is the number of blocks this node will subtract from // the expiry of an incoming HTLC. This value expresses the time buffer // the node would like to HTLC exchanges. @@ -82,38 +93,81 @@ type ChannelEdgePolicy struct { // and ensure we're able to make upgrades to the network in a forwards // compatible manner. ExtraOpaqueData lnwire.ExtraOpaqueData + + // ExtraSignedFields are the extra signed fields found in v2 channel + // updates. + ExtraSignedFields map[uint64][]byte } -// Signature is a channel announcement signature, which is needed for proper -// edge policy announcement. -// -// NOTE: By having this method to access an attribute, we ensure we only need -// to fully deserialize the signature if absolutely necessary. -func (c *ChannelEdgePolicy) Signature() (*ecdsa.Signature, error) { - if c.sig != nil { - return c.sig, nil +// ChanEdgePolicyFromWire constructs a ChannelEdgePolicy from a channel update +// message. +func ChanEdgePolicyFromWire(scid uint64, + update lnwire.ChannelUpdate) (*ChannelEdgePolicy, error) { + + switch upd := update.(type) { + case *lnwire.ChannelUpdate1: + //nolint:ll + return &ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, + SigBytes: upd.Signature.ToSignatureBytes(), + ChannelID: scid, + LastUpdate: time.Unix(int64(upd.Timestamp), 0), + MessageFlags: upd.MessageFlags, + ChannelFlags: upd.ChannelFlags, + TimeLockDelta: upd.TimeLockDelta, + MinHTLC: upd.HtlcMinimumMsat, + MaxHTLC: upd.HtlcMaximumMsat, + FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), + FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), + InboundFee: upd.InboundFee.ValOpt(), + ExtraOpaqueData: upd.ExtraOpaqueData, + }, nil + + case *lnwire.ChannelUpdate2: + return &ChannelEdgePolicy{ + Version: lnwire.GossipVersion2, + SigBytes: upd.Signature.Val.ToSignatureBytes(), + ChannelID: scid, + LastBlockHeight: upd.BlockHeight.Val, + SecondPeer: upd.SecondPeer.IsSome(), + DisableFlags: upd.DisabledFlags.Val, + TimeLockDelta: upd.CLTVExpiryDelta.Val, + MinHTLC: upd.HTLCMinimumMsat.Val, + MaxHTLC: upd.HTLCMaximumMsat.Val, + FeeBaseMSat: lnwire.MilliSatoshi( + upd.FeeBaseMsat.Val, + ), + FeeProportionalMillionths: lnwire.MilliSatoshi( + upd.FeeProportionalMillionths.Val, + ), + InboundFee: upd.InboundFee.ValOpt(), + ExtraSignedFields: upd.ExtraSignedFields, + }, nil } - sig, err := ecdsa.ParseSignature(c.SigBytes) - if err != nil { - return nil, err - } - - c.sig = sig - - return sig, nil + return nil, fmt.Errorf("unknown channel update version: %v", + update.MsgType()) } -// SetSigBytes updates the signature and invalidates the cached parsed -// signature. -func (c *ChannelEdgePolicy) SetSigBytes(sig []byte) { - c.SigBytes = sig - c.sig = nil +// IsNode1 returns true if this policy was announced by the channel's node_1. +func (c *ChannelEdgePolicy) IsNode1() bool { + if c.Version == lnwire.GossipVersion1 { + return c.ChannelFlags&lnwire.ChanUpdateDirection == 0 + } + + return !c.SecondPeer } // IsDisabled determines whether the edge has the disabled bit set. +// +// NOTE: for v2 channel updates, we return true here only if both the incoming +// and outgoing disabled bits are set. func (c *ChannelEdgePolicy) IsDisabled() bool { - return c.ChannelFlags.IsDisabled() + if c.Version == lnwire.GossipVersion1 { + return c.ChannelFlags.IsDisabled() + } + + return !c.DisableFlags.IsEnabled() } // ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over @@ -127,7 +181,13 @@ func (c *ChannelEdgePolicy) ComputeFee( // String returns a human-readable version of the channel edge policy. func (c *ChannelEdgePolicy) String() string { - return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, ChannelFlags=%v, "+ - "LastUpdate=%v", c.ChannelID, c.MessageFlags, c.ChannelFlags, - c.LastUpdate) + if c.Version == lnwire.GossipVersion1 { + return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, "+ + "ChannelFlags=%v, LastUpdate=%v", c.ChannelID, + c.MessageFlags, c.ChannelFlags, c.LastUpdate) + } + + return fmt.Sprintf("ChannelID=%v, Node1=%v, DisableFlags=%v, "+ + "BlockHeight=%v", c.ChannelID, !c.SecondPeer, + c.DisableFlags, c.LastBlockHeight) } diff --git a/graph/db/models/node.go b/graph/db/models/node.go index 9a045d53c..7f3ce777a 100644 --- a/graph/db/models/node.go +++ b/graph/db/models/node.go @@ -27,6 +27,11 @@ type Node struct { // been updated. LastUpdate time.Time + // LastBlockHeight is the block height that timestamps the last update + // we received for this node. This is only used if this is a V2 node + // announcement. + LastBlockHeight uint32 + // Address is the TCP address this node is reachable over. Addresses []net.Addr @@ -49,8 +54,13 @@ type Node struct { // 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. + // compatible manner. This is only used for V1 node announcements. ExtraOpaqueData []byte + + // ExtraSignedFields is a map of extra fields that are covered by the + // node announcement's signature that we have not explicitly parsed. + // This is only used for version 2 node announcements and beyond. + ExtraSignedFields map[uint64][]byte } // NodeV1Fields houses the fields that are specific to a version 1 node @@ -103,6 +113,53 @@ func NewV1Node(pub route.Vertex, n *NodeV1Fields) *Node { } } +// NodeV2Fields houses the fields that are specific to a version 2 node +// announcement. +type NodeV2Fields struct { + // LastBlockHeight is the block height that timestamps the last update + // we received for this node. + LastBlockHeight uint32 + + // Address is the TCP address this node is reachable over. + Addresses []net.Addr + + // Color is the selected color for the node. + 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 fn.Option[string] + + // Signature is the schnorr signature under the advertised public key + // which serves to authenticate the attributes announced by this node. + Signature []byte + + // Features is the list of protocol features supported by this node. + Features *lnwire.RawFeatureVector + + // ExtraSignedFields is a map of extra fields that are covered by the + // node announcement's signature that we have not explicitly parsed. + ExtraSignedFields map[uint64][]byte +} + +// NewV2Node creates a new version 2 node from the passed fields. +func NewV2Node(pub route.Vertex, n *NodeV2Fields) *Node { + return &Node{ + Version: lnwire.GossipVersion2, + PubKeyBytes: pub, + Addresses: n.Addresses, + AuthSigBytes: n.Signature, + Features: lnwire.NewFeatureVector( + n.Features, lnwire.Features, + ), + LastBlockHeight: n.LastBlockHeight, + Color: n.Color, + Alias: n.Alias, + LastUpdate: time.Unix(0, 0), + ExtraSignedFields: n.ExtraSignedFields, + } +} + // NewV1ShellNode creates a new shell version 1 node. func NewV1ShellNode(pubKey route.Vertex) *Node { return NewShellNode(lnwire.GossipVersion1, pubKey) diff --git a/graph/db/reject_cache.go b/graph/db/reject_cache.go index 2a2721928..f54463224 100644 --- a/graph/db/reject_cache.go +++ b/graph/db/reject_cache.go @@ -1,5 +1,11 @@ package graphdb +import ( + "time" + + "github.com/lightningnetwork/lnd/lnwire" +) + // rejectFlags is a compact representation of various metadata stored by the // reject cache about a particular channel. type rejectFlags uint8 @@ -41,9 +47,63 @@ func (f rejectFlags) unpack() (bool, bool) { // including the timestamps of its latest edge policies and whether or not the // channel exists in the graph. type rejectCacheEntry struct { + // upd{1,2}Time are Unix timestamps for v1 policies. upd1Time int64 upd2Time int64 - flags rejectFlags + + // upd{1,2}BlockHeight are the last known block heights for v2 + // policies. + upd1BlockHeight uint32 + upd2BlockHeight uint32 + + flags rejectFlags +} + +func newRejectCacheEntryV1(upd1, upd2 time.Time, exists, + isZombie bool) rejectCacheEntry { + + return rejectCacheEntry{ + upd1Time: upd1.Unix(), + upd2Time: upd2.Unix(), + flags: packRejectFlags(exists, isZombie), + } +} + +func newRejectCacheEntryV2(upd1, upd2 uint32, exists, + isZombie bool) rejectCacheEntry { + + return rejectCacheEntry{ + upd1BlockHeight: upd1, + upd2BlockHeight: upd2, + flags: packRejectFlags(exists, isZombie), + } +} + +func updateRejectCacheEntryV1(entry *rejectCacheEntry, isUpdate1 bool, + lastUpdate time.Time) { + + if isUpdate1 { + entry.upd1Time = lastUpdate.Unix() + } else { + entry.upd2Time = lastUpdate.Unix() + } +} + +func updateRejectCacheEntryV2(entry *rejectCacheEntry, isUpdate1 bool, + blockHeight uint32) { + + if isUpdate1 { + entry.upd1BlockHeight = blockHeight + } else { + entry.upd2BlockHeight = blockHeight + } +} + +// rejectCacheKey uniquely identifies a channel entry in the reject cache by +// gossip version and channel ID. +type rejectCacheKey struct { + version lnwire.GossipVersion + chanID uint64 } // rejectCache is an in-memory cache used to improve the performance of @@ -51,20 +111,25 @@ type rejectCacheEntry struct { // well as the most recent timestamps for each policy (if they exists). type rejectCache struct { n int - edges map[uint64]rejectCacheEntry + edges map[rejectCacheKey]rejectCacheEntry } // newRejectCache creates a new rejectCache with maximum capacity of n entries. func newRejectCache(n int) *rejectCache { return &rejectCache{ n: n, - edges: make(map[uint64]rejectCacheEntry, n), + edges: make(map[rejectCacheKey]rejectCacheEntry, n), } } // get returns the entry from the cache for chanid, if it exists. -func (c *rejectCache) get(chanid uint64) (rejectCacheEntry, bool) { - entry, ok := c.edges[chanid] +func (c *rejectCache) get(version lnwire.GossipVersion, chanid uint64) ( + rejectCacheEntry, bool) { + + entry, ok := c.edges[rejectCacheKey{ + version: version, + chanID: chanid, + }] return entry, ok } @@ -72,10 +137,17 @@ func (c *rejectCache) get(chanid uint64) (rejectCacheEntry, bool) { // exists, it will be replaced with the new entry. If the entry doesn't exists, // it will be inserted to the cache, performing a random eviction if the cache // is at capacity. -func (c *rejectCache) insert(chanid uint64, entry rejectCacheEntry) { +func (c *rejectCache) insert(version lnwire.GossipVersion, chanid uint64, + entry rejectCacheEntry) { + + key := rejectCacheKey{ + version: version, + chanID: chanid, + } + // If entry exists, replace it. - if _, ok := c.edges[chanid]; ok { - c.edges[chanid] = entry + if _, ok := c.edges[key]; ok { + c.edges[key] = entry return } @@ -86,10 +158,13 @@ func (c *rejectCache) insert(chanid uint64, entry rejectCacheEntry) { break } } - c.edges[chanid] = entry + c.edges[key] = entry } // remove deletes an entry for chanid from the cache, if it exists. -func (c *rejectCache) remove(chanid uint64) { - delete(c.edges, chanid) +func (c *rejectCache) remove(version lnwire.GossipVersion, chanid uint64) { + delete(c.edges, rejectCacheKey{ + version: version, + chanID: chanid, + }) } diff --git a/graph/db/reject_cache_test.go b/graph/db/reject_cache_test.go index f64c39c33..c526f487b 100644 --- a/graph/db/reject_cache_test.go +++ b/graph/db/reject_cache_test.go @@ -3,6 +3,8 @@ package graphdb import ( "reflect" "testing" + + "github.com/lightningnetwork/lnd/lnwire" ) // TestRejectCache checks the behavior of the rejectCache with respect to insertion, @@ -15,14 +17,14 @@ func TestRejectCache(t *testing.T) { // As a sanity check, assert that querying the empty cache does not // return an entry. - _, ok := c.get(0) + _, ok := c.get(lnwire.GossipVersion1, 0) if ok { t.Fatalf("reject cache should be empty") } // Now, fill up the cache entirely. for i := uint64(0); i < cacheSize; i++ { - c.insert(i, entryForInt(i)) + c.insert(lnwire.GossipVersion1, i, entryForInt(i)) } // Assert that the cache has all of the entries just inserted, since no @@ -30,7 +32,10 @@ func TestRejectCache(t *testing.T) { assertHasEntries(t, c, 0, cacheSize) // Now, insert a new element that causes the cache to evict an element. - c.insert(cacheSize, entryForInt(cacheSize)) + c.insert( + lnwire.GossipVersion1, cacheSize, + entryForInt(cacheSize), + ) // Assert that the cache has this last entry, as the cache should evict // some prior element and not the newly inserted one. @@ -40,7 +45,7 @@ func TestRejectCache(t *testing.T) { // elements. evicted := make(map[uint64]struct{}) for i := uint64(0); i < cacheSize+1; i++ { - _, ok := c.get(i) + _, ok := c.get(lnwire.GossipVersion1, i) if !ok { evicted[i] = struct{}{} } @@ -54,9 +59,9 @@ func TestRejectCache(t *testing.T) { // Remove the highest item which initially caused the eviction and // reinsert the element that was evicted prior. - c.remove(cacheSize) + c.remove(lnwire.GossipVersion1, cacheSize) for i := range evicted { - c.insert(i, entryForInt(i)) + c.insert(lnwire.GossipVersion1, i, entryForInt(i)) } // Since the removal created an extra slot, the last insertion should @@ -69,7 +74,7 @@ func TestRejectCache(t *testing.T) { // happening on inserts for existing cache items, we expect this to fail // with high probability. for i := uint64(0); i < cacheSize; i++ { - c.insert(i, entryForInt(i)) + c.insert(lnwire.GossipVersion1, i, entryForInt(i)) } assertHasEntries(t, c, 0, cacheSize) @@ -82,7 +87,7 @@ func assertHasEntries(t *testing.T, c *rejectCache, start, end uint64) { t.Helper() for i := start; i < end; i++ { - entry, ok := c.get(i) + entry, ok := c.get(lnwire.GossipVersion1, i) if !ok { t.Fatalf("reject cache should contain chan %d", i) } diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 3e2d74dc2..44e2fd75b 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -50,9 +50,11 @@ type SQLQueries interface { ListNodesPaginated(ctx context.Context, arg sqlc.ListNodesPaginatedParams) ([]sqlc.GraphNode, error) ListNodeIDsAndPubKeys(ctx context.Context, arg sqlc.ListNodeIDsAndPubKeysParams) ([]sqlc.ListNodeIDsAndPubKeysRow, error) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, error) + IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) DeleteUnconnectedNodes(ctx context.Context) ([][]byte, error) DeleteNodeByPubKey(ctx context.Context, arg sqlc.DeleteNodeByPubKeyParams) (sql.Result, error) DeleteNode(ctx context.Context, id int64) error + NodeExists(ctx context.Context, arg sqlc.NodeExistsParams) (bool, error) GetExtraNodeTypes(ctx context.Context, nodeID int64) ([]sqlc.GraphNodeExtraType, error) GetNodeExtraTypesBatch(ctx context.Context, ids []int64) ([]sqlc.GraphNodeExtraType, error) @@ -81,6 +83,7 @@ type SQLQueries interface { */ CreateChannel(ctx context.Context, arg sqlc.CreateChannelParams) (int64, error) AddV1ChannelProof(ctx context.Context, arg sqlc.AddV1ChannelProofParams) (sql.Result, error) + AddV2ChannelProof(ctx context.Context, arg sqlc.AddV2ChannelProofParams) (sql.Result, error) GetChannelBySCID(ctx context.Context, arg sqlc.GetChannelBySCIDParams) (sqlc.GraphChannel, error) GetChannelsBySCIDs(ctx context.Context, arg sqlc.GetChannelsBySCIDsParams) ([]sqlc.GraphChannel, error) GetChannelsByOutpoints(ctx context.Context, outpoints []string) ([]sqlc.GetChannelsByOutpointsRow, error) @@ -163,7 +166,7 @@ type BatchedSQLQueries interface { sqldb.BatchedTx[SQLQueries] } -// SQLStore is an implementation of the V1Store interface that uses a SQL +// SQLStore is an implementation of the Store interface that uses a SQL // database as the backend. type SQLStore struct { cfg *SQLStoreConfig @@ -183,9 +186,9 @@ type SQLStore struct { srcNodeMu sync.Mutex } -// A compile-time assertion to ensure that SQLStore implements the V1Store +// A compile-time assertion to ensure that SQLStore implements the Store // interface. -var _ V1Store = (*SQLStore)(nil) +var _ Store = (*SQLStore)(nil) // SQLStoreConfig holds the configuration for the SQLStore. type SQLStoreConfig struct { @@ -235,7 +238,7 @@ func NewSQLStore(cfg *SQLStoreConfig, db BatchedSQLQueries, // graph. If it is present from before, this will update that node's // information. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) AddNode(ctx context.Context, node *models.Node, opts ...batch.SchedulerOption) error { @@ -265,14 +268,16 @@ func (s *SQLStore) AddNode(ctx context.Context, // key. If the node isn't found in the database, then ErrGraphNodeNotFound is // returned. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) FetchNode(ctx context.Context, +// NOTE: part of the Store interface. +func (s *SQLStore) FetchNode(ctx context.Context, v lnwire.GossipVersion, pubKey route.Vertex) (*models.Node, error) { var node *models.Node err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { var err error - _, node, err = getNodeByPubKey(ctx, s.cfg.QueryCfg, db, pubKey) + _, node, err = getNodeByPubKey( + ctx, s.cfg.QueryCfg, db, v, pubKey, + ) return err }, sqldb.NoOpReset) @@ -283,14 +288,14 @@ func (s *SQLStore) FetchNode(ctx context.Context, return node, nil } -// HasNode determines if the graph has a vertex identified by the +// HasV1Node determines if the graph has a vertex identified by the // target node identity public key. If the node exists in the database, a // timestamp of when the data for the node was lasted updated is returned along // with a true boolean. Otherwise, an empty time.Time is returned with a false // boolean. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) HasNode(ctx context.Context, +// NOTE: part of the Store interface. +func (s *SQLStore) HasV1Node(ctx context.Context, pubKey [33]byte) (time.Time, bool, error) { var ( @@ -326,12 +331,37 @@ func (s *SQLStore) HasNode(ctx context.Context, return lastUpdate, exists, nil } +// HasNode determines if the graph has a vertex identified by the +// target node identity public key. +// +// NOTE: part of the Store interface. +func (s *SQLStore) HasNode(ctx context.Context, v lnwire.GossipVersion, + pubKey [33]byte) (bool, error) { + + var exists bool + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { + var err error + exists, err = db.NodeExists(ctx, sqlc.NodeExistsParams{ + Version: int16(v), + PubKey: pubKey[:], + }) + + return err + }, sqldb.NoOpReset) + if err != nil { + return false, fmt.Errorf("unable to check if node (%x) "+ + "exists: %w", pubKey, err) + } + + return exists, nil +} + // AddrsForNode returns all known addresses for the target node public key // that the graph DB is aware of. The returned boolean indicates if the // given node is unknown to the graph DB or not. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) AddrsForNode(ctx context.Context, +// NOTE: part of the Store interface. +func (s *SQLStore) AddrsForNode(ctx context.Context, v lnwire.GossipVersion, nodePub *btcec.PublicKey) (bool, []net.Addr, error) { var ( @@ -343,7 +373,7 @@ func (s *SQLStore) AddrsForNode(ctx context.Context, // does. dbID, err := db.GetNodeIDByPubKey( ctx, sqlc.GetNodeIDByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: nodePub.SerializeCompressed(), }, ) @@ -372,14 +402,14 @@ func (s *SQLStore) AddrsForNode(ctx context.Context, // DeleteNode starts a new database transaction to remove a vertex/node // from the database according to the node's public key. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) DeleteNode(ctx context.Context, +// NOTE: part of the Store interface. +func (s *SQLStore) DeleteNode(ctx context.Context, v lnwire.GossipVersion, pubKey route.Vertex) error { err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { res, err := db.DeleteNodeByPubKey( ctx, sqlc.DeleteNodeByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: pubKey[:], }, ) @@ -411,19 +441,19 @@ func (s *SQLStore) DeleteNode(ctx context.Context, // known for the node, an empty feature vector is returned. // // NOTE: this is part of the graphdb.NodeTraverser interface. -func (s *SQLStore) FetchNodeFeatures(nodePub route.Vertex) ( - *lnwire.FeatureVector, error) { +func (s *SQLStore) FetchNodeFeatures(v lnwire.GossipVersion, + nodePub route.Vertex) (*lnwire.FeatureVector, error) { ctx := context.TODO() - return fetchNodeFeatures(ctx, s.db, nodePub) + return fetchNodeFeatures(ctx, s.db, v, nodePub) } // DisabledChannelIDs returns the channel ids of disabled channels. // A channel is disabled when two of the associated ChanelEdgePolicies // have their disabled bit on. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) DisabledChannelIDs() ([]uint64, error) { var ( ctx = context.TODO() @@ -450,15 +480,15 @@ func (s *SQLStore) DisabledChannelIDs() ([]uint64, error) { // LookupAlias attempts to return the alias as advertised by the target node. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) LookupAlias(ctx context.Context, +// NOTE: part of the Store interface. +func (s *SQLStore) LookupAlias(ctx context.Context, v lnwire.GossipVersion, pub *btcec.PublicKey) (string, error) { var alias string err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: pub.SerializeCompressed(), }, ) @@ -488,21 +518,21 @@ func (s *SQLStore) LookupAlias(ctx context.Context, // a path finding algorithm in order to explore the reachability of another // node based off the source node. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) SourceNode(ctx context.Context) (*models.Node, - error) { +// NOTE: part of the Store interface. +func (s *SQLStore) SourceNode(ctx context.Context, + v lnwire.GossipVersion) (*models.Node, error) { var node *models.Node err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - _, nodePub, err := s.getSourceNode( - ctx, db, lnwire.GossipVersion1, - ) + _, nodePub, err := s.getSourceNode(ctx, db, v) if err != nil { - return fmt.Errorf("unable to fetch V1 source node: %w", + return fmt.Errorf("unable to fetch source node: %w", err) } - _, node, err = getNodeByPubKey(ctx, s.cfg.QueryCfg, db, nodePub) + _, node, err = getNodeByPubKey( + ctx, s.cfg.QueryCfg, db, v, nodePub, + ) return err }, sqldb.NoOpReset) @@ -517,7 +547,7 @@ func (s *SQLStore) SourceNode(ctx context.Context) (*models.Node, // node is to be used as the center of a star-graph within path finding // algorithms. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) SetSourceNode(ctx context.Context, node *models.Node) error { @@ -538,7 +568,7 @@ func (s *SQLStore) SetSourceNode(ctx context.Context, // Make sure that if a source node for this version is already // set, then the ID is the same as the one we are about to set. dbSourceNodeID, _, err := s.getSourceNode( - ctx, db, lnwire.GossipVersion1, + ctx, db, node.Version, ) if err != nil && !errors.Is(err, ErrSourceNodeNotSet) { return fmt.Errorf("unable to fetch source node: %w", @@ -562,7 +592,7 @@ func (s *SQLStore) SetSourceNode(ctx context.Context, // nodes to quickly determine if they have the same set of up to date node // announcements. // -// NOTE: This is part of the V1Store interface. +// NOTE: This is part of the Store interface. func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, opts ...IteratorOption) iter.Seq2[*models.Node, error] { @@ -671,10 +701,15 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time, // supports. The chanPoint and chanID are used to uniquely identify the edge // globally within the database. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) AddChannelEdge(ctx context.Context, edge *models.ChannelEdgeInfo, opts ...batch.SchedulerOption) error { + if !isKnownGossipVersion(edge.Version) { + return fmt.Errorf("unsupported gossip version: %d", + edge.Version) + } + var alreadyExists bool r := &batch.Request[SQLQueries]{ Opts: batch.NewSchedulerOptions(opts...), @@ -692,7 +727,7 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, _, err := tx.GetChannelBySCID( ctx, sqlc.GetChannelBySCIDParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(edge.Version), }, ) if err == nil { @@ -712,8 +747,11 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, case alreadyExists: return ErrEdgeAlreadyExist default: - s.rejectCache.remove(edge.ChannelID) + s.rejectCache.remove( + edge.Version, edge.ChannelID, + ) s.chanCache.remove(edge.ChannelID) + return nil } }, @@ -726,7 +764,7 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context, // This represents the "newest" channel from the PoV of the chain. This method // can be used by peers to quickly determine if their graphs are in sync. // -// NOTE: This is part of the V1Store interface. +// NOTE: This is part of the Store interface. func (s *SQLStore) HighestChanID(ctx context.Context) (uint64, error) { var highestChanID uint64 err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { @@ -757,7 +795,7 @@ func (s *SQLStore) HighestChanID(ctx context.Context) (uint64, error) { // determined by the lexicographical ordering of the identity public keys of the // nodes on either side of the channel. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) UpdateEdgePolicy(ctx context.Context, edge *models.ChannelEdgePolicy, opts ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { @@ -827,13 +865,18 @@ func (s *SQLStore) updateEdgeCache(e *models.ChannelEdgePolicy, // the entry with the updated timestamp for the direction that was just // written. If the edge doesn't exist, we'll load the cache entry lazily // during the next query for this edge. - if entry, ok := s.rejectCache.get(e.ChannelID); ok { - if isUpdate1 { - entry.upd1Time = e.LastUpdate.Unix() - } else { - entry.upd2Time = e.LastUpdate.Unix() + if entry, ok := s.rejectCache.get(e.Version, e.ChannelID); ok { + switch e.Version { + case lnwire.GossipVersion1: + updateRejectCacheEntryV1( + &entry, isUpdate1, e.LastUpdate, + ) + case lnwire.GossipVersion2: + updateRejectCacheEntryV2( + &entry, isUpdate1, e.LastBlockHeight, + ) } - s.rejectCache.insert(e.ChannelID, entry) + s.rejectCache.insert(e.Version, e.ChannelID, entry) } // If an entry for this channel is found in channel cache, we'll modify @@ -855,7 +898,7 @@ func (s *SQLStore) updateEdgeCache(e *models.ChannelEdgePolicy, // channel's outpoint, whether we have a policy for the channel and the channel // peer's node information. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, cb func(chanPoint wire.OutPoint, havePolicy bool, otherNode *models.Node) error, reset func()) error { @@ -870,7 +913,7 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, } return forEachNodeChannel( - ctx, db, s.cfg, nodeID, + ctx, db, s.cfg, lnwire.GossipVersion1, nodeID, func(info *models.ChannelEdgeInfo, outPolicy *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -892,7 +935,8 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, } _, otherNode, err := getNodeByPubKey( - ctx, s.cfg.QueryCfg, db, otherNodePub, + ctx, s.cfg.QueryCfg, db, + lnwire.GossipVersion1, otherNodePub, ) if err != nil { return fmt.Errorf("unable to fetch "+ @@ -914,7 +958,7 @@ func (s *SQLStore) ForEachSourceNodeChannel(ctx context.Context, // returns an error, then the transaction is aborted and the iteration stops // early. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ForEachNode(ctx context.Context, cb func(node *models.Node) error, reset func()) error { @@ -982,15 +1026,16 @@ func (s *SQLStore) ForEachNodeCacheable(ctx context.Context, // // Unknown policies are passed into the callback as nil values. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, +// NOTE: part of the Store interface. +func (s *SQLStore) ForEachNodeChannel(ctx context.Context, + v lnwire.GossipVersion, nodePub route.Vertex, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, reset func()) error { return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: nodePub[:], }, ) @@ -1000,7 +1045,7 @@ func (s *SQLStore) ForEachNodeChannel(ctx context.Context, nodePub route.Vertex, return fmt.Errorf("unable to fetch node: %w", err) } - return forEachNodeChannel(ctx, db, s.cfg, dbNode.ID, cb) + return forEachNodeChannel(ctx, db, s.cfg, v, dbNode.ID, cb) }, reset) } @@ -1099,7 +1144,7 @@ func (s *SQLStore) updateChanCacheBatch(edgesToCache map[uint64]ChannelEdge) { // 5. Update cache after successful batch // 6. Repeat with updated pagination cursor until no more results // -// NOTE: This is part of the V1Store interface. +// NOTE: This is part of the Store interface. func (s *SQLStore) ChanUpdatesInHorizon(startTime, endTime time.Time, opts ...IteratorOption) iter.Seq2[ChannelEdge, error] { @@ -1268,7 +1313,7 @@ func (s *SQLStore) ChanUpdatesInHorizon(startTime, endTime time.Time, // result in an additional round-trip to the database, so it should only be used // if the addresses are actually needed. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ForEachNodeCached(ctx context.Context, withAddrs bool, cb func(ctx context.Context, node route.Vertex, addrs []net.Addr, chans map[uint64]*DirectedChannel) error, reset func()) error { @@ -1564,13 +1609,18 @@ func (s *SQLStore) ForEachChannelCacheable(cb func(*models.CachedEdgeInfo, // for that particular channel edge routing policy will be passed into the // callback. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ForEachChannel(ctx context.Context, - cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error, reset func()) error { + v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error, + reset func()) error { + + if !isKnownGossipVersion(v) { + return fmt.Errorf("unsupported gossip version: %d", v) + } return s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { - return forEachChannelWithPolicies(ctx, db, s.cfg, cb) + return forEachChannelWithPolicies(ctx, db, s.cfg, v, cb) }, reset) } @@ -1582,7 +1632,7 @@ func (s *SQLStore) ForEachChannel(ctx context.Context, // timestamp info of the latest received channel update messages of the channel // will be included in the response. // -// NOTE: This is part of the V1Store interface. +// NOTE: This is part of the Store interface. func (s *SQLStore) FilterChannelRange(startHeight, endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) { @@ -1702,7 +1752,7 @@ func (s *SQLStore) FilterChannelRange(startHeight, endHeight uint32, // zombie. This method is used on an ad-hoc basis, when channels need to be // marked as zombies outside the normal pruning cycle. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) MarkEdgeZombie(chanID uint64, pubKey1, pubKey2 [33]byte) error { @@ -1728,7 +1778,7 @@ func (s *SQLStore) MarkEdgeZombie(chanID uint64, "(channel_id=%d): %w", chanID, err) } - s.rejectCache.remove(chanID) + s.rejectCache.remove(lnwire.GossipVersion1, chanID) s.chanCache.remove(chanID) return nil @@ -1736,7 +1786,7 @@ func (s *SQLStore) MarkEdgeZombie(chanID uint64, // MarkEdgeLive clears an edge from our zombie index, deeming it as live. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) MarkEdgeLive(chanID uint64) error { s.cacheMu.Lock() defer s.cacheMu.Unlock() @@ -1777,7 +1827,7 @@ func (s *SQLStore) MarkEdgeLive(chanID uint64) error { "(channel_id=%d): %w", chanID, err) } - s.rejectCache.remove(chanID) + s.rejectCache.remove(lnwire.GossipVersion1, chanID) s.chanCache.remove(chanID) return err @@ -1787,9 +1837,9 @@ func (s *SQLStore) MarkEdgeLive(chanID uint64) error { // zombie, then the two node public keys corresponding to this edge are also // returned. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, - error) { +// NOTE: part of the Store interface. +func (s *SQLStore) IsZombieEdge(v lnwire.GossipVersion, + chanID uint64) (bool, [33]byte, [33]byte, error) { var ( ctx = context.TODO() @@ -1798,11 +1848,16 @@ func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, chanIDB = channelIDToBytes(chanID) ) + if !isKnownGossipVersion(v) { + return false, [33]byte{}, [33]byte{}, + fmt.Errorf("unsupported gossip version: %d", v) + } + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { zombie, err := db.GetZombieChannel( ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -1830,7 +1885,7 @@ func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, // NumZombies returns the current number of zombie channels in the graph. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) NumZombies() (uint64, error) { var ( ctx = context.TODO() @@ -1865,9 +1920,10 @@ func (s *SQLStore) NumZombies() (uint64, error) { // that resurrects the channel from its zombie state. The markZombie bool // denotes whether to mark the channel as a zombie. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, - chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) { +// NOTE: part of the Store interface. +func (s *SQLStore) DeleteChannelEdges(v lnwire.GossipVersion, + strictZombiePruning, markZombie bool, chanIDs ...uint64) ( + []*models.ChannelEdgeInfo, error) { s.cacheMu.Lock() defer s.cacheMu.Unlock() @@ -1900,7 +1956,7 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, } err := s.forEachChanWithPoliciesInSCIDList( - ctx, db, chanCallBack, chanIDs, + ctx, db, v, chanCallBack, chanIDs, ) if err != nil { return err @@ -1928,7 +1984,7 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, scid := byteOrder.Uint64(row.GraphChannel.Scid) err := handleZombieMarking( - ctx, db, row, edges[i], + ctx, db, v, row, edges[i], strictZombiePruning, scid, ) if err != nil { @@ -1953,7 +2009,7 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, } for _, chanID := range chanIDs { - s.rejectCache.remove(chanID) + s.rejectCache.remove(v, chanID) s.chanCache.remove(chanID) } @@ -1970,9 +2026,9 @@ func (s *SQLStore) DeleteChannelEdges(strictZombiePruning, markZombie bool, // within the database. In this case, the ChannelEdgePolicy's will be nil, and // the ChannelEdgeInfo will only include the public keys of each node. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, +// NOTE: part of the Store interface. +func (s *SQLStore) FetchChannelEdgesByID(v lnwire.GossipVersion, + chanID uint64) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { var ( @@ -1981,11 +2037,18 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( policy1, policy2 *models.ChannelEdgePolicy chanIDB = channelIDToBytes(chanID) ) + + if !isKnownGossipVersion(v) { + return nil, nil, nil, fmt.Errorf( + "unsupported gossip version: %d", v, + ) + } + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { row, err := db.GetChannelBySCIDWithPolicies( ctx, sqlc.GetChannelBySCIDWithPoliciesParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -1994,7 +2057,7 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( zombie, err := db.GetZombieChannel( ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2009,9 +2072,22 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( // populate the edge info with the public keys of each // party as this is the only information we have about // it. - edge = &models.ChannelEdgeInfo{} - copy(edge.NodeKey1Bytes[:], zombie.NodeKey1) - copy(edge.NodeKey2Bytes[:], zombie.NodeKey2) + node1, err := route.NewVertexFromBytes(zombie.NodeKey1) + if err != nil { + return err + } + node2, err := route.NewVertexFromBytes(zombie.NodeKey2) + if err != nil { + return err + } + zombieEdge, err := models.NewV1Channel( + 0, chainhash.Hash{}, node1, node2, + &models.ChannelV1Fields{}, + ) + if err != nil { + return err + } + edge = zombieEdge return ErrZombieEdge } else if err != nil { @@ -2067,9 +2143,9 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( // information for the channel itself is returned as well as two structs that // contain the routing policies for the channel in either direction. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, +// NOTE: part of the Store interface. +func (s *SQLStore) FetchChannelEdgesByOutpoint(v lnwire.GossipVersion, + op *wire.OutPoint) (*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error) { var ( @@ -2077,11 +2153,18 @@ func (s *SQLStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( edge *models.ChannelEdgeInfo policy1, policy2 *models.ChannelEdgePolicy ) + + if !isKnownGossipVersion(v) { + return nil, nil, nil, fmt.Errorf( + "unsupported gossip version: %d", v, + ) + } + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { row, err := db.GetChannelByOutpointWithPolicies( ctx, sqlc.GetChannelByOutpointWithPoliciesParams{ Outpoint: op.String(), - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -2130,15 +2213,15 @@ func (s *SQLStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( return edge, policy1, policy2, nil } -// HasChannelEdge returns true if the database knows of a channel edge with the -// passed channel ID, and false otherwise. If an edge with that ID is found -// within the graph, then two time stamps representing the last time the edge -// was updated for both directed edges are returned along with the boolean. If -// it is not found, then the zombie index is checked and its result is returned -// as the second boolean. +// HasV1ChannelEdge returns true if the database knows of a channel edge +// with the passed channel ID, and false otherwise. If an edge with that ID +// is found within the graph, then two time stamps representing the last time +// the edge was updated for both directed edges are returned along with the +// boolean. If it is not found, then the zombie index is checked and its +// result is returned as the second boolean. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, +// NOTE: part of the Store interface. +func (s *SQLStore) HasV1ChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, error) { ctx := context.TODO() @@ -2153,7 +2236,7 @@ func (s *SQLStore) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, // We'll query the cache with the shared lock held to allow multiple // readers to access values in the cache concurrently if they exist. s.cacheMu.RLock() - if entry, ok := s.rejectCache.get(chanID); ok { + if entry, ok := s.rejectCache.get(lnwire.GossipVersion1, chanID); ok { s.cacheMu.RUnlock() node1LastUpdate = time.Unix(entry.upd1Time, 0) node2LastUpdate = time.Unix(entry.upd2Time, 0) @@ -2169,7 +2252,7 @@ func (s *SQLStore) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, // The item was not found with the shared lock, so we'll acquire the // exclusive lock and check the cache again in case another method added // the entry to the cache while no lock was held. - if entry, ok := s.rejectCache.get(chanID); ok { + if entry, ok := s.rejectCache.get(lnwire.GossipVersion1, chanID); ok { node1LastUpdate = time.Unix(entry.upd1Time, 0) node2LastUpdate = time.Unix(entry.upd2Time, 0) exists, isZombie = entry.flags.unpack() @@ -2240,20 +2323,174 @@ func (s *SQLStore) HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, fmt.Errorf("unable to fetch channel: %w", err) } - s.rejectCache.insert(chanID, rejectCacheEntry{ - upd1Time: node1LastUpdate.Unix(), - upd2Time: node2LastUpdate.Unix(), - flags: packRejectFlags(exists, isZombie), - }) + s.rejectCache.insert( + lnwire.GossipVersion1, chanID, + newRejectCacheEntryV1( + node1LastUpdate, node2LastUpdate, exists, + isZombie, + ), + ) return node1LastUpdate, node2LastUpdate, exists, isZombie, nil } +// HasChannelEdge returns true if the database knows of a channel edge with the +// passed channel ID and gossip version, and false otherwise. If an edge with +// that ID is found within the graph, then the zombie index is checked and its +// result is returned as the second boolean. +// +// NOTE: part of the Store interface. +func (s *SQLStore) HasChannelEdge(v lnwire.GossipVersion, + chanID uint64) (bool, bool, error) { + + if !isKnownGossipVersion(v) { + return false, false, fmt.Errorf( + "unsupported gossip version: %d", v, + ) + } + + ctx := context.TODO() + + var ( + exists bool + isZombie bool + node1LastUpdate time.Time + node2LastUpdate time.Time + node1Block uint32 + node2Block uint32 + ) + + // We'll query the cache with the shared lock held to allow multiple + // readers to access values in the cache concurrently if they exist. + s.cacheMu.RLock() + if entry, ok := s.rejectCache.get(v, chanID); ok { + s.cacheMu.RUnlock() + exists, isZombie = entry.flags.unpack() + return exists, isZombie, nil + } + s.cacheMu.RUnlock() + + s.cacheMu.Lock() + defer s.cacheMu.Unlock() + + // The item was not found with the shared lock, so we'll acquire the + // exclusive lock and check the cache again in case another method added + // the entry to the cache while no lock was held. + if entry, ok := s.rejectCache.get(v, chanID); ok { + exists, isZombie = entry.flags.unpack() + return exists, isZombie, nil + } + + chanIDB := channelIDToBytes(chanID) + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { + channel, err := db.GetChannelBySCID( + ctx, sqlc.GetChannelBySCIDParams{ + Scid: chanIDB, + Version: int16(v), + }, + ) + if errors.Is(err, sql.ErrNoRows) { + // Check if it is a zombie channel. + isZombie, err = db.IsZombieChannel( + ctx, sqlc.IsZombieChannelParams{ + Scid: chanIDB, + Version: int16(v), + }, + ) + if err != nil { + return fmt.Errorf("could not check if channel "+ + "is zombie: %w", err) + } + + return nil + } else if err != nil { + return fmt.Errorf("unable to fetch channel: %w", err) + } + + exists = true + + policy1, err := db.GetChannelPolicyByChannelAndNode( + ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ + Version: int16(v), + ChannelID: channel.ID, + NodeID: channel.NodeID1, + }, + ) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("unable to fetch channel policy: %w", + err) + } else if err == nil { + switch v { + case lnwire.GossipVersion1: + if policy1.LastUpdate.Valid { + node1LastUpdate = time.Unix( + policy1.LastUpdate.Int64, 0, + ) + } + case lnwire.GossipVersion2: + if policy1.BlockHeight.Valid { + node1Block = uint32( + policy1.BlockHeight.Int64, + ) + } + } + } + + policy2, err := db.GetChannelPolicyByChannelAndNode( + ctx, sqlc.GetChannelPolicyByChannelAndNodeParams{ + Version: int16(v), + ChannelID: channel.ID, + NodeID: channel.NodeID2, + }, + ) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("unable to fetch channel policy: %w", + err) + } else if err == nil { + switch v { + case lnwire.GossipVersion1: + if policy2.LastUpdate.Valid { + node2LastUpdate = time.Unix( + policy2.LastUpdate.Int64, 0, + ) + } + case lnwire.GossipVersion2: + if policy2.BlockHeight.Valid { + node2Block = uint32( + policy2.BlockHeight.Int64, + ) + } + } + } + + return nil + }, sqldb.NoOpReset) + if err != nil { + return false, false, + fmt.Errorf("unable to fetch channel: %w", err) + } + + var entry rejectCacheEntry + switch v { + case lnwire.GossipVersion1: + entry = newRejectCacheEntryV1( + node1LastUpdate, node2LastUpdate, exists, isZombie, + ) + case lnwire.GossipVersion2: + entry = newRejectCacheEntryV2( + node1Block, node2Block, exists, isZombie, + ) + } + s.rejectCache.insert(v, chanID, entry) + + return exists, isZombie, nil +} + // ChannelID attempt to lookup the 8-byte compact channel ID which maps to the // passed channel point (outpoint). If the passed channel doesn't exist within // the database, then ErrEdgeNotFound is returned. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { var ( ctx = context.TODO() @@ -2288,14 +2525,24 @@ func (s *SQLStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { // given public key is seen as a public node in the graph from the graph's // source node's point of view. // -// NOTE: part of the V1Store interface. -func (s *SQLStore) IsPublicNode(pubKey [33]byte) (bool, error) { +// NOTE: part of the Store interface. +func (s *SQLStore) IsPublicNode(v lnwire.GossipVersion, pubKey [33]byte) (bool, + error) { + ctx := context.TODO() + if !isKnownGossipVersion(v) { + return false, fmt.Errorf("unsupported gossip version: %d", v) + } var isPublic bool err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { var err error - isPublic, err = db.IsPublicV1Node(ctx, pubKey[:]) + switch v { + case lnwire.GossipVersion1: + isPublic, err = db.IsPublicV1Node(ctx, pubKey[:]) + case lnwire.GossipVersion2: + isPublic, err = db.IsPublicV2Node(ctx, pubKey[:]) + } return err }, sqldb.NoOpReset) @@ -2313,7 +2560,7 @@ func (s *SQLStore) IsPublicNode(pubKey [33]byte) (bool, error) { // of the query. This can be used to respond to peer queries that are seeking to // fill in gaps in their view of the channel graph. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { var ( ctx = context.TODO() @@ -2330,7 +2577,7 @@ func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { } err := s.forEachChanWithPoliciesInSCIDList( - ctx, db, chanCallBack, chanIDs, + ctx, db, lnwire.GossipVersion1, chanCallBack, chanIDs, ) if err != nil { return err @@ -2378,7 +2625,7 @@ func (s *SQLStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) { // GetChannelsBySCIDWithPolicies query that allows us to iterate through // channels in a paginated manner. func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, - db SQLQueries, cb func(ctx context.Context, + db SQLQueries, v lnwire.GossipVersion, cb func(ctx context.Context, row sqlc.GetChannelsBySCIDWithPoliciesRow) error, chanIDs []uint64) error { @@ -2388,7 +2635,7 @@ func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, return db.GetChannelsBySCIDWithPolicies( ctx, sqlc.GetChannelsBySCIDWithPoliciesParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), Scids: scids, }, ) @@ -2407,7 +2654,7 @@ func (s *SQLStore) forEachChanWithPoliciesInSCIDList(ctx context.Context, // channels another peer knows of that we don't. The ChannelUpdateInfos for the // known zombies is also returned. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64, []ChannelUpdateInfo, error) { @@ -2531,7 +2778,7 @@ func (s *SQLStore) forEachChanInSCIDList(ctx context.Context, db SQLQueries, // NOTE: this prunes nodes across protocol versions. It will never prune the // source nodes. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) PruneGraphNodes() ([]route.Vertex, error) { var ctx = context.TODO() @@ -2560,7 +2807,7 @@ func (s *SQLStore) PruneGraphNodes() ([]route.Vertex, error) { // the target block along with any pruned nodes are returned if the function // succeeds without error. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, []route.Vertex, error) { @@ -2653,7 +2900,7 @@ func (s *SQLStore) PruneGraph(spentOutputs []*wire.OutPoint, } for _, channel := range closedChans { - s.rejectCache.remove(channel.ChannelID) + s.rejectCache.remove(channel.Version, channel.ChannelID) s.chanCache.remove(channel.ChannelID) } @@ -2715,7 +2962,7 @@ func (s *SQLStore) deleteChannels(ctx context.Context, db SQLQueries, // returned are the ones that need to be watched on chain to detect channel // closes on the resident blockchain. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) ChannelView() ([]EdgePoint, error) { var ( ctx = context.TODO() @@ -2726,6 +2973,7 @@ func (s *SQLStore) ChannelView() ([]EdgePoint, error) { handleChannel := func(_ context.Context, channel sqlc.ListChannelsPaginatedRow) error { + // TODO(elle): update to handle V2 channels. pkScript, err := genMultiSigP2WSH( channel.BitcoinKey1, channel.BitcoinKey2, ) @@ -2781,7 +3029,7 @@ func (s *SQLStore) ChannelView() ([]EdgePoint, error) { // to tell if the graph is currently in sync with the current best known UTXO // state. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) PruneTip() (*chainhash.Hash, uint32, error) { var ( ctx = context.TODO() @@ -2843,7 +3091,7 @@ func (s *SQLStore) pruneGraphNodes(ctx context.Context, // Channels that were removed from the graph resulting from the // disconnected block are returned. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( []*models.ChannelEdgeInfo, error) { @@ -2921,7 +3169,7 @@ func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( s.cacheMu.Lock() for _, channel := range removedChans { - s.rejectCache.remove(channel.ChannelID) + s.rejectCache.remove(channel.Version, channel.ChannelID) s.chanCache.remove(channel.ChannelID) } s.cacheMu.Unlock() @@ -2931,25 +3179,49 @@ func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( // AddEdgeProof sets the proof of an existing edge in the graph database. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) AddEdgeProof(scid lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { + if !isKnownGossipVersion(proof.Version) { + return fmt.Errorf("unsupported gossip version: %d", + proof.Version) + } + var ( ctx = context.TODO() scidBytes = channelIDToBytes(scid.ToUint64()) ) err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error { - res, err := db.AddV1ChannelProof( - ctx, sqlc.AddV1ChannelProofParams{ - Scid: scidBytes, - Node1Signature: proof.NodeSig1Bytes, - Node2Signature: proof.NodeSig2Bytes, - Bitcoin1Signature: proof.BitcoinSig1Bytes, - Bitcoin2Signature: proof.BitcoinSig2Bytes, - }, + var ( + res sql.Result + err error ) + switch proof.Version { + case lnwire.GossipVersion1: + res, err = db.AddV1ChannelProof( + ctx, sqlc.AddV1ChannelProofParams{ + Scid: scidBytes, + Node1Signature: proof.NodeSig1(), + Node2Signature: proof.NodeSig2(), + Bitcoin1Signature: proof.BitcoinSig1(), + Bitcoin2Signature: proof.BitcoinSig2(), + }, + ) + + case lnwire.GossipVersion2: + res, err = db.AddV2ChannelProof( + ctx, sqlc.AddV2ChannelProofParams{ + Scid: scidBytes, + Signature: proof.Sig(), + }, + ) + + default: + return fmt.Errorf("unsupported gossip version: %d", + proof.Version) + } if err != nil { return fmt.Errorf("unable to add edge proof: %w", err) } @@ -2981,7 +3253,7 @@ func (s *SQLStore) AddEdgeProof(scid lnwire.ShortChannelID, // that we can ignore channel announcements that we know to be closed without // having to validate them and fetch a block. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) PutClosedScid(scid lnwire.ShortChannelID) error { var ( ctx = context.TODO() @@ -2996,7 +3268,7 @@ func (s *SQLStore) PutClosedScid(scid lnwire.ShortChannelID) error { // IsClosedScid checks whether a channel identified by the passed in scid is // closed. This helps avoid having to perform expensive validation checks. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { var ( ctx = context.TODO() @@ -3024,7 +3296,7 @@ func (s *SQLStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) { // GraphSession will provide the call-back with access to a NodeTraverser // instance which can be used to perform queries against the channel graph. // -// NOTE: part of the V1Store interface. +// NOTE: part of the Store interface. func (s *SQLStore) GraphSession(cb func(graph NodeTraverser) error, reset func()) error { @@ -3077,7 +3349,7 @@ func (s *sqlNodeTraverser) FetchNodeFeatures(nodePub route.Vertex) ( ctx := context.TODO() - return fetchNodeFeatures(ctx, s.db, nodePub) + return fetchNodeFeatures(ctx, s.db, lnwire.GossipVersion1, nodePub) } // forEachNodeDirectedChannel iterates through all channels of a given @@ -3253,14 +3525,15 @@ func forEachNodeCacheable(ctx context.Context, cfg *sqldb.QueryConfig, // edge information, the outgoing policy and the incoming policy for the // channel and node combo. func forEachNodeChannel(ctx context.Context, db SQLQueries, - cfg *SQLStoreConfig, id int64, cb func(*models.ChannelEdgeInfo, + cfg *SQLStoreConfig, v lnwire.GossipVersion, id int64, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { - // Get all the V1 channels for this node. + // Get all the channels for this node. rows, err := db.ListChannelsByNodeID( ctx, sqlc.ListChannelsByNodeIDParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), NodeID1: id, }, ) @@ -3351,10 +3624,16 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, var ( node1Pub, node2Pub route.Vertex - isNode1 bool chanIDB = channelIDToBytes(edge.ChannelID) + version = edge.Version ) + if !isKnownGossipVersion(version) { + return node1Pub, node2Pub, false, fmt.Errorf( + "unsupported gossip version: %d", version, + ) + } + // Check that this edge policy refers to a channel that we already // know of. We do this explicitly so that we can return the appropriate // ErrEdgeNotFound error if the channel doesn't exist, rather than @@ -3362,7 +3641,7 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, dbChan, err := tx.GetChannelAndNodesBySCID( ctx, sqlc.GetChannelAndNodesBySCIDParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(version), }, ) if errors.Is(err, sql.ErrNoRows) { @@ -3376,7 +3655,7 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, copy(node2Pub[:], dbChan.Node2PubKey) // Figure out which node this edge is from. - isNode1 = edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 + isNode1 := edge.IsNode1() nodeID := dbChan.NodeID1 if !isNode1 { nodeID = dbChan.NodeID2 @@ -3391,29 +3670,40 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, inboundBase = sqldb.SQLInt64(fee.BaseFee) }) - id, err := tx.UpsertEdgePolicy(ctx, sqlc.UpsertEdgePolicyParams{ - Version: int16(lnwire.GossipVersion1), - ChannelID: dbChan.ID, - NodeID: nodeID, - Timelock: int32(edge.TimeLockDelta), - FeePpm: int64(edge.FeeProportionalMillionths), - BaseFeeMsat: int64(edge.FeeBaseMSat), - MinHtlcMsat: int64(edge.MinHTLC), - LastUpdate: sqldb.SQLInt64(edge.LastUpdate.Unix()), - Disabled: sql.NullBool{ - Valid: true, - Bool: edge.IsDisabled(), - }, - MaxHtlcMsat: sql.NullInt64{ - Valid: edge.MessageFlags.HasMaxHtlc(), - Int64: int64(edge.MaxHTLC), - }, + params := sqlc.UpsertEdgePolicyParams{ + Version: int16(version), + ChannelID: dbChan.ID, + NodeID: nodeID, + Timelock: int32(edge.TimeLockDelta), + FeePpm: int64(edge.FeeProportionalMillionths), + BaseFeeMsat: int64(edge.FeeBaseMSat), + MinHtlcMsat: int64(edge.MinHTLC), MessageFlags: sqldb.SQLInt16(edge.MessageFlags), ChannelFlags: sqldb.SQLInt16(edge.ChannelFlags), InboundBaseFeeMsat: inboundBase, InboundFeeRateMilliMsat: inboundRate, Signature: edge.SigBytes, - }) + } + + if version == lnwire.GossipVersion1 { + params.LastUpdate = sqldb.SQLInt64(edge.LastUpdate.Unix()) + params.Disabled = sql.NullBool{ + Valid: true, + Bool: edge.IsDisabled(), + } + params.MaxHtlcMsat = sql.NullInt64{ + Valid: edge.MessageFlags.HasMaxHtlc(), + Int64: int64(edge.MaxHTLC), + } + } else { + params.BlockHeight = sqldb.SQLInt64( + int64(edge.LastBlockHeight), + ) + params.DisableFlags = sqldb.SQLInt16(edge.DisableFlags) + params.MaxHtlcMsat = sqldb.SQLInt64(int64(edge.MaxHTLC)) + } + + id, err := tx.UpsertEdgePolicy(ctx, params) if err != nil { return node1Pub, node2Pub, isNode1, fmt.Errorf("unable to upsert edge policy: %w", err) @@ -3421,10 +3711,14 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, // Convert the flat extra opaque data into a map of TLV types to // values. - extra, err := marshalExtraOpaqueData(edge.ExtraOpaqueData) - if err != nil { - return node1Pub, node2Pub, false, fmt.Errorf("unable to "+ - "marshal extra opaque data: %w", err) + extra := edge.ExtraSignedFields + if version == lnwire.GossipVersion1 { + extra, err = marshalExtraOpaqueData(edge.ExtraOpaqueData) + if err != nil { + return node1Pub, node2Pub, false, fmt.Errorf( + "unable to marshal extra opaque data: %w", err, + ) + } } // Update the channel policy's extra signed fields. @@ -3439,11 +3733,12 @@ func updateChanEdgePolicy(ctx context.Context, tx SQLQueries, // getNodeByPubKey attempts to look up a target node by its public key. func getNodeByPubKey(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, - pubKey route.Vertex) (int64, *models.Node, error) { + v lnwire.GossipVersion, pubKey route.Vertex) (int64, *models.Node, + error) { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: pubKey[:], }, ) @@ -3489,6 +3784,19 @@ func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, return buildNodeWithBatchData(dbNode, data) } +// isKnownGossipVersion checks whether the provided gossip version is known +// and supported. +func isKnownGossipVersion(v lnwire.GossipVersion) bool { + switch v { + case lnwire.GossipVersion1: + return true + case lnwire.GossipVersion2: + return true + default: + return false + } +} + // buildNodeWithBatchData builds a models.Node instance // from the provided sqlc.GraphNode and batchNodeData. If the node does have // features/addresses/extra fields, then the corresponding fields are expected @@ -3496,15 +3804,18 @@ func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries, func buildNodeWithBatchData(dbNode sqlc.GraphNode, batchData *batchNodeData) (*models.Node, error) { - if dbNode.Version != int16(lnwire.GossipVersion1) { - return nil, fmt.Errorf("unsupported node version: %d", - dbNode.Version) + v := lnwire.GossipVersion(dbNode.Version) + + if !isKnownGossipVersion(v) { + return nil, fmt.Errorf("unknown node version: %d", v) } - var pub [33]byte - copy(pub[:], dbNode.PubKey) + pub, err := route.NewVertexFromBytes(dbNode.PubKey) + if err != nil { + return nil, fmt.Errorf("unable to parse pubkey: %w", err) + } - node := models.NewV1ShellNode(pub) + node := models.NewShellNode(v, pub) if len(dbNode.Signature) == 0 { return node, nil @@ -3518,8 +3829,10 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode, if dbNode.LastUpdate.Valid { node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0) } + if dbNode.BlockHeight.Valid { + node.LastBlockHeight = uint32(dbNode.BlockHeight.Int64) + } - var err error if dbNode.Color.Valid { nodeColor, err := DecodeHexColor(dbNode.Color.String) if err != nil { @@ -3551,13 +3864,19 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode, // Use preloaded extra fields. if extraFields, exists := batchData.extraFields[dbNode.ID]; exists { - recs, err := lnwire.CustomRecords(extraFields).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra "+ - "signed fields: %w", err) - } - if len(recs) != 0 { - node.ExtraOpaqueData = recs + if v == lnwire.GossipVersion1 { + records := lnwire.CustomRecords(extraFields) + recs, err := records.Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize "+ + "extra signed fields: %w", err) + } + + if len(recs) != 0 { + node.ExtraOpaqueData = recs + } + } else if len(extraFields) > 0 { + node.ExtraSignedFields = extraFields } } @@ -3637,10 +3956,13 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries, // Convert the flat extra opaque data into a map of TLV types to // values. - extra, err := marshalExtraOpaqueData(node.ExtraOpaqueData) - if err != nil { - return fmt.Errorf("unable to marshal extra opaque data: %w", - err) + extra := node.ExtraSignedFields + if node.Version == lnwire.GossipVersion1 { + extra, err = marshalExtraOpaqueData(node.ExtraOpaqueData) + if err != nil { + return fmt.Errorf("unable to marshal extra opaque "+ + "data: %w", err) + } } // Update the node's extra signed fields. @@ -3655,34 +3977,39 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries, // populateNodeParams populates the common node parameters from a models.Node. // This is a helper for building UpsertNodeParams and UpsertSourceNodeParams. func populateNodeParams(node *models.Node, - setParams func(lastUpdate sql.NullInt64, alias, + setParams func(lastUpdate, lastBlockHeight sql.NullInt64, alias, colorStr sql.NullString, signature []byte)) error { if !node.HaveAnnouncement() { return nil } + var ( + alias, colorStr sql.NullString + lastUpdate, lastBlockHeight sql.NullInt64 + ) + node.Color.WhenSome(func(rgba color.RGBA) { + colorStr = sqldb.SQLStrValid(EncodeHexColor(rgba)) + }) + node.Alias.WhenSome(func(s string) { + alias = sqldb.SQLStrValid(s) + }) + switch node.Version { case lnwire.GossipVersion1: - lastUpdate := sqldb.SQLInt64(node.LastUpdate.Unix()) - var alias, colorStr sql.NullString - - node.Color.WhenSome(func(rgba color.RGBA) { - colorStr = sqldb.SQLStrValid(EncodeHexColor(rgba)) - }) - node.Alias.WhenSome(func(s string) { - alias = sqldb.SQLStrValid(s) - }) - - setParams(lastUpdate, alias, colorStr, node.AuthSigBytes) + lastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix()) case lnwire.GossipVersion2: - // No-op for now. + lastBlockHeight = sqldb.SQLInt64(int64(node.LastBlockHeight)) default: return fmt.Errorf("unknown gossip version: %d", node.Version) } + setParams( + lastUpdate, lastBlockHeight, alias, colorStr, node.AuthSigBytes, + ) + return nil } @@ -3690,20 +4017,22 @@ func populateNodeParams(node *models.Node, // strict UpsertNode query (requires timestamp to be increasing). func buildNodeUpsertParams(node *models.Node) (sqlc.UpsertNodeParams, error) { params := sqlc.UpsertNodeParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(node.Version), PubKey: node.PubKeyBytes[:], } err := populateNodeParams( - node, func(lastUpdate sql.NullInt64, alias, + node, func(lastUpdate, lastBlockHeight sql.NullInt64, alias, colorStr sql.NullString, signature []byte) { params.LastUpdate = lastUpdate + params.BlockHeight = lastBlockHeight params.Alias = alias params.Color = colorStr params.Signature = signature - }) + }, + ) return params, err } @@ -3714,14 +4043,15 @@ func buildSourceNodeUpsertParams(node *models.Node) ( sqlc.UpsertSourceNodeParams, error) { params := sqlc.UpsertSourceNodeParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(node.Version), PubKey: node.PubKeyBytes[:], } err := populateNodeParams( - node, func(lastUpdate sql.NullInt64, alias, + node, func(lastUpdate, lastBlock sql.NullInt64, alias, colorStr sql.NullString, signature []byte) { + params.BlockHeight = lastBlock params.LastUpdate = lastUpdate params.Alias = alias params.Color = colorStr @@ -3772,6 +4102,10 @@ func upsertSourceNode(ctx context.Context, db SQLQueries, func upsertNode(ctx context.Context, db SQLQueries, node *models.Node) (int64, error) { + if !isKnownGossipVersion(node.Version) { + return 0, fmt.Errorf("unknown gossip version: %d", node.Version) + } + params, err := buildNodeUpsertParams(node) if err != nil { return 0, err @@ -3860,12 +4194,13 @@ func upsertNodeFeatures(ctx context.Context, db SQLQueries, nodeID int64, // fetchNodeFeatures fetches the features for a node with the given public key. func fetchNodeFeatures(ctx context.Context, queries SQLQueries, - nodePub route.Vertex) (*lnwire.FeatureVector, error) { + v lnwire.GossipVersion, nodePub route.Vertex) (*lnwire.FeatureVector, + error) { rows, err := queries.GetNodeFeaturesByPubKey( ctx, sqlc.GetNodeFeaturesByPubKeyParams{ PubKey: nodePub[:], - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if err != nil { @@ -4169,14 +4504,16 @@ func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) { func insertChannel(ctx context.Context, db SQLQueries, edge *models.ChannelEdgeInfo) error { + v := edge.Version + // Make sure that at least a "shell" entry for each node is present in // the nodes table. - node1DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey1Bytes) + node1DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey1Bytes) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } - node2DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey2Bytes) + node2DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey2Bytes) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } @@ -4187,23 +4524,34 @@ func insertChannel(ctx context.Context, db SQLQueries, } createParams := sqlc.CreateChannelParams{ - Version: int16(lnwire.GossipVersion1), - Scid: channelIDToBytes(edge.ChannelID), - NodeID1: node1DBID, - NodeID2: node2DBID, - Outpoint: edge.ChannelPoint.String(), - Capacity: capacity, - BitcoinKey1: edge.BitcoinKey1Bytes[:], - BitcoinKey2: edge.BitcoinKey2Bytes[:], + Version: int16(v), + Scid: channelIDToBytes(edge.ChannelID), + NodeID1: node1DBID, + NodeID2: node2DBID, + Outpoint: edge.ChannelPoint.String(), + Capacity: capacity, } + edge.BitcoinKey1Bytes.WhenSome(func(vertex route.Vertex) { + createParams.BitcoinKey1 = vertex[:] + }) + edge.BitcoinKey2Bytes.WhenSome(func(vertex route.Vertex) { + createParams.BitcoinKey2 = vertex[:] + }) + edge.FundingScript.WhenSome(func(script []byte) { + createParams.FundingPkScript = script + }) + edge.MerkleRootHash.WhenSome(func(hash chainhash.Hash) { + createParams.MerkleRootHash = hash[:] + }) if edge.AuthProof != nil { proof := edge.AuthProof - createParams.Node1Signature = proof.NodeSig1Bytes - createParams.Node2Signature = proof.NodeSig2Bytes - createParams.Bitcoin1Signature = proof.BitcoinSig1Bytes - createParams.Bitcoin2Signature = proof.BitcoinSig2Bytes + createParams.Node1Signature = proof.NodeSig1() + createParams.Node2Signature = proof.NodeSig2() + createParams.Bitcoin1Signature = proof.BitcoinSig1() + createParams.Bitcoin2Signature = proof.BitcoinSig2() + createParams.Signature = proof.Sig() } // Insert the new channel record. @@ -4227,10 +4575,13 @@ func insertChannel(ctx context.Context, db SQLQueries, } // Finally, insert any extra TLV fields in the channel announcement. - extra, err := marshalExtraOpaqueData(edge.ExtraOpaqueData) - if err != nil { - return fmt.Errorf("unable to marshal extra opaque data: %w", - err) + extra := edge.ExtraSignedFields + if v == lnwire.GossipVersion1 { + extra, err = marshalExtraOpaqueData(edge.ExtraOpaqueData) + if err != nil { + return fmt.Errorf("unable to marshal extra opaque "+ + "data: %w", err) + } } for tlvType, value := range extra { @@ -4256,12 +4607,12 @@ func insertChannel(ctx context.Context, db SQLQueries, // created. The ID of the node is returned. A shell node only has a protocol // version and public key persisted. func maybeCreateShellNode(ctx context.Context, db SQLQueries, - pubKey route.Vertex) (int64, error) { + v lnwire.GossipVersion, pubKey route.Vertex) (int64, error) { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ PubKey: pubKey[:], - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) // The node exists. Return the ID. @@ -4274,7 +4625,7 @@ func maybeCreateShellNode(ctx context.Context, db SQLQueries, // Otherwise, the node does not exist, so we create a shell entry for // it. id, err := db.UpsertNode(ctx, sqlc.UpsertNodeParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: pubKey[:], }) if err != nil { @@ -4342,9 +4693,9 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, dbChan sqlc.GraphChannel, node1, node2 route.Vertex, batchData *batchChannelData) (*models.ChannelEdgeInfo, error) { - if dbChan.Version != int16(lnwire.GossipVersion1) { - return nil, fmt.Errorf("unsupported channel version: %d", - dbChan.Version) + v := lnwire.GossipVersion(dbChan.Version) + if !isKnownGossipVersion(v) { + return nil, fmt.Errorf("unknown channel version: %d", v) } // Use pre-loaded features and extras types. @@ -4368,42 +4719,120 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, return nil, err } - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra signed "+ - "fields: %w", err) - } - if recs == nil { - recs = make([]byte, 0) - } - - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], dbChan.BitcoinKey1) - copy(btcKey2[:], dbChan.BitcoinKey2) - - channel := &models.ChannelEdgeInfo{ - ChainHash: chain, - ChannelID: byteOrder.Uint64(dbChan.Scid), - NodeKey1Bytes: node1, - NodeKey2Bytes: node2, - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - ChannelPoint: *op, - Capacity: btcutil.Amount(dbChan.Capacity.Int64), - Features: fv, - ExtraOpaqueData: recs, - } - - // We always set all the signatures at the same time, so we can - // safely check if one signature is present to determine if we have the - // rest of the signatures for the auth proof. - if len(dbChan.Bitcoin1Signature) > 0 { - channel.AuthProof = &models.ChannelAuthProof{ - NodeSig1Bytes: dbChan.Node1Signature, - NodeSig2Bytes: dbChan.Node2Signature, - BitcoinSig1Bytes: dbChan.Bitcoin1Signature, - BitcoinSig2Bytes: dbChan.Bitcoin2Signature, + // Build the appropriate channel based on version. + var channel *models.ChannelEdgeInfo + switch v { + case lnwire.GossipVersion1: + // For v1, serialize extras into ExtraOpaqueData. + recs, err := lnwire.CustomRecords(extras).Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize extra "+ + "signed fields: %w", err) } + if recs == nil { + recs = make([]byte, 0) + } + + // Bitcoin keys are required for v1. + btcKey1, err := route.NewVertexFromBytes(dbChan.BitcoinKey1) + if err != nil { + return nil, err + } + btcKey2, err := route.NewVertexFromBytes(dbChan.BitcoinKey2) + if err != nil { + return nil, err + } + + channel, err = models.NewV1Channel( + byteOrder.Uint64(dbChan.Scid), chain, node1, node2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + ExtraOpaqueData: recs, + }, + models.WithChannelPoint(*op), + models.WithCapacity( + btcutil.Amount(dbChan.Capacity.Int64), + ), + models.WithFeatures(fv.RawFeatureVector), + ) + if err != nil { + return nil, err + } + + // For v1 channels, attach the auth proof if all four + // signatures are present. + if len(dbChan.Bitcoin1Signature) > 0 { + channel.AuthProof = models.NewV1ChannelAuthProof( + dbChan.Node1Signature, + dbChan.Node2Signature, + dbChan.Bitcoin1Signature, + dbChan.Bitcoin2Signature, + ) + } + + case lnwire.GossipVersion2: + v2Fields := &models.ChannelV2Fields{ + ExtraSignedFields: extras, + } + + // For v2, bitcoin keys are optional. + if len(dbChan.BitcoinKey1) > 0 { + btcKey1, err := route.NewVertexFromBytes( + dbChan.BitcoinKey1, + ) + if err != nil { + return nil, err + } + v2Fields.BitcoinKey1Bytes = fn.Some(btcKey1) + } + if len(dbChan.BitcoinKey2) > 0 { + btcKey2, err := route.NewVertexFromBytes( + dbChan.BitcoinKey2, + ) + if err != nil { + return nil, err + } + v2Fields.BitcoinKey2Bytes = fn.Some(btcKey2) + } + + // Parse funding script if present. + if len(dbChan.FundingPkScript) > 0 { + v2Fields.FundingScript = fn.Some(dbChan.FundingPkScript) + } + + // Parse merkle root hash if present. + if len(dbChan.MerkleRootHash) > 0 { + var hash chainhash.Hash + copy(hash[:], dbChan.MerkleRootHash) + v2Fields.MerkleRootHash = fn.Some(hash) + } + + opts := []models.EdgeModifier{ + models.WithChannelPoint(*op), + models.WithCapacity(btcutil.Amount( + dbChan.Capacity.Int64, + )), + models.WithFeatures(fv.RawFeatureVector), + } + + // For v2 channels, attach the auth proof if the signature is + // present. + if len(dbChan.Signature) > 0 { + proof := models.NewV2ChannelAuthProof(dbChan.Signature) + opts = append(opts, models.WithChanProof(proof)) + } + + channel, err = models.NewV2Channel( + byteOrder.Uint64(dbChan.Scid), chain, node1, node2, + v2Fields, opts..., + ) + if err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported channel version: %d", v) } return channel, nil @@ -4442,6 +4871,21 @@ func getAndBuildChanPolicies(ctx context.Context, cfg *sqldb.QueryConfig, return nil, nil, nil } + // TODO(elle): update to support v2 policies. + if dbPol1 != nil && + lnwire.GossipVersion(dbPol1.Version) != lnwire.GossipVersion1 { + + return nil, nil, fmt.Errorf("unsupported policy1 version: %d", + dbPol1.Version) + } + + if dbPol2 != nil && + lnwire.GossipVersion(dbPol2.Version) != lnwire.GossipVersion1 { + + return nil, nil, fmt.Errorf("unsupported policy2 version: %d", + dbPol2.Version) + } + var policyIDs = make([]int64, 0, 2) if dbPol1 != nil { policyIDs = append(policyIDs, dbPol1.ID) @@ -4457,14 +4901,14 @@ func getAndBuildChanPolicies(ctx context.Context, cfg *sqldb.QueryConfig, } pol1, err := buildChanPolicyWithBatchData( - dbPol1, channelID, node2, batchData, + true, dbPol1, channelID, node2, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy1: %w", err) } pol2, err := buildChanPolicyWithBatchData( - dbPol2, channelID, node1, batchData, + false, dbPol2, channelID, node1, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy2: %w", err) @@ -4482,7 +4926,9 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, var p1, p2 *models.CachedEdgePolicy if dbPol1 != nil { - policy1, err := buildChanPolicy(*dbPol1, channelID, nil, node2) + policy1, err := buildChanPolicy( + true, *dbPol1, channelID, nil, node2, + ) if err != nil { return nil, nil, err } @@ -4490,7 +4936,9 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, p1 = models.NewCachedPolicy(policy1) } if dbPol2 != nil { - policy2, err := buildChanPolicy(*dbPol2, channelID, nil, node1) + policy2, err := buildChanPolicy( + false, *dbPol2, channelID, nil, node1, + ) if err != nil { return nil, nil, err } @@ -4503,16 +4951,10 @@ func buildCachedChanPolicies(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, // buildChanPolicy builds a models.ChannelEdgePolicy instance from the // provided sqlc.GraphChannelPolicy and other required information. -func buildChanPolicy(dbPolicy sqlc.GraphChannelPolicy, channelID uint64, - extras map[uint64][]byte, +func buildChanPolicy(isNode1 bool, dbPolicy sqlc.GraphChannelPolicy, + channelID uint64, extras map[uint64][]byte, toNode route.Vertex) (*models.ChannelEdgePolicy, error) { - recs, err := lnwire.CustomRecords(extras).Serialize() - if err != nil { - return nil, fmt.Errorf("unable to serialize extra signed "+ - "fields: %w", err) - } - var inboundFee fn.Option[lnwire.Fee] if dbPolicy.InboundFeeRateMilliMsat.Valid || dbPolicy.InboundBaseFeeMsat.Valid { @@ -4523,18 +4965,11 @@ func buildChanPolicy(dbPolicy sqlc.GraphChannelPolicy, channelID uint64, }) } - return &models.ChannelEdgePolicy{ - SigBytes: dbPolicy.Signature, - ChannelID: channelID, - LastUpdate: time.Unix( - dbPolicy.LastUpdate.Int64, 0, - ), - MessageFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateMsgFlags]( - dbPolicy.MessageFlags, - ), - ChannelFlags: sqldb.ExtractSqlInt16[lnwire.ChanUpdateChanFlags]( - dbPolicy.ChannelFlags, - ), + p := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion(dbPolicy.Version), + SigBytes: dbPolicy.Signature, + ChannelID: channelID, + SecondPeer: !isNode1, TimeLockDelta: uint16(dbPolicy.Timelock), MinHTLC: lnwire.MilliSatoshi( dbPolicy.MinHtlcMsat, @@ -4548,8 +4983,40 @@ func buildChanPolicy(dbPolicy sqlc.GraphChannelPolicy, channelID uint64, FeeProportionalMillionths: lnwire.MilliSatoshi(dbPolicy.FeePpm), ToNode: toNode, InboundFee: inboundFee, - ExtraOpaqueData: recs, - }, nil + } + + if p.Version == lnwire.GossipVersion1 { + recs, err := lnwire.CustomRecords(extras).Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize extra "+ + "signed fields: %w", err) + } + + p.ExtraOpaqueData = recs + p.LastUpdate = time.Unix(dbPolicy.LastUpdate.Int64, 0) + //nolint:ll + p.MessageFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateMsgFlags]( + dbPolicy.MessageFlags, + ) + //nolint:ll + p.ChannelFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateChanFlags]( + dbPolicy.ChannelFlags, + ) + } else { + if dbPolicy.BlockHeight.Valid { + p.LastBlockHeight = uint32( + dbPolicy.BlockHeight.Int64, + ) + } + + //nolint:ll + p.DisableFlags = sqldb.ExtractSqlInt16[lnwire.ChanUpdateDisableFlags]( + dbPolicy.DisableFlags, + ) + p.ExtraSignedFields = extras + } + + return p, nil } // extractChannelPolicies extracts the sqlc.GraphChannelPolicy records from the give @@ -4566,6 +5033,7 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, case sqlc.ListChannelsWithPoliciesForCachePaginatedRow: if r.Policy1Timelock.Valid { policy1 = &sqlc.GraphChannelPolicy{ + Version: int16(lnwire.GossipVersion1), Timelock: r.Policy1Timelock.Int32, FeePpm: r.Policy1FeePpm.Int64, BaseFeeMsat: r.Policy1BaseFeeMsat.Int64, @@ -4576,10 +5044,13 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, Disabled: r.Policy1Disabled, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2Timelock.Valid { policy2 = &sqlc.GraphChannelPolicy{ + Version: int16(lnwire.GossipVersion1), Timelock: r.Policy2Timelock.Int32, FeePpm: r.Policy2FeePpm.Int64, BaseFeeMsat: r.Policy2BaseFeeMsat.Int64, @@ -4590,6 +5061,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, Disabled: r.Policy2Disabled, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4614,6 +5087,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4634,6 +5109,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4658,6 +5135,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4678,6 +5157,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4702,6 +5183,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4722,6 +5205,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4746,6 +5231,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4766,6 +5253,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4790,6 +5279,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4810,6 +5301,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4834,6 +5327,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4854,6 +5349,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4878,6 +5375,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4898,6 +5397,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -4922,6 +5423,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy1MessageFlags, ChannelFlags: r.Policy1ChannelFlags, Signature: r.Policy1Signature, + BlockHeight: r.Policy1BlockHeight, + DisableFlags: r.Policy1DisableFlags, } } if r.Policy2ID.Valid { @@ -4942,6 +5445,8 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy, MessageFlags: r.Policy2MessageFlags, ChannelFlags: r.Policy2ChannelFlags, Signature: r.Policy2Signature, + BlockHeight: r.Policy2BlockHeight, + DisableFlags: r.Policy2DisableFlags, } } @@ -5223,14 +5728,14 @@ func buildChanPoliciesWithBatchData(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, *models.ChannelEdgePolicy, error) { pol1, err := buildChanPolicyWithBatchData( - dbPol1, channelID, node2, batchData, + true, dbPol1, channelID, node2, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy1: %w", err) } pol2, err := buildChanPolicyWithBatchData( - dbPol2, channelID, node1, batchData, + false, dbPol2, channelID, node1, batchData, ) if err != nil { return nil, nil, fmt.Errorf("unable to build policy2: %w", err) @@ -5241,9 +5746,10 @@ func buildChanPoliciesWithBatchData(dbPol1, dbPol2 *sqlc.GraphChannelPolicy, // buildChanPolicyWithBatchData builds a models.ChannelEdgePolicy instance from // the provided sqlc.GraphChannelPolicy and the provided batchChannelData. -func buildChanPolicyWithBatchData(dbPol *sqlc.GraphChannelPolicy, - channelID uint64, toNode route.Vertex, - batchData *batchChannelData) (*models.ChannelEdgePolicy, error) { +func buildChanPolicyWithBatchData(isNode1 bool, + dbPol *sqlc.GraphChannelPolicy, channelID uint64, + toNode route.Vertex, batchData *batchChannelData) ( + *models.ChannelEdgePolicy, error) { if dbPol == nil { return nil, nil @@ -5256,7 +5762,7 @@ func buildChanPolicyWithBatchData(dbPol *sqlc.GraphChannelPolicy, dbPol1Extras = make(map[uint64][]byte) } - return buildChanPolicy(*dbPol, channelID, dbPol1Extras, toNode) + return buildChanPolicy(isNode1, *dbPol, channelID, dbPol1Extras, toNode) } // batchChannelData holds all the related data for a batch of channels. @@ -5474,8 +5980,8 @@ func forEachNodePaginated(ctx context.Context, cfg *sqldb.QueryConfig, // forEachChannelWithPolicies executes a paginated query to process each channel // with policies in the graph. func forEachChannelWithPolicies(ctx context.Context, db SQLQueries, - cfg *SQLStoreConfig, processChannel func(*models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, + cfg *SQLStoreConfig, v lnwire.GossipVersion, + processChannel func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { type channelBatchIDs struct { @@ -5489,7 +5995,7 @@ func forEachChannelWithPolicies(ctx context.Context, db SQLQueries, return db.ListChannelsWithPoliciesPaginated( ctx, sqlc.ListChannelsWithPoliciesPaginatedParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), ID: lastID, Limit: limit, }, @@ -5838,12 +6344,19 @@ func batchBuildChannelInfo[T sqlc.ChannelAndNodeIDs](ctx context.Context, // we are in strict zombie pruning mode, and adjusts the node public keys // accordingly based on the last update timestamps of the channel policies. func handleZombieMarking(ctx context.Context, db SQLQueries, + v lnwire.GossipVersion, row sqlc.GetChannelsBySCIDWithPoliciesRow, info *models.ChannelEdgeInfo, strictZombiePruning bool, scid uint64) error { nodeKey1, nodeKey2 := info.NodeKey1Bytes, info.NodeKey2Bytes if strictZombiePruning { + // TODO(elle): update for V2 last update times. + if v != lnwire.GossipVersion1 { + return fmt.Errorf("strict zombie pruning only "+ + "supported for gossip v1, got %v", v) + } + var e1UpdateTime, e2UpdateTime *time.Time if row.Policy1LastUpdate.Valid { e1Time := time.Unix(row.Policy1LastUpdate.Int64, 0) @@ -5862,7 +6375,7 @@ func handleZombieMarking(ctx context.Context, db SQLQueries, return db.UpsertZombieChannel( ctx, sqlc.UpsertZombieChannelParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), Scid: channelIDToBytes(scid), NodeKey1: nodeKey1[:], NodeKey2: nodeKey2[:], diff --git a/graph/db/test_kvdb.go b/graph/db/test_kvdb.go index f325d41f0..22904f858 100644 --- a/graph/db/test_kvdb.go +++ b/graph/db/test_kvdb.go @@ -9,8 +9,11 @@ import ( "github.com/stretchr/testify/require" ) +// isSQLDB indicates that this build does not use a SQL database. +var isSQLDB = false + // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t testing.TB) V1Store { +func NewTestDB(t testing.TB) Store { backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") require.NoError(t, err) diff --git a/graph/db/test_postgres.go b/graph/db/test_postgres.go index 6134f0114..a1bd61a2c 100644 --- a/graph/db/test_postgres.go +++ b/graph/db/test_postgres.go @@ -11,9 +11,12 @@ import ( "github.com/stretchr/testify/require" ) +// isSQLDB indicates that this build uses a SQL database. +var isSQLDB = true + // NewTestDB is a helper function that creates a SQLStore backed by a SQL // database for testing. -func NewTestDB(t testing.TB) V1Store { +func NewTestDB(t testing.TB) Store { return NewTestDBWithFixture(t, nil) } @@ -31,7 +34,7 @@ func NewTestDBFixture(t *testing.T) *sqldb.TestPgFixture { // NewTestDBWithFixture is a helper function that creates a SQLStore backed by a // SQL database for testing. func NewTestDBWithFixture(t testing.TB, - pgFixture *sqldb.TestPgFixture) V1Store { + pgFixture *sqldb.TestPgFixture) Store { var querier BatchedSQLQueries if pgFixture == nil { diff --git a/graph/db/test_sqlite.go b/graph/db/test_sqlite.go index c1c6d808f..137ab7aa9 100644 --- a/graph/db/test_sqlite.go +++ b/graph/db/test_sqlite.go @@ -11,9 +11,12 @@ import ( "github.com/stretchr/testify/require" ) +// isSQLDB indicates that this build uses a SQL database. +var isSQLDB = true + // NewTestDB is a helper function that creates a SQLStore backed by a SQL // database for testing. -func NewTestDB(t testing.TB) V1Store { +func NewTestDB(t testing.TB) Store { return NewTestDBWithFixture(t, nil) } @@ -24,7 +27,7 @@ func NewTestDBFixture(_ *testing.T) *sqldb.TestPgFixture { // NewTestDBWithFixture is a helper function that creates a SQLStore backed by a // SQL database for testing. -func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) V1Store { +func NewTestDBWithFixture(t testing.TB, _ *sqldb.TestPgFixture) Store { store, err := NewSQLStore( &SQLStoreConfig{ ChainHash: *chaincfg.MainNetParams.GenesisHash, diff --git a/graph/notifications_test.go b/graph/notifications_test.go index 3a30486c9..20e45026a 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" @@ -68,12 +69,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + testAuthProof = *models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) ) func createTestNode(t *testing.T) *models.Node { @@ -112,6 +113,7 @@ func randEdgePolicy(chanID *lnwire.ShortChannelID, } return &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: chanID.ToUint64(), LastUpdate: time.Unix(int64(prand.Int31()), 0), @@ -447,23 +449,27 @@ func TestEdgeUpdateNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // update to announce the created channel between the two nodes. - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanPoint, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithChannelPoint(*chanPoint), + models.WithCapacity(chanValue), + models.WithFundingScript(script), + ) + require.NoError(t, err) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) @@ -641,21 +647,25 @@ func TestNodeUpdateNotification(t *testing.T) { testFeaturesBuf := new(bytes.Buffer) require.NoError(t, testFeatures.Encode(testFeaturesBuf)) - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - Features: lnwire.EmptyFeatureVector(), - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithFundingScript(script), + ) + require.NoError(t, err) // Adding the edge will add the nodes to the graph, but with no info // except the pubkey known. @@ -827,23 +837,28 @@ func TestNotificationCancellation(t *testing.T) { // to the client. ntfnClient.Cancel() - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanPoint, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithChannelPoint(*chanPoint), + models.WithCapacity(chanValue), + models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -903,23 +918,28 @@ func TestChannelCloseNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // announcement to announce the created channel between the two nodes. - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanUtxo, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, node2.PubKeyBytes, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, models.WithChanProof(proof), + models.WithChannelPoint(*chanUtxo), + models.WithCapacity(chanValue), + models.WithFundingScript(script), + ) + require.NoError(t, err) + if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1042,7 +1062,7 @@ func TestEncodeHexColor(t *testing.T) { type testCtx struct { builder *Builder - graph *graphdb.ChannelGraph + graph *graphdb.VersionedGraph aliases map[string]route.Vertex @@ -1059,7 +1079,9 @@ type testCtx struct { func createTestCtxSingleNode(t *testing.T, startingHeight uint32) *testCtx { - graph := graphdb.MakeTestGraph(t) + graph := graphdb.NewVersionedGraph( + graphdb.MakeTestGraph(t), lnwire.GossipVersion1, + ) sourceNode := createTestNode(t) require.NoError(t, @@ -1086,7 +1108,7 @@ func (c *testCtx) RestartBuilder(t *testing.T) { // start it. builder, err := NewBuilder(&Config{ SelfNode: selfNode.PubKeyBytes, - Graph: c.graph, + Graph: c.graph.ChannelGraph, Chain: c.chain, ChainView: c.chainView, Notifier: c.builder.cfg.Notifier, @@ -1108,7 +1130,7 @@ func (c *testCtx) RestartBuilder(t *testing.T) { } type testGraphInstance struct { - graph *graphdb.ChannelGraph + graph *graphdb.VersionedGraph // aliasMap is a map from a node's alias to its public key. This type is // provided in order to allow easily look up from the human memorable @@ -1157,7 +1179,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, graphBuilder, err := NewBuilder(&Config{ SelfNode: selfnode.PubKeyBytes, - Graph: graphInstance.graph, + Graph: graphInstance.graph.ChannelGraph, Chain: chain, ChainView: chainView, Notifier: notifier, diff --git a/itest/lnd_graph_migration_test.go b/itest/lnd_graph_migration_test.go index 81fe65e87..d4a17a424 100644 --- a/itest/lnd_graph_migration_test.go +++ b/itest/lnd_graph_migration_test.go @@ -60,7 +60,7 @@ func testGraphMigration(ht *lntest.HarnessTest) { // assertDBState is a helper function that asserts the state of the // graph DB. - assertDBState := func(db graphdb.V1Store) { + assertDBState := func(db graphdb.Store) { var ( numNodes int edges = make(map[uint64]bool) @@ -127,7 +127,7 @@ func testGraphMigration(ht *lntest.HarnessTest) { } func openNativeSQLGraphDB(ht *lntest.HarnessTest, - hn *node.HarnessNode) graphdb.V1Store { + hn *node.HarnessNode) graphdb.Store { db := openNativeSQLDB(ht, hn) diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index 39c089dff..3cecb36b5 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -224,7 +224,6 @@ func (s *Server) ImportGraph(ctx context.Context, // Obtain the pointer to the global singleton channel graph. graphDB := s.cfg.GraphDB - var err error for _, rpcNode := range graph.Nodes { pubKeyBytes, err := parsePubKey(rpcNode.PubKey) if err != nil { @@ -258,7 +257,7 @@ func (s *Server) ImportGraph(ctx context.Context, Color: nodeColor, // NOTE: this is a workaround to ensure that // HaveAnnouncement() returns true so that the other - // fields are properly persisted. However, + // fields are properly persisted. AuthSigBytes: []byte{0}, }) @@ -273,18 +272,12 @@ func (s *Server) ImportGraph(ctx context.Context, for _, rpcEdge := range graph.Edges { rpcEdge := rpcEdge - edge := &models.ChannelEdgeInfo{ - ChannelID: rpcEdge.ChannelId, - ChainHash: *s.cfg.ActiveNetParams.GenesisHash, - Capacity: btcutil.Amount(rpcEdge.Capacity), - } - - edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub) + node1, err := parsePubKey(rpcEdge.Node1Pub) if err != nil { return nil, err } - edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub) + node2, err := parsePubKey(rpcEdge.Node2Pub) if err != nil { return nil, err } @@ -293,7 +286,16 @@ func (s *Server) ImportGraph(ctx context.Context, if err != nil { return nil, err } - edge.ChannelPoint = *channelPoint + + edge, err := models.NewV1Channel( + rpcEdge.ChannelId, *s.cfg.ActiveNetParams.GenesisHash, + node1, node2, &models.ChannelV1Fields{}, + models.WithCapacity(btcutil.Amount(rpcEdge.Capacity)), + models.WithChannelPoint(*channelPoint), + ) + if err != nil { + return nil, err + } if err := graphDB.AddChannelEdge(ctx, edge); err != nil { return nil, fmt.Errorf("unable to add edge %v: %w", @@ -302,6 +304,7 @@ func (s *Server) ImportGraph(ctx context.Context, makePolicy := func(rpcPolicy *lnrpc.RoutingPolicy) *models.ChannelEdgePolicy { //nolint:ll policy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: rpcEdge.ChannelId, LastUpdate: time.Unix( int64(rpcPolicy.LastUpdate), 0, diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index 9394ce26d..104b2873d 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -6,10 +6,12 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -305,10 +307,19 @@ var shouldIncludeChannelTestCases = []struct { h.Mock.On( "FetchChannelEdgesByID", mock.Anything, ).Once().Return( - &models.ChannelEdgeInfo{ - NodeKey1Bytes: selectedPolicy, - }, + func() *models.ChannelEdgeInfo { + edge, err := models.NewV1Channel( + 0, chainhash.Hash{}, selectedPolicy, + route.Vertex{}, + &models.ChannelV1Fields{}, + ) + require.NoError(h.t, err) + + return edge + }(), + //nolint:ll &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, @@ -355,7 +366,9 @@ var shouldIncludeChannelTestCases = []struct { ).Once().Return( &models.ChannelEdgeInfo{}, &models.ChannelEdgePolicy{}, + //nolint:ll &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, @@ -400,7 +413,9 @@ var shouldIncludeChannelTestCases = []struct { ).Once().Return( &models.ChannelEdgeInfo{}, &models.ChannelEdgePolicy{}, + //nolint:ll &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, FeeBaseMSat: 1000, FeeProportionalMillionths: 20, TimeLockDelta: 13, diff --git a/lnwire/channel_update_2.go b/lnwire/channel_update_2.go index 8e82fe9d7..3fba2470d 100644 --- a/lnwire/channel_update_2.go +++ b/lnwire/channel_update_2.go @@ -380,9 +380,23 @@ func (c ChanUpdateDisableFlags) IsEnabled() bool { return c == 0 } -// String returns the bitfield flags as a string. +// String returns a human-readable representation of the disable flags. func (c ChanUpdateDisableFlags) String() string { - return fmt.Sprintf("%08b", c) + if c.IsEnabled() { + return "Enabled" + } + + incoming := c.IncomingDisabled() + outgoing := c.OutgoingDisabled() + + switch { + case incoming && outgoing: + return "Disabled(incoming&outgoing)" + case incoming: + return "Disabled(incoming)" + default: + return "Disabled(outgoing)" + } } // Record returns the tlv record for the disable flags. diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 024b77932..59ce324c6 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -19,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -100,18 +102,28 @@ func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, // bit. dir2 |= lnwire.ChanUpdateDirection - return &models.ChannelEdgeInfo{ - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: pubkey1, - NodeKey2Bytes: pubkey2, - }, + pubkey1Vertex, err := route.NewVertexFromBytes(pubkey1[:]) + require.NoError(t, err) + pubkey2Vertex, err := route.NewVertexFromBytes(pubkey2[:]) + require.NoError(t, err) + + edgeInfo, err := models.NewV1Channel( + channel.ShortChanID().ToUint64(), chainhash.Hash{}, + pubkey1Vertex, pubkey2Vertex, &models.ChannelV1Fields{}, + models.WithChannelPoint(channel.FundingOutpoint), + ) + require.NoError(t, err) + + return edgeInfo, &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir1, LastUpdate: time.Now(), SigBytes: testSigBytes, }, &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir2, LastUpdate: time.Now(), @@ -212,6 +224,7 @@ func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate1, timestamp := time.Unix(int64(update.Timestamp), 0) policy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: update.ShortChannelID.ToUint64(), ChannelFlags: update.ChannelFlags, LastUpdate: timestamp, diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index 9bb21c401..14f65c3f5 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -34,48 +34,14 @@ const ( // function is used to transform out database structs into the corresponding wire // structs for announcing new channels to other peers, or simply syncing up a // peer's initial routing table upon connect. -func CreateChanAnnouncement(chanProof *models.ChannelAuthProof, - chanInfo *models.ChannelEdgeInfo, +func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo, e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1, *lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) { // First, using the parameters of the channel, along with the channel - // authentication chanProof, we'll create re-create the original + // authentication proof, we'll create re-create the original // authenticated channel announcement. - chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID) - chanAnn := &lnwire.ChannelAnnouncement1{ - ShortChannelID: chanID, - NodeID1: chanInfo.NodeKey1Bytes, - NodeID2: chanInfo.NodeKey2Bytes, - ChainHash: chanInfo.ChainHash, - BitcoinKey1: chanInfo.BitcoinKey1Bytes, - BitcoinKey2: chanInfo.BitcoinKey2Bytes, - Features: chanInfo.Features.RawFeatureVector, - ExtraOpaqueData: chanInfo.ExtraOpaqueData, - } - - var err error - chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig1Bytes, - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig2Bytes, - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig1Bytes, - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig2Bytes, - ) + chanAnn, err := chanInfo.ToChannelAnnouncement() if err != nil { return nil, nil, nil, err } diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 38949e046..11e72bb4a 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -40,29 +40,26 @@ func TestCreateChanAnnouncement(t *testing.T) { ExtraOpaqueData: []byte{0x1}, } - chanProof := &models.ChannelAuthProof{ - NodeSig1Bytes: expChanAnn.NodeSig1.ToSignatureBytes(), - NodeSig2Bytes: expChanAnn.NodeSig2.ToSignatureBytes(), - BitcoinSig1Bytes: expChanAnn.BitcoinSig1.ToSignatureBytes(), - BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(), - } - chanInfo := &models.ChannelEdgeInfo{ - ChainHash: expChanAnn.ChainHash, - ChannelID: expChanAnn.ShortChannelID.ToUint64(), - ChannelPoint: wire.OutPoint{Index: 1}, - Capacity: btcutil.SatoshiPerBitcoin, - NodeKey1Bytes: key, - NodeKey2Bytes: key, - BitcoinKey1Bytes: key, - BitcoinKey2Bytes: key, - Features: lnwire.NewFeatureVector( - features, lnwire.Features, - ), - ExtraOpaqueData: expChanAnn.ExtraOpaqueData, - } - chanAnn, _, _, err := CreateChanAnnouncement( - chanProof, chanInfo, nil, nil, + chanProof := models.NewV1ChannelAuthProof( + expChanAnn.NodeSig1.ToSignatureBytes(), + expChanAnn.NodeSig2.ToSignatureBytes(), + expChanAnn.BitcoinSig1.ToSignatureBytes(), + expChanAnn.BitcoinSig2.ToSignatureBytes(), ) + chanInfo, err := models.NewV1Channel( + expChanAnn.ShortChannelID.ToUint64(), expChanAnn.ChainHash, + key, key, &models.ChannelV1Fields{ + BitcoinKey1Bytes: key, + BitcoinKey2Bytes: key, + ExtraOpaqueData: expChanAnn.ExtraOpaqueData, + }, + models.WithChanProof(chanProof), + models.WithChannelPoint(wire.OutPoint{Index: 1}), + models.WithCapacity(btcutil.SatoshiPerBitcoin), + models.WithFeatures(features), + ) + require.NoError(t, err) + chanAnn, _, _, err := CreateChanAnnouncement(chanInfo, nil, nil) require.NoError(t, err, "unable to create channel announcement") assert.Equal(t, chanAnn, expChanAnn) diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index a48486e7b..6dd30b0ae 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -18,6 +18,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/route" ) // Manager manages the node's local channels. The only operation that is @@ -328,24 +329,42 @@ func (r *Manager) createEdge(channel *channeldb.OpenChannel, "script: %v", err) } - info := &models.ChannelEdgeInfo{ - ChannelID: shortChanID.ToUint64(), - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: channel.Capacity, - ChannelPoint: channel.FundingOutpoint, - FundingScript: fn.Some(fundingScript), + nodeKey1, err := route.NewVertexFromBytes(nodeKey1Bytes) + if err != nil { + return nil, nil, err + } + nodeKey2, err := route.NewVertexFromBytes(nodeKey2Bytes) + if err != nil { + return nil, nil, err + } + bitcoinKey1, err := route.NewVertexFromBytes(bitcoinKey1Bytes) + if err != nil { + return nil, nil, err + } + bitcoinKey2, err := route.NewVertexFromBytes(bitcoinKey2Bytes) + if err != nil { + return nil, nil, err } - copy(info.NodeKey1Bytes[:], nodeKey1Bytes) - copy(info.NodeKey2Bytes[:], nodeKey2Bytes) - copy(info.BitcoinKey1Bytes[:], bitcoinKey1Bytes) - copy(info.BitcoinKey2Bytes[:], bitcoinKey2Bytes) + info, err := models.NewV1Channel( + shortChanID.ToUint64(), channel.ChainHash, nodeKey1, nodeKey2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: bitcoinKey1, + BitcoinKey2Bytes: bitcoinKey2, + }, + models.WithCapacity(channel.Capacity), + models.WithChannelPoint(channel.FundingOutpoint), + models.WithFundingScript(fundingScript), + ) + if err != nil { + return nil, nil, err + } // Construct a dummy channel edge policy with default values that will // be updated with the new values in the call to processChan below. timeLockDelta := uint16(r.DefaultRoutingPolicy.TimeLockDelta) edge := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: shortChanID.ToUint64(), LastUpdate: timestamp, TimeLockDelta: timeLockDelta, @@ -449,7 +468,7 @@ func (r *Manager) updateEdge(chanPoint wire.OutPoint, } // Clear signature to help prevent usage of the previous signature. - edge.SetSigBytes(nil) + edge.SigBytes = nil return nil } diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 5df344bba..c48e61641 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -13,13 +13,13 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/discovery" - "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -64,6 +64,7 @@ func TestManager(t *testing.T) { } currentPolicy := models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, MinHTLC: minHTLC, MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, } @@ -211,10 +212,21 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -229,10 +241,21 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{}, @@ -247,10 +270,22 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointMissing}, @@ -269,10 +304,22 @@ func TestManager(t *testing.T) { newPolicy: noMaxHtlcPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -391,23 +438,21 @@ func TestCreateEdgeLower(t *testing.T) { fundingScript, err := funding.MakeFundingScript(channel) require.NoError(t, err) - expectedInfo := &models.ChannelEdgeInfo{ - ChannelID: 8, - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: 9, - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: sp, - NodeKey2Bytes: rp, - BitcoinKey1Bytes: [33]byte( - localMultisigKey.SerializeCompressed()), - BitcoinKey2Bytes: [33]byte( - remoteMultisigKey.SerializeCompressed()), - AuthProof: nil, - ExtraOpaqueData: nil, - FundingScript: fn.Some(fundingScript), - } + btcKey1 := route.NewVertex(localMultisigKey) + btcKey2 := route.NewVertex(remoteMultisigKey) + expectedInfo, err := models.NewV1Channel( + 8, channel.ChainHash, sp, rp, &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithCapacity(9), + models.WithChannelPoint(channel.FundingOutpoint), + models.WithFundingScript(fundingScript), + ) + require.NoError(t, err) + expectedEdge := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, @@ -484,23 +529,22 @@ func TestCreateEdgeHigher(t *testing.T) { fundingScript, err := funding.MakeFundingScript(channel) require.NoError(t, err) - expectedInfo := &models.ChannelEdgeInfo{ - ChannelID: 8, - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: 9, - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: rp, - NodeKey2Bytes: sp, - BitcoinKey1Bytes: [33]byte( - remoteMultisigKey.SerializeCompressed()), - BitcoinKey2Bytes: [33]byte( - localMultisigKey.SerializeCompressed()), - AuthProof: nil, - ExtraOpaqueData: nil, - FundingScript: fn.Some(fundingScript), - } + btcKey1 := route.NewVertex(remoteMultisigKey) + btcKey2 := route.NewVertex(localMultisigKey) + expectedInfo, err := models.NewV1Channel( + 8, channel.ChainHash, rp, sp, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithCapacity(9), + models.WithChannelPoint(channel.FundingOutpoint), + models.WithFundingScript(fundingScript), + ) + require.NoError(t, err) + expectedEdge := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, ChannelID: 8, LastUpdate: timestamp, TimeLockDelta: 7, diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 473bd413e..01f5d9849 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -99,12 +100,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + testAuthProof = *models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) ) // noProbabilitySource is used in testing to return the same probability 1 for @@ -345,18 +346,28 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: edge.ChannelID, - AuthProof: &testAuthProof, - ChannelPoint: fundingPoint, - Features: lnwire.EmptyFeatureVector(), - Capacity: btcutil.Amount(edge.Capacity), - } + var node1Vertex, node2Vertex route.Vertex + copy(node1Vertex[:], node1Bytes) + copy(node2Vertex[:], node2Bytes) - copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) - copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], node1Bytes) + copy(btcKey2[:], node2Bytes) + + edgeInfo, err := models.NewV1Channel( + edge.ChannelID, *chaincfg.SimNetParams.GenesisHash, + node1Vertex, node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(fundingPoint), + models.WithCapacity(btcutil.Amount(edge.Capacity)), + ) + if err != nil { + return nil, err + } shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ @@ -365,7 +376,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -378,6 +389,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: lnwire.ChanUpdateMsgFlags(edge.MessageFlags), ChannelFlags: channelFlags, @@ -395,12 +407,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + node1Vertex, err = route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err := route.NewVertexFromBytes(node2Bytes) + node2Vertex, err = route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -417,7 +429,10 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graph, + graph: graph, + v1Graph: graphdb.NewVersionedGraph( + graph, lnwire.GossipVersion1, + ), mcBackend: mcBackend, aliasMap: aliasMap, privKeyMap: privKeyMap, @@ -487,6 +502,7 @@ type testChannel struct { type testGraphInstance struct { graph *graphdb.ChannelGraph + v1Graph *graphdb.VersionedGraph mcBackend kvdb.Backend // aliasMap is a map from a node's alias to its public key. This type is @@ -673,20 +689,21 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. - edgeInfo := models.ChannelEdgeInfo{ - ChannelID: channelID, - AuthProof: &testAuthProof, - ChannelPoint: *fundingPoint, - Capacity: testChannel.Capacity, - Features: lnwire.EmptyFeatureVector(), - - NodeKey1Bytes: node1Vertex, - BitcoinKey1Bytes: node1Vertex, - NodeKey2Bytes: node2Vertex, - BitcoinKey2Bytes: node2Vertex, + edgeInfo, err := models.NewV1Channel( + channelID, *chaincfg.SimNetParams.GenesisHash, + node1Vertex, node2Vertex, &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Vertex, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(*fundingPoint), + models.WithCapacity(testChannel.Capacity), + ) + if err != nil { + return nil, err } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -724,7 +741,9 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, channelFlags |= lnwire.ChanUpdateDisabled } + //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -756,7 +775,9 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } channelFlags |= lnwire.ChanUpdateDirection + //nolint:ll edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), MessageFlags: msgFlags, ChannelFlags: channelFlags, @@ -781,7 +802,10 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } return &testGraphInstance{ - graph: graph, + graph: graph, + v1Graph: graphdb.NewVersionedGraph( + graph, lnwire.GossipVersion1, + ), mcBackend: graphBackend, aliasMap: aliasMap, privKeyMap: privKeyMap, @@ -1095,7 +1119,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc expectedHops := test.expectedHops expectedHopCount := len(expectedHops) - sourceNode, err := graphInstance.graph.SourceNode(ctx) + sourceNode, err := graphInstance.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") sourceVertex := route.Vertex(sourceNode.PubKeyBytes) @@ -1107,7 +1131,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc paymentAmt := lnwire.NewMSatFromSatoshis(test.paymentAmt) target := graphInstance.aliasMap[test.target] path, err := dbFindPath( - graphInstance.graph, nil, &mockBandwidthHints{}, + graphInstance.v1Graph, nil, &mockBandwidthHints{}, &RestrictParams{ FeeLimit: test.feeLimit, ProbabilitySource: noProbabilitySource, @@ -1237,7 +1261,7 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") paymentAmt := lnwire.NewMSatFromSatoshis(100) @@ -1282,7 +1306,8 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { []*unifiedEdge, error) { return dbFindPath( - graph.graph, additionalEdges, &mockBandwidthHints{}, + graph.v1Graph, additionalEdges, + &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, 0, 0, @@ -1322,7 +1347,7 @@ func runPathFindingWithBlindedPathDuplicateHop(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") paymentAmt := lnwire.NewMSatFromSatoshis(100) @@ -1396,7 +1421,7 @@ func runPathFindingWithBlindedPathDuplicateHop(t *testing.T, useCache bool) { []*unifiedEdge, error) { return dbFindPath( - graph.graph, blindedPath, &mockBandwidthHints{}, + graph.v1Graph, blindedPath, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, dummyTarget, paymentAmt, 0, 0, @@ -1456,7 +1481,7 @@ func runPathFindingWithRedundantAdditionalEdges(t *testing.T, useCache bool) { } path, err := dbFindPath( - ctx.graph, additionalEdges, ctx.bandwidthHints, + ctx.v1Graph, additionalEdges, ctx.bandwidthHints, &ctx.restrictParams, &ctx.pathFindingConfig, ctx.source, target, paymentAmt, ctx.timePref, 0, ) @@ -1809,7 +1834,7 @@ func runPathNotAvailable(t *testing.T, useCache bool) { ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // With the test graph loaded, we'll test that queries for target that @@ -1822,7 +1847,7 @@ func runPathNotAvailable(t *testing.T, useCache bool) { copy(unknownNode[:], unknownNodeBytes) _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, unknownNode, 100, 0, 0, ) @@ -1865,14 +1890,14 @@ func runDestTLVGraphFallback(t *testing.T, useCache bool) { ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") - sourceNode, err := ctx.graph.SourceNode(t.Context()) + sourceNode, err := ctx.v1Graph.SourceNode(t.Context()) require.NoError(t, err, "unable to fetch source node") find := func(r *RestrictParams, target route.Vertex) ([]*unifiedEdge, error) { return dbFindPath( - ctx.graph, nil, &mockBandwidthHints{}, + ctx.v1Graph, nil, &mockBandwidthHints{}, r, testPathFindingConfig, sourceNode.PubKeyBytes, target, 100, 0, 0, ) @@ -2084,7 +2109,7 @@ func runPathInsufficientCapacity(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // Next, test that attempting to find a path in which the current @@ -2099,7 +2124,7 @@ func runPathInsufficientCapacity(t *testing.T, useCache bool) { payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2115,7 +2140,7 @@ func runRouteFailMinHTLC(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // We'll not attempt to route an HTLC of 10 SAT from roasbeef to Son @@ -2124,7 +2149,7 @@ func runRouteFailMinHTLC(t *testing.T, useCache bool) { target := graph.aliasMap["songoku"] payAmt := lnwire.MilliSatoshi(10) _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2200,7 +2225,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // First, we'll try to route from roasbeef -> sophon. This should @@ -2208,7 +2233,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { target := graph.aliasMap["sophon"] payAmt := lnwire.NewMSatFromSatoshis(105000) _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2232,7 +2257,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { } _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2252,7 +2277,7 @@ func runRouteFailDisabledEdge(t *testing.T, useCache bool) { // If we attempt to route through that edge, we should get a failure as // it is no longer eligible. _, err = dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2269,7 +2294,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { require.NoError(t, err, "unable to create graph") ctx := t.Context() - sourceNode, err := graph.graph.SourceNode(ctx) + sourceNode, err := graph.v1Graph.SourceNode(ctx) require.NoError(t, err, "unable to fetch source node") // First, we'll try to route from roasbeef -> sophon. This should @@ -2278,7 +2303,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { target := graph.aliasMap["sophon"] payAmt := lnwire.NewMSatFromSatoshis(50000) path, err := dbFindPath( - graph.graph, nil, &mockBandwidthHints{}, + graph.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2299,7 +2324,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Since both these edges has a bandwidth of zero, no path should be // found. _, err = dbFindPath( - graph.graph, nil, bandwidths, + graph.v1Graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2314,7 +2339,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Now, if we attempt to route again, we should find the path via // phamnuven, as the other source edge won't be considered. path, err = dbFindPath( - graph.graph, nil, bandwidths, + graph.v1Graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -2340,7 +2365,7 @@ func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { // Since we ignore disable flags for local channels, a path should // still be found. path, err = dbFindPath( - graph.graph, nil, bandwidths, + graph.v1Graph, nil, bandwidths, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, payAmt, 0, 0, ) @@ -3184,6 +3209,7 @@ func runInboundFees(t *testing.T, useCache bool) { type pathFindingTestContext struct { t *testing.T graph *graphdb.ChannelGraph + v1Graph *graphdb.VersionedGraph restrictParams RestrictParams bandwidthHints bandwidthHints pathFindingConfig PathFindingConfig @@ -3201,7 +3227,7 @@ func newPathFindingTestContext(t *testing.T, useCache bool, ) require.NoError(t, err, "unable to create graph") - sourceNode, err := testGraphInstance.graph.SourceNode( + sourceNode, err := testGraphInstance.v1Graph.SourceNode( t.Context(), ) require.NoError(t, err, "unable to fetch source node") @@ -3212,6 +3238,7 @@ func newPathFindingTestContext(t *testing.T, useCache bool, source: route.Vertex(sourceNode.PubKeyBytes), pathFindingConfig: *testPathFindingConfig, graph: testGraphInstance.graph, + v1Graph: testGraphInstance.v1Graph, restrictParams: *noRestrictions, bandwidthHints: &mockBandwidthHints{}, } @@ -3247,7 +3274,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex, error) { return dbFindPath( - c.graph, nil, c.bandwidthHints, &c.restrictParams, + c.v1Graph, nil, c.bandwidthHints, &c.restrictParams, &c.pathFindingConfig, c.source, target, amt, c.timePref, 0, ) } @@ -3255,7 +3282,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex, func (c *pathFindingTestContext) findBlindedPaths( restrictions *blindedPathRestrictions) ([][]blindedHop, error) { - return dbFindBlindedPaths(c.graph, restrictions) + return dbFindBlindedPaths(c.v1Graph, restrictions) } func (c *pathFindingTestContext) assertPath(path []*unifiedEdge, @@ -3277,7 +3304,7 @@ func (c *pathFindingTestContext) assertPath(path []*unifiedEdge, // dbFindPath calls findPath after getting a db transaction from the database // graph. -func dbFindPath(graph *graphdb.ChannelGraph, +func dbFindPath(graph *graphdb.VersionedGraph, additionalEdges map[route.Vertex][]AdditionalEdge, bandwidthHints bandwidthHints, r *RestrictParams, cfg *PathFindingConfig, @@ -3315,7 +3342,7 @@ func dbFindPath(graph *graphdb.ChannelGraph, // dbFindBlindedPaths calls findBlindedPaths after getting a db transaction from // the database graph. -func dbFindBlindedPaths(graph *graphdb.ChannelGraph, +func dbFindBlindedPaths(graph *graphdb.VersionedGraph, restrictions *blindedPathRestrictions) ([][]blindedHop, error) { sourceNode, err := graph.SourceNode(context.Background()) diff --git a/routing/router_test.go b/routing/router_test.go index 9f088917d..115c02c2c 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -61,7 +61,8 @@ type testCtx struct { graphBuilder *mockGraphBuilder - graph *graphdb.ChannelGraph + graph *graphdb.ChannelGraph + v1Graph *graphdb.VersionedGraph aliases map[string]route.Vertex @@ -131,7 +132,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, ) require.NoError(t, err) - sourceNode, err := graphInstance.graph.SourceNode(t.Context()) + sourceNode, err := graphInstance.v1Graph.SourceNode(t.Context()) require.NoError(t, err) sessionSource := &SessionSource{ GraphSessionFactory: graphInstance.graph, @@ -170,9 +171,12 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, router: router, graphBuilder: graphBuilder, graph: graphInstance.graph, - aliases: graphInstance.aliasMap, - privKeys: graphInstance.privKeyMap, - channelIDs: graphInstance.channelIDs, + v1Graph: graphdb.NewVersionedGraph( + graphInstance.graph, lnwire.GossipVersion1, + ), + aliases: graphInstance.aliasMap, + privKeys: graphInstance.privKeyMap, + channelIDs: graphInstance.channelIDs, } t.Cleanup(func() { @@ -1201,7 +1205,7 @@ func TestFindPathFeeWeighting(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - sourceNode, err := ctx.graph.SourceNode(t.Context()) + sourceNode, err := ctx.v1Graph.SourceNode(t.Context()) require.NoError(t, err, "unable to fetch source node") amt := lnwire.MilliSatoshi(100) @@ -1212,7 +1216,7 @@ func TestFindPathFeeWeighting(t *testing.T) { // the edge weighting, we should select the direct path over the 2 hop // path even though the direct path has a higher potential time lock. path, err := dbFindPath( - ctx.graph, nil, &mockBandwidthHints{}, + ctx.v1Graph, nil, &mockBandwidthHints{}, noRestrictions, testPathFindingConfig, sourceNode.PubKeyBytes, target, amt, 0, 0, @@ -2717,11 +2721,11 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { copy(pub2[:], priv2.PubKey().SerializeCompressed()) // The two nodes we are about to add should not exist yet. - _, exists1, err := ctx.graph.HasNode(ctxb, pub1) + exists1, err := ctx.v1Graph.HasNode(ctxb, pub1) require.NoError(t, err, "unable to query graph") require.False(t, exists1) - _, exists2, err := ctx.graph.HasNode(ctxb, pub2) + exists2, err := ctx.v1Graph.HasNode(ctxb, pub2) require.NoError(t, err, "unable to query graph") require.False(t, exists2) @@ -2734,20 +2738,20 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { ) require.NoError(t, err, "unable to create channel edge") - edge := &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - Features: lnwire.EmptyFeatureVector(), - AuthProof: nil, - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), chainhash.Hash{}, pub1, pub2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + ) + require.NoError(t, err) require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) // We must add the edge policy to be able to use the edge for route // finding. edgePolicy := &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2763,6 +2767,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // Create edge in the other direction as well. edgePolicy = &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2778,11 +2783,11 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // After adding the edge between the two previously unknown nodes, they // should have been added to the graph. - _, exists1, err = ctx.graph.HasNode(ctxb, pub1) + exists1, err = ctx.v1Graph.HasNode(ctxb, pub1) require.NoError(t, err, "unable to query graph") require.True(t, exists1) - _, exists2, err = ctx.graph.HasNode(ctxb, pub2) + exists2, err = ctx.v1Graph.HasNode(ctxb, pub2) require.NoError(t, err, "unable to query graph") require.True(t, exists2) @@ -2814,19 +2819,22 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { 10000, 510) require.NoError(t, err, "unable to create channel edge") - edge = &models.ChannelEdgeInfo{ - ChannelID: chanID.ToUint64(), - Features: lnwire.EmptyFeatureVector(), - AuthProof: nil, - } - copy(edge.NodeKey1Bytes[:], node1Bytes) - edge.NodeKey2Bytes = node2Bytes - copy(edge.BitcoinKey1Bytes[:], node1Bytes) - edge.BitcoinKey2Bytes = node2Bytes + node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + require.NoError(t, err) + + edge, err = models.NewV1Channel( + chanID.ToUint64(), chainhash.Hash{}, node1Vertex, node2Bytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Bytes, + }, + ) + require.NoError(t, err) require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) edgePolicy = &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2841,6 +2849,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { require.NoError(t, ctx.graph.UpdateEdgePolicy(ctxb, edgePolicy)) edgePolicy = &models.ChannelEdgePolicy{ + Version: lnwire.GossipVersion1, SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, LastUpdate: testTime, @@ -2907,12 +2916,12 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { _, _, err = ctx.router.FindRoute(req) require.NoError(t, err, "unable to find any routes") - copy1, err := ctx.graph.FetchNode(ctxb, pub1) + copy1, err := ctx.v1Graph.FetchNode(ctxb, pub1) require.NoError(t, err, "unable to fetch node") require.Equal(t, n1.Alias, copy1.Alias) - copy2, err := ctx.graph.FetchNode(ctxb, pub2) + copy2, err := ctx.v1Graph.FetchNode(ctxb, pub2) require.NoError(t, err, "unable to fetch node") require.Equal(t, n2.Alias, copy2.Alias) @@ -2959,19 +2968,13 @@ func (m *mockGraphBuilder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool { return false } - err := m.updateEdge(&models.ChannelEdgePolicy{ - SigBytes: msg.Signature.ToSignatureBytes(), - ChannelID: msg.ShortChannelID.ToUint64(), - LastUpdate: time.Unix(int64(msg.Timestamp), 0), - MessageFlags: msg.MessageFlags, - ChannelFlags: msg.ChannelFlags, - TimeLockDelta: msg.TimeLockDelta, - MinHTLC: msg.HtlcMinimumMsat, - MaxHTLC: msg.HtlcMaximumMsat, - FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee), - FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate), - ExtraOpaqueData: msg.ExtraOpaqueData, - }) + update, err := models.ChanEdgePolicyFromWire( + msg.ShortChannelID.ToUint64(), msg, + ) + if err != nil { + return false + } + err = m.updateEdge(update) return err == nil } diff --git a/routing/unified_edges.go b/routing/unified_edges.go index 9b8f6c5c0..fda06aabf 100644 --- a/routing/unified_edges.go +++ b/routing/unified_edges.go @@ -188,7 +188,7 @@ func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool { } // Skip channels for which this htlc is too large. - if u.policy.MessageFlags.HasMaxHtlc() && + if u.policy.HasMaxHTLC && amt > u.policy.MaxHTLC { log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v", @@ -376,7 +376,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi, } // For network channels, skip the disabled ones. - if edge.policy.IsDisabled() { + if edge.policy.IsDisabled { log.Debugf("Skipped edge %v due to it being disabled", edge.policy.ChannelID) continue @@ -385,7 +385,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi, // Track the maximal capacity for usable channels. If we don't // know the capacity, we fall back to MaxHTLC. capMsat := lnwire.NewMSatFromSatoshis(edge.capacity) - if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() { + if capMsat == 0 && edge.policy.HasMaxHTLC { log.Tracef("No capacity available for channel %v, "+ "using MaxHtlcMsat (%v) as a fallback.", edge.policy.ChannelID, edge.policy.MaxHTLC) diff --git a/routing/unified_edges_test.go b/routing/unified_edges_test.go index 8fc79031a..25c8e9220 100644 --- a/routing/unified_edges_test.go +++ b/routing/unified_edges_test.go @@ -30,7 +30,7 @@ func TestNodeEdgeUnifier(t *testing.T) { FeeProportionalMillionths: 100000, FeeBaseMSat: 30, TimeLockDelta: 60, - MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, + HasMaxHTLC: true, MaxHTLC: 5000, MinHTLC: 100, } @@ -39,7 +39,7 @@ func TestNodeEdgeUnifier(t *testing.T) { FeeProportionalMillionths: 190000, FeeBaseMSat: 10, TimeLockDelta: 40, - MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc, + HasMaxHTLC: true, MaxHTLC: 4000, MinHTLC: 100, } diff --git a/rpcserver.go b/rpcserver.go index fe0e6702b..564d8cc32 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -704,7 +704,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error { // Set up router rpc backend. - selfNode, err := s.graphDB.SourceNode(ctx) + selfNode, err := s.v1Graph.SourceNode(ctx) if err != nil { return err } @@ -746,7 +746,7 @@ func (r *rpcServer) addDeps(ctx context.Context, s *server, return info.NodeKey1Bytes, info.NodeKey2Bytes, nil }, HasNode: func(nodePub route.Vertex) (bool, error) { - _, exists, err := graph.HasNode(ctx, nodePub) + exists, err := s.v1Graph.HasNode(ctx, nodePub) return exists, err }, @@ -1821,8 +1821,8 @@ func (r *rpcServer) VerifyMessage(ctx context.Context, // channels signed the message. // // TODO(phlip9): Require valid nodes to have capital in active channels. - graph := r.server.graphDB - _, active, err := graph.HasNode(ctx, pub) + graph := r.server.v1Graph + active, err := graph.HasNode(ctx, pub) if err != nil { return nil, fmt.Errorf("failed to query graph: %w", err) } @@ -3196,7 +3196,7 @@ func createRPCCloseUpdate( // abandonChanFromGraph attempts to remove a channel from the channel graph. If // we can't find the chanID in the graph, then we assume it has already been // removed, and will return a nop. -func abandonChanFromGraph(chanGraph *graphdb.ChannelGraph, +func abandonChanFromGraph(chanGraph *graphdb.VersionedGraph, chanPoint *wire.OutPoint) error { // First, we'll obtain the channel ID. If we can't locate this, then @@ -3237,7 +3237,8 @@ func (r *rpcServer) abandonChan(chanPoint *wire.OutPoint, if err != nil { return err } - err = abandonChanFromGraph(r.server.graphDB, chanPoint) + // TODO: update to support deletions for v2 channels. + err = abandonChanFromGraph(r.server.v1Graph, chanPoint) if err != nil { return err } @@ -4995,7 +4996,7 @@ func createRPCOpenChannel(ctx context.Context, r *rpcServer, // Look up our channel peer's node alias if the caller requests it. if peerAliasLookup { - peerAlias, err := r.server.graphDB.LookupAlias(ctx, nodePub) + peerAlias, err := r.server.v1Graph.LookupAlias(ctx, nodePub) if err != nil { peerAlias = fmt.Sprintf("unable to lookup "+ "peer alias: %v", err) @@ -6852,10 +6853,11 @@ func (r *rpcServer) DescribeGraph(ctx context.Context, } } - // Obtain the pointer to the global singleton channel graph, this will - // provide a consistent view of the graph due to bolt db's - // transactional model. - graph := r.server.graphDB + // Obtain the pointer to the V1 channel graph. This will provide a + // consistent view of the graph due to bolt db's transactional model. + // + // TODO(elle): switch to a cross-version graph view when available. + graph := r.server.v1Graph // First iterate through all the known nodes (connected or unconnected // within the graph), collating their current state into the RPC @@ -6980,10 +6982,10 @@ func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo, // channel announcement. if includeAuthProof && edgeInfo.AuthProof != nil { edge.AuthProof = &lnrpc.ChannelAuthProof{ - NodeSig1: edgeInfo.AuthProof.NodeSig1Bytes, - BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1Bytes, - NodeSig2: edgeInfo.AuthProof.NodeSig2Bytes, - BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2Bytes, + NodeSig1: edgeInfo.AuthProof.NodeSig1(), + BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1(), + NodeSig2: edgeInfo.AuthProof.NodeSig2(), + BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2(), } } @@ -7143,7 +7145,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, "include_channels") } - graph := r.server.graphDB + graph := r.server.v1Graph // First, parse the hex-encoded public key into a full in-memory public // key object we can work with for querying. @@ -7909,7 +7911,7 @@ const feeBase float64 = 1000000 func (r *rpcServer) FeeReport(ctx context.Context, _ *lnrpc.FeeReportRequest) (*lnrpc.FeeReportResponse, error) { - channelGraph := r.server.graphDB + channelGraph := r.server.v1Graph selfNode, err := channelGraph.SourceNode(ctx) if err != nil { return nil, err @@ -8298,7 +8300,7 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context, return "", err } - peer, err := r.server.graphDB.FetchNode(ctx, vertex) + peer, err := r.server.v1Graph.FetchNode(ctx, vertex) if err != nil { return "", err } diff --git a/server.go b/server.go index 4e8ad037c..ad97261ed 100644 --- a/server.go +++ b/server.go @@ -321,6 +321,7 @@ type server struct { fundingMgr *funding.Manager graphDB *graphdb.ChannelGraph + v1Graph *graphdb.VersionedGraph chanStateDB *channeldb.ChannelStateDB @@ -669,12 +670,17 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, HtlcInterceptor: invoiceHtlcModifier, } - addrSource := channeldb.NewMultiAddrSource(dbs.ChanStateDB, dbs.GraphDB) + v1Graph := graphdb.NewVersionedGraph( + dbs.GraphDB, lnwire.GossipVersion1, + ) + + addrSource := channeldb.NewMultiAddrSource(dbs.ChanStateDB, v1Graph) s := &server{ cfg: cfg, implCfg: implCfg, graphDB: dbs.GraphDB, + v1Graph: v1Graph, chanStateDB: dbs.ChanStateDB.ChannelStateDB(), addrSource: addrSource, miscDB: dbs.ChanStateDB, @@ -982,7 +988,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, MinProbability: routingConfig.MinRouteProbability, } - sourceNode, err := dbs.GraphDB.SourceNode(ctx) + sourceNode, err := s.v1Graph.SourceNode(ctx) if err != nil { return nil, fmt.Errorf("error getting source node: %w", err) } @@ -1125,7 +1131,8 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, *models.ChannelEdgePolicy) error, reset func()) error { - return s.graphDB.ForEachNodeChannel(ctx, selfVertex, + return s.v1Graph.ForEachNodeChannel( + ctx, selfVertex, func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -1416,7 +1423,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, return nil, fmt.Errorf("we don't have an edge") } - err = s.graphDB.DeleteChannelEdges( + err = s.v1Graph.DeleteChannelEdges( false, false, scid.ToUint64(), ) return ourPolicy, err @@ -3434,7 +3441,7 @@ func (s *server) updateAndBroadcastSelfNode(ctx context.Context, // Update the on-disk version of our announcement. // Load and modify self node istead of creating anew instance so we // don't risk overwriting any existing values. - selfNode, err := s.graphDB.SourceNode(ctx) + selfNode, err := s.v1Graph.SourceNode(ctx) if err != nil { return fmt.Errorf("unable to get current source node: %w", err) } @@ -5207,7 +5214,7 @@ func (s *server) fetchNodeAdvertisedAddrs(ctx context.Context, return nil, err } - node, err := s.graphDB.FetchNode(ctx, vertex) + node, err := s.v1Graph.FetchNode(ctx, vertex) if err != nil { return nil, err } @@ -5618,7 +5625,7 @@ func (s *server) setSelfNode(ctx context.Context, nodePub route.Vertex, nodeLastUpdate = time.Now() ) - srcNode, err := s.graphDB.SourceNode(ctx) + srcNode, err := s.v1Graph.SourceNode(ctx) switch { case err == nil: // If we have a source node persisted in the DB already, then we diff --git a/sqldb/migrations.go b/sqldb/migrations.go index 38535d8b9..28d45f37f 100644 --- a/sqldb/migrations.go +++ b/sqldb/migrations.go @@ -92,6 +92,11 @@ var ( // schema. This is optional and can be disabled by the // user if necessary. }, + { + Name: "000009_graph_v2_columns", + Version: 11, + SchemaVersion: 9, + }, }, migrationAdditions...) // ErrMigrationMismatch is returned when a migrated record does not diff --git a/sqldb/migrations_test.go b/sqldb/migrations_test.go index 063f810b0..536ba8971 100644 --- a/sqldb/migrations_test.go +++ b/sqldb/migrations_test.go @@ -452,132 +452,6 @@ func TestCustomMigration(t *testing.T) { } } -// TestSchemaMigrationIdempotency tests that the our schema migrations are -// idempotent. This means that we can apply the migrations multiple times and -// the schema version will always be the same. -func TestSchemaMigrationIdempotency(t *testing.T) { - dropMigrationTrackerEntries := func(t *testing.T, db *BaseDB) { - _, err := db.Exec("DELETE FROM migration_tracker;") - require.NoError(t, err) - } - - lastMigration := migrationConfig[len(migrationConfig)-1] - - t.Run("SQLite", func(t *testing.T) { - // First instantiate the database and run the migrations - // including the custom migrations. - t.Logf("Creating new SQLite DB for testing migrations") - - dbFileName := filepath.Join(t.TempDir(), "tmp.db") - var ( - db *SqliteStore - err error - ) - - // Run the migration 3 times to test that the migrations - // are idempotent. - for i := 0; i < 3; i++ { - db, err = NewSqliteStore(&SqliteConfig{ - SkipMigrations: false, - }, dbFileName) - require.NoError(t, err) - - dbToCleanup := db.DB - t.Cleanup(func() { - require.NoError( - t, dbToCleanup.Close(), - ) - }) - - ctxb := t.Context() - require.NoError( - t, db.ApplyAllMigrations(ctxb, GetMigrations()), - ) - - version, dirty, err := db.GetSchemaVersion() - require.NoError(t, err) - - // Now reset the schema version to 0 and make sure that - // we can apply the migrations again. - require.Equal(t, lastMigration.SchemaVersion, version) - require.False(t, dirty) - - require.NoError( - t, db.SetSchemaVersion( - database.NilVersion, false, - ), - ) - dropMigrationTrackerEntries(t, db.BaseDB) - - // Make sure that we reset the schema version. - version, dirty, err = db.GetSchemaVersion() - require.NoError(t, err) - require.Equal(t, -1, version) - require.False(t, dirty) - } - }) - - t.Run("Postgres", func(t *testing.T) { - // First create a temporary Postgres database to run - // the migrations on. - fixture := NewTestPgFixture( - t, DefaultPostgresFixtureLifetime, - ) - t.Cleanup(func() { - fixture.TearDown(t) - }) - - dbName := randomDBName(t) - - // Next instantiate the database and run the migrations - // including the custom migrations. - t.Logf("Creating new Postgres DB '%s' for testing "+ - "migrations", dbName) - - _, err := fixture.db.ExecContext( - t.Context(), "CREATE DATABASE "+dbName, - ) - require.NoError(t, err) - - cfg := fixture.GetConfig(dbName) - var db *PostgresStore - - // Run the migration 3 times to test that the migrations - // are idempotent. - for i := 0; i < 3; i++ { - cfg.SkipMigrations = false - db, err = NewPostgresStore(cfg) - require.NoError(t, err) - - ctxb := t.Context() - require.NoError( - t, db.ApplyAllMigrations(ctxb, GetMigrations()), - ) - - version, dirty, err := db.GetSchemaVersion() - require.NoError(t, err) - - // Now reset the schema version to 0 and make sure that - // we can apply the migrations again. - require.Equal(t, lastMigration.SchemaVersion, version) - require.False(t, dirty) - - require.NoError( - t, db.SetSchemaVersion( - database.NilVersion, false, - ), - ) - dropMigrationTrackerEntries(t, db.BaseDB) - - // Make sure that we reset the schema version. - version, dirty, err = db.GetSchemaVersion() - require.NoError(t, err) - require.Equal(t, -1, version) - require.False(t, dirty) - } - }) -} - // TestMigrationBug19RC1 tests a bug that was present in the migration code // at the v0.19.0-rc1 release. // The bug was fixed in: https://github.com/lightningnetwork/lnd/pull/9647 diff --git a/sqldb/sqlc/graph.sql.go b/sqldb/sqlc/graph.sql.go index 0ce7780d5..aa2a04671 100644 --- a/sqldb/sqlc/graph.sql.go +++ b/sqldb/sqlc/graph.sql.go @@ -55,6 +55,22 @@ func (q *Queries) AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofPa ) } +const addV2ChannelProof = `-- name: AddV2ChannelProof :execresult +UPDATE graph_channels +SET signature = $2 +WHERE scid = $1 + AND version = 2 +` + +type AddV2ChannelProofParams struct { + Scid []byte + Signature []byte +} + +func (q *Queries) AddV2ChannelProof(ctx context.Context, arg AddV2ChannelProofParams) (sql.Result, error) { + return q.db.ExecContext(ctx, addV2ChannelProof, arg.Scid, arg.Signature) +} + const countZombieChannels = `-- name: CountZombieChannels :one SELECT COUNT(*) FROM graph_zombie_channels @@ -78,9 +94,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature + bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING id ` @@ -98,6 +114,9 @@ type CreateChannelParams struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte + Signature []byte + FundingPkScript []byte + MerkleRootHash []byte } func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) { @@ -114,6 +133,9 @@ func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (i arg.Node2Signature, arg.Bitcoin1Signature, arg.Bitcoin2Signature, + arg.Signature, + arg.FundingPkScript, + arg.MerkleRootHash, ) var id int64 err := row.Scan(&id) @@ -290,7 +312,7 @@ func (q *Queries) DeleteZombieChannel(ctx context.Context, arg DeleteZombieChann const getChannelAndNodesBySCID = `-- name: GetChannelAndNodesBySCID :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pub_key, n2.pub_key AS node2_pub_key FROM graph_channels c @@ -319,6 +341,9 @@ type GetChannelAndNodesBySCIDRow struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte + Signature []byte + FundingPkScript []byte + MerkleRootHash []byte Node1PubKey []byte Node2PubKey []byte } @@ -340,6 +365,9 @@ func (q *Queries) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAn &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, + &i.Signature, + &i.FundingPkScript, + &i.MerkleRootHash, &i.Node1PubKey, &i.Node2PubKey, ) @@ -348,7 +376,7 @@ func (q *Queries) GetChannelAndNodesBySCID(ctx context.Context, arg GetChannelAn const getChannelByOutpointWithPolicies = `-- name: GetChannelByOutpointWithPolicies :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -369,6 +397,8 @@ SELECT cp1.message_flags AS policy_1_message_flags, cp1.channel_flags AS policy_1_channel_flags, cp1.signature AS policy_1_signature, + cp1.block_height AS policy_1_block_height, + cp1.disable_flags AS policy_1_disable_flags, -- Node 2 policy cp2.id AS policy_2_id, @@ -385,7 +415,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy_2_signature + cp2.signature AS policy_2_signature, + cp2.block_height AS policy_2_block_height, + cp2.disable_flags AS policy_2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id JOIN graph_nodes n2 ON c.node_id_2 = n2.id @@ -420,6 +452,8 @@ type GetChannelByOutpointWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -435,6 +469,8 @@ type GetChannelByOutpointWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetChannelByOutpointWithPoliciesParams) (GetChannelByOutpointWithPoliciesRow, error) { @@ -454,6 +490,9 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -471,6 +510,8 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -486,12 +527,14 @@ func (q *Queries) GetChannelByOutpointWithPolicies(ctx context.Context, arg GetC &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ) return i, err } const getChannelBySCID = `-- name: GetChannelBySCID :one -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash FROM graph_channels WHERE scid = $1 AND version = $2 ` @@ -517,15 +560,18 @@ func (q *Queries) GetChannelBySCID(ctx context.Context, arg GetChannelBySCIDPara &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, + &i.Signature, + &i.FundingPkScript, + &i.MerkleRootHash, ) return i, err } const getChannelBySCIDWithPolicies = `-- name: GetChannelBySCIDWithPolicies :one SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, -- Policy 1 cp1.id AS policy1_id, @@ -543,6 +589,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -559,7 +607,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -596,6 +646,8 @@ type GetChannelBySCIDWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -611,6 +663,8 @@ type GetChannelBySCIDWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChannelBySCIDWithPoliciesParams) (GetChannelBySCIDWithPoliciesRow, error) { @@ -630,6 +684,9 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -637,6 +694,7 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, + &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -644,6 +702,7 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, + &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -659,6 +718,8 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -674,6 +735,8 @@ func (q *Queries) GetChannelBySCIDWithPolicies(ctx context.Context, arg GetChann &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ) return i, err } @@ -764,7 +827,7 @@ func (q *Queries) GetChannelFeaturesBatch(ctx context.Context, chanIds []int64) } const getChannelPolicyByChannelAndNode = `-- name: GetChannelPolicyByChannelAndNode :one -SELECT id, version, channel_id, node_id, timelock, fee_ppm, base_fee_msat, min_htlc_msat, max_htlc_msat, last_update, disabled, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, signature +SELECT id, version, channel_id, node_id, timelock, fee_ppm, base_fee_msat, min_htlc_msat, max_htlc_msat, last_update, disabled, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, signature, block_height, disable_flags FROM graph_channel_policies WHERE channel_id = $1 AND node_id = $2 @@ -797,6 +860,8 @@ func (q *Queries) GetChannelPolicyByChannelAndNode(ctx context.Context, arg GetC &i.MessageFlags, &i.ChannelFlags, &i.Signature, + &i.BlockHeight, + &i.DisableFlags, ) return i, err } @@ -852,7 +917,7 @@ func (q *Queries) GetChannelPolicyExtraTypesBatch(ctx context.Context, policyIds const getChannelsByIDs = `-- name: GetChannelsByIDs :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, -- Minimal node data. n1.id AS node1_id, @@ -876,6 +941,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -892,7 +959,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -925,6 +994,8 @@ type GetChannelsByIDsRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -940,6 +1011,8 @@ type GetChannelsByIDsRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChannelsByIDsRow, error) { @@ -975,6 +1048,9 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1ID, &i.Node1PubKey, &i.Node2ID, @@ -994,6 +1070,8 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1009,6 +1087,8 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1025,7 +1105,7 @@ func (q *Queries) GetChannelsByIDs(ctx context.Context, ids []int64) ([]GetChann const getChannelsByOutpoints = `-- name: GetChannelsByOutpoints :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey FROM graph_channels c @@ -1074,6 +1154,9 @@ func (q *Queries) GetChannelsByOutpoints(ctx context.Context, outpoints []string &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, ); err != nil { @@ -1092,9 +1175,9 @@ func (q *Queries) GetChannelsByOutpoints(ctx context.Context, outpoints []string const getChannelsByPolicyLastUpdateRange = `-- name: GetChannelsByPolicyLastUpdateRange :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, -- Policy 1 (node_id_1) cp1.id AS policy1_id, @@ -1112,6 +1195,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 (node_id_2) cp2.id AS policy2_id, @@ -1128,7 +1213,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1197,6 +1284,8 @@ type GetChannelsByPolicyLastUpdateRangeRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -1212,6 +1301,8 @@ type GetChannelsByPolicyLastUpdateRangeRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg GetChannelsByPolicyLastUpdateRangeParams) ([]GetChannelsByPolicyLastUpdateRangeRow, error) { @@ -1244,6 +1335,9 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -1251,6 +1345,7 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, + &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -1258,6 +1353,7 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, + &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -1273,6 +1369,8 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1288,6 +1386,8 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1303,7 +1403,7 @@ func (q *Queries) GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg Ge } const getChannelsBySCIDRange = `-- name: GetChannelsBySCIDRange :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pub_key, n2.pub_key AS node2_pub_key FROM graph_channels c @@ -1347,6 +1447,9 @@ func (q *Queries) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsByS &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1PubKey, &i.Node2PubKey, ); err != nil { @@ -1365,9 +1468,9 @@ func (q *Queries) GetChannelsBySCIDRange(ctx context.Context, arg GetChannelsByS const getChannelsBySCIDWithPolicies = `-- name: GetChannelsBySCIDWithPolicies :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, - n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, - n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, + n1.id, n1.version, n1.pub_key, n1.alias, n1.last_update, n1.color, n1.signature, n1.block_height, + n2.id, n2.version, n2.pub_key, n2.alias, n2.last_update, n2.color, n2.signature, n2.block_height, -- Policy 1 cp1.id AS policy1_id, @@ -1385,6 +1488,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -1401,7 +1506,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1439,6 +1546,8 @@ type GetChannelsBySCIDWithPoliciesRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -1454,6 +1563,8 @@ type GetChannelsBySCIDWithPoliciesRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChannelsBySCIDWithPoliciesParams) ([]GetChannelsBySCIDWithPoliciesRow, error) { @@ -1490,6 +1601,9 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.GraphNode.ID, &i.GraphNode.Version, &i.GraphNode.PubKey, @@ -1497,6 +1611,7 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphNode.LastUpdate, &i.GraphNode.Color, &i.GraphNode.Signature, + &i.GraphNode.BlockHeight, &i.GraphNode_2.ID, &i.GraphNode_2.Version, &i.GraphNode_2.PubKey, @@ -1504,6 +1619,7 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.GraphNode_2.LastUpdate, &i.GraphNode_2.Color, &i.GraphNode_2.Signature, + &i.GraphNode_2.BlockHeight, &i.Policy1ID, &i.Policy1NodeID, &i.Policy1Version, @@ -1519,6 +1635,8 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -1534,6 +1652,8 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -1549,7 +1669,7 @@ func (q *Queries) GetChannelsBySCIDWithPolicies(ctx context.Context, arg GetChan } const getChannelsBySCIDs = `-- name: GetChannelsBySCIDs :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature FROM graph_channels +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash FROM graph_channels WHERE version = $1 AND scid IN (/*SLICE:scids*/?) ` @@ -1593,6 +1713,9 @@ func (q *Queries) GetChannelsBySCIDs(ctx context.Context, arg GetChannelsBySCIDs &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, + &i.Signature, + &i.FundingPkScript, + &i.MerkleRootHash, ); err != nil { return nil, err } @@ -1756,7 +1879,7 @@ func (q *Queries) GetNodeAddressesBatch(ctx context.Context, ids []int64) ([]Gra } const getNodeByPubKey = `-- name: GetNodeByPubKey :one -SELECT id, version, pub_key, alias, last_update, color, signature +SELECT id, version, pub_key, alias, last_update, color, signature, block_height FROM graph_nodes WHERE pub_key = $1 AND version = $2 @@ -1778,6 +1901,7 @@ func (q *Queries) GetNodeByPubKey(ctx context.Context, arg GetNodeByPubKeyParams &i.LastUpdate, &i.Color, &i.Signature, + &i.BlockHeight, ) return i, err } @@ -1947,7 +2071,7 @@ func (q *Queries) GetNodeIDByPubKey(ctx context.Context, arg GetNodeIDByPubKeyPa } const getNodesByIDs = `-- name: GetNodesByIDs :many -SELECT id, version, pub_key, alias, last_update, color, signature +SELECT id, version, pub_key, alias, last_update, color, signature, block_height FROM graph_nodes WHERE id IN (/*SLICE:ids*/?) ` @@ -1979,6 +2103,7 @@ func (q *Queries) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, &i.LastUpdate, &i.Color, &i.Signature, + &i.BlockHeight, ); err != nil { return nil, err } @@ -1994,7 +2119,7 @@ func (q *Queries) GetNodesByIDs(ctx context.Context, ids []int64) ([]GraphNode, } const getNodesByLastUpdateRange = `-- name: GetNodesByLastUpdateRange :many -SELECT id, version, pub_key, alias, last_update, color, signature +SELECT id, version, pub_key, alias, last_update, color, signature, block_height FROM graph_nodes WHERE last_update >= $1 AND last_update <= $2 @@ -2020,7 +2145,7 @@ WHERE last_update >= $1 SELECT 1 FROM graph_channels c WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) ) ) @@ -2061,6 +2186,7 @@ func (q *Queries) GetNodesByLastUpdateRange(ctx context.Context, arg GetNodesByL &i.LastUpdate, &i.Color, &i.Signature, + &i.BlockHeight, ); err != nil { return nil, err } @@ -2143,9 +2269,9 @@ func (q *Queries) GetPruneTip(ctx context.Context) (GraphPruneLog, error) { } const getPublicV1ChannelsBySCID = `-- name: GetPublicV1ChannelsBySCID :many -SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature +SELECT id, version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash FROM graph_channels -WHERE node_1_signature IS NOT NULL +WHERE COALESCE(length(node_1_signature), 0) > 0 AND scid >= $1 AND scid < $2 ` @@ -2178,6 +2304,9 @@ func (q *Queries) GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1 &i.Node2Signature, &i.Bitcoin1Signature, &i.Bitcoin2Signature, + &i.Signature, + &i.FundingPkScript, + &i.MerkleRootHash, ); err != nil { return nil, err } @@ -2663,14 +2792,14 @@ SELECT EXISTS ( -- one of the signatures since we only ever set them -- together. WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 AND n.pub_key = $1 UNION ALL SELECT 1 FROM graph_channels c JOIN graph_nodes n ON n.id = c.node_id_2 WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 AND n.pub_key = $1 ) ` @@ -2682,6 +2811,36 @@ func (q *Queries) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, erro return exists, err } +const isPublicV2Node = `-- name: IsPublicV2Node :one +SELECT EXISTS ( + SELECT 1 + FROM graph_channels c + JOIN graph_nodes n ON n.id = c.node_id_1 + -- NOTE: we hard-code the version here since the clauses + -- here that determine if a node is public is specific + -- to the V2 gossip protocol. + WHERE c.version = 2 + AND COALESCE(length(c.signature), 0) > 0 + AND n.pub_key = $1 + + UNION ALL + + SELECT 1 + FROM graph_channels c + JOIN graph_nodes n ON n.id = c.node_id_2 + WHERE c.version = 2 + AND COALESCE(length(c.signature), 0) > 0 + AND n.pub_key = $1 +) +` + +func (q *Queries) IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) { + row := q.db.QueryRowContext(ctx, isPublicV2Node, pubKey) + var exists bool + err := row.Scan(&exists) + return exists, err +} + const isZombieChannel = `-- name: IsZombieChannel :one SELECT EXISTS ( SELECT 1 @@ -2704,7 +2863,7 @@ func (q *Queries) IsZombieChannel(ctx context.Context, arg IsZombieChannelParams } const listChannelsByNodeID = `-- name: ListChannelsByNodeID :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -2727,6 +2886,8 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -2743,7 +2904,9 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -2780,6 +2943,8 @@ type ListChannelsByNodeIDRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -2795,6 +2960,8 @@ type ListChannelsByNodeIDRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error) { @@ -2820,6 +2987,9 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -2837,6 +3007,8 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -2852,6 +3024,8 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -2867,7 +3041,7 @@ func (q *Queries) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNo } const listChannelsForNodeIDs = `-- name: ListChannelsForNodeIDs :many -SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, +SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, n1.pub_key AS node1_pubkey, n2.pub_key AS node2_pubkey, @@ -2890,6 +3064,8 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -2906,7 +3082,9 @@ SELECT c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -2945,6 +3123,8 @@ type ListChannelsForNodeIDsRow struct { Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 Policy1Signature []byte + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 Policy2Version sql.NullInt16 @@ -2960,6 +3140,8 @@ type ListChannelsForNodeIDsRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error) { @@ -3004,6 +3186,9 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -3021,6 +3206,8 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.Policy1MessageFlags, &i.Policy1ChannelFlags, &i.Policy1Signature, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2ID, &i.Policy2NodeID, &i.Policy2Version, @@ -3036,6 +3223,8 @@ func (q *Queries) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsFo &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3120,6 +3309,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Node 2 policy cp2.timelock AS policy_2_timelock, @@ -3131,7 +3322,9 @@ SELECT cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags + cp2.channel_flags AS policy2_channel_flags, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -3167,6 +3360,8 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct { Policy1InboundFeeRateMilliMsat sql.NullInt64 Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy2Timelock sql.NullInt32 Policy2FeePpm sql.NullInt64 Policy2BaseFeeMsat sql.NullInt64 @@ -3177,6 +3372,8 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct { Policy2InboundFeeRateMilliMsat sql.NullInt64 Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg ListChannelsWithPoliciesForCachePaginatedParams) ([]ListChannelsWithPoliciesForCachePaginatedRow, error) { @@ -3204,6 +3401,8 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, &i.Policy1InboundFeeRateMilliMsat, &i.Policy1MessageFlags, &i.Policy1ChannelFlags, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy2Timelock, &i.Policy2FeePpm, &i.Policy2BaseFeeMsat, @@ -3214,6 +3413,8 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, &i.Policy2InboundFeeRateMilliMsat, &i.Policy2MessageFlags, &i.Policy2ChannelFlags, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3230,7 +3431,7 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context, const listChannelsWithPoliciesPaginated = `-- name: ListChannelsWithPoliciesPaginated :many SELECT - c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, + c.id, c.version, c.scid, c.node_id_1, c.node_id_2, c.outpoint, c.capacity, c.bitcoin_key_1, c.bitcoin_key_2, c.node_1_signature, c.node_2_signature, c.bitcoin_1_signature, c.bitcoin_2_signature, c.signature, c.funding_pk_script, c.merkle_root_hash, -- Join node pubkeys n1.pub_key AS node1_pubkey, @@ -3251,6 +3452,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, cp1.signature AS policy_1_signature, -- Node 2 policy @@ -3268,7 +3471,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy_2_signature + cp2.signature AS policy_2_signature, + cp2.block_height AS policy_2_block_height, + cp2.disable_flags AS policy_2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -3306,6 +3511,8 @@ type ListChannelsWithPoliciesPaginatedRow struct { Policy1InboundFeeRateMilliMsat sql.NullInt64 Policy1MessageFlags sql.NullInt16 Policy1ChannelFlags sql.NullInt16 + Policy1BlockHeight sql.NullInt64 + Policy1DisableFlags sql.NullInt16 Policy1Signature []byte Policy2ID sql.NullInt64 Policy2NodeID sql.NullInt64 @@ -3322,6 +3529,8 @@ type ListChannelsWithPoliciesPaginatedRow struct { Policy2MessageFlags sql.NullInt16 Policy2ChannelFlags sql.NullInt16 Policy2Signature []byte + Policy2BlockHeight sql.NullInt64 + Policy2DisableFlags sql.NullInt16 } func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg ListChannelsWithPoliciesPaginatedParams) ([]ListChannelsWithPoliciesPaginatedRow, error) { @@ -3347,6 +3556,9 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.GraphChannel.Node2Signature, &i.GraphChannel.Bitcoin1Signature, &i.GraphChannel.Bitcoin2Signature, + &i.GraphChannel.Signature, + &i.GraphChannel.FundingPkScript, + &i.GraphChannel.MerkleRootHash, &i.Node1Pubkey, &i.Node2Pubkey, &i.Policy1ID, @@ -3363,6 +3575,8 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.Policy1InboundFeeRateMilliMsat, &i.Policy1MessageFlags, &i.Policy1ChannelFlags, + &i.Policy1BlockHeight, + &i.Policy1DisableFlags, &i.Policy1Signature, &i.Policy2ID, &i.Policy2NodeID, @@ -3379,6 +3593,8 @@ func (q *Queries) ListChannelsWithPoliciesPaginated(ctx context.Context, arg Lis &i.Policy2MessageFlags, &i.Policy2ChannelFlags, &i.Policy2Signature, + &i.Policy2BlockHeight, + &i.Policy2DisableFlags, ); err != nil { return nil, err } @@ -3436,7 +3652,7 @@ func (q *Queries) ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndP } const listNodesPaginated = `-- name: ListNodesPaginated :many -SELECT id, version, pub_key, alias, last_update, color, signature +SELECT id, version, pub_key, alias, last_update, color, signature, block_height FROM graph_nodes WHERE version = $1 AND id > $2 ORDER BY id @@ -3466,6 +3682,7 @@ func (q *Queries) ListNodesPaginated(ctx context.Context, arg ListNodesPaginated &i.LastUpdate, &i.Color, &i.Signature, + &i.BlockHeight, ); err != nil { return nil, err } @@ -3480,6 +3697,27 @@ func (q *Queries) ListNodesPaginated(ctx context.Context, arg ListNodesPaginated return items, nil } +const nodeExists = `-- name: NodeExists :one +SELECT EXISTS ( + SELECT 1 + FROM graph_nodes + WHERE pub_key = $1 + AND version = $2 +) AS node_exists +` + +type NodeExistsParams struct { + PubKey []byte + Version int16 +} + +func (q *Queries) NodeExists(ctx context.Context, arg NodeExistsParams) (bool, error) { + row := q.db.QueryRowContext(ctx, nodeExists, arg.PubKey, arg.Version) + var node_exists bool + err := row.Scan(&node_exists) + return node_exists, err +} + const upsertChanPolicyExtraType = `-- name: UpsertChanPolicyExtraType :exec /* ───────────────────────────────────────────── graph_channel_policy_extra_types table queries @@ -3544,9 +3782,9 @@ INSERT INTO graph_channel_policies ( base_fee_msat, min_htlc_msat, last_update, disabled, max_htlc_msat, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature + signature, block_height, disable_flags ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 ) ON CONFLICT (channel_id, node_id, version) -- Update the following fields if a conflict occurs on channel_id, @@ -3563,8 +3801,21 @@ ON CONFLICT (channel_id, node_id, version) inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature -WHERE EXCLUDED.last_update > graph_channel_policies.last_update + signature = EXCLUDED.signature, + block_height = EXCLUDED.block_height, + disable_flags = EXCLUDED.disable_flags +WHERE ( + EXCLUDED.version = 1 AND ( + graph_channel_policies.last_update IS NULL + OR EXCLUDED.last_update > graph_channel_policies.last_update + ) +) +OR ( + EXCLUDED.version = 2 AND ( + graph_channel_policies.block_height IS NULL + OR EXCLUDED.block_height >= graph_channel_policies.block_height + ) +) RETURNING id ` @@ -3584,6 +3835,8 @@ type UpsertEdgePolicyParams struct { MessageFlags sql.NullInt16 ChannelFlags sql.NullInt16 Signature []byte + BlockHeight sql.NullInt64 + DisableFlags sql.NullInt16 } func (q *Queries) UpsertEdgePolicy(ctx context.Context, arg UpsertEdgePolicyParams) (int64, error) { @@ -3603,6 +3856,8 @@ func (q *Queries) UpsertEdgePolicy(ctx context.Context, arg UpsertEdgePolicyPara arg.MessageFlags, arg.ChannelFlags, arg.Signature, + arg.BlockHeight, + arg.DisableFlags, ) var id int64 err := row.Scan(&id) @@ -3616,9 +3871,9 @@ const upsertNode = `-- name: UpsertNode :one */ INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature + version, pub_key, alias, last_update, block_height, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -3626,20 +3881,24 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, + block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature -WHERE graph_nodes.last_update IS NULL - OR EXCLUDED.last_update > graph_nodes.last_update +WHERE (graph_nodes.last_update IS NULL + OR EXCLUDED.last_update > graph_nodes.last_update) +AND (graph_nodes.block_height IS NULL + OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id ` type UpsertNodeParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + BlockHeight sql.NullInt64 + Color sql.NullString + Signature []byte } func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (int64, error) { @@ -3648,6 +3907,7 @@ func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (int64, arg.PubKey, arg.Alias, arg.LastUpdate, + arg.BlockHeight, arg.Color, arg.Signature, ) @@ -3744,9 +4004,9 @@ func (q *Queries) UpsertPruneLogEntry(ctx context.Context, arg UpsertPruneLogEnt const upsertSourceNode = `-- name: UpsertSourceNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature + version, pub_key, alias, last_update, block_height, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -3754,20 +4014,24 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, + block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature WHERE graph_nodes.last_update IS NULL OR EXCLUDED.last_update >= graph_nodes.last_update +AND (graph_nodes.block_height IS NULL + OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id ` type UpsertSourceNodeParams struct { - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + BlockHeight sql.NullInt64 + Color sql.NullString + Signature []byte } // We use a separate upsert for our own node since we want to be less strict @@ -3779,6 +4043,7 @@ func (q *Queries) UpsertSourceNode(ctx context.Context, arg UpsertSourceNodePara arg.PubKey, arg.Alias, arg.LastUpdate, + arg.BlockHeight, arg.Color, arg.Signature, ) diff --git a/sqldb/sqlc/migrations/000009_graph_v2.down.sql b/sqldb/sqlc/migrations/000009_graph_v2.down.sql new file mode 100644 index 000000000..b566e4016 --- /dev/null +++ b/sqldb/sqlc/migrations/000009_graph_v2.down.sql @@ -0,0 +1,17 @@ +-- Remove the block_height column from graph_nodes +ALTER TABLE graph_nodes DROP COLUMN block_height; + +-- Remove the signature column from graph_channels +ALTER TABLE graph_channels DROP COLUMN signature; + +-- Remove the funding_pk_script column from graph_channels +ALTER TABLE graph_channels DROP COLUMN funding_pk_script; + +-- Remove the merkle_root_hash column from graph_channels +ALTER TABLE graph_channels DROP COLUMN merkle_root_hash; + +-- Remove the block_height column from graph_channel_policies +ALTER TABLE graph_channel_policies DROP COLUMN block_height; + +-- Remove the disable_flags column from graph_channel_policies +ALTER TABLE graph_channel_policies DROP COLUMN disable_flags; \ No newline at end of file diff --git a/sqldb/sqlc/migrations/000009_graph_v2.up.sql b/sqldb/sqlc/migrations/000009_graph_v2.up.sql new file mode 100644 index 000000000..19a3e99f9 --- /dev/null +++ b/sqldb/sqlc/migrations/000009_graph_v2.up.sql @@ -0,0 +1,23 @@ +-- The block height timestamp of this node's latest received node announcement. +-- It may be zero if we have not received a node announcement yet. +ALTER TABLE graph_nodes ADD COLUMN block_height BIGINT; + +-- The signature of the channel announcement. If this is null, then the channel +-- belongs to the source node and the channel has not been announced yet. +ALTER TABLE graph_channels ADD COLUMN signature BLOB; + +-- For v2 channels onwards, we cant necessarily derive the funding pk script +-- from the other fields in the announcement, so we store it here so that +-- we have easy access to it when we want to subscribe to channel closures. +ALTER TABLE graph_channels ADD COLUMN funding_pk_script BLOB; + +-- The optional merkel root hash advertised in the V2 channel announcement. +ALTER TABLE graph_channels ADD COLUMN merkle_root_hash BLOB; + +-- The block height timestamp of this channel's latest received channel-update +-- message (for v2 channel update messages). +ALTER TABLE graph_channel_policies ADD COLUMN block_height BIGINT; + +-- A bitfield describing the disabled flags for a v2 channel update. +ALTER TABLE graph_channel_policies ADD COLUMN disable_flags SMALLINT + CHECK (disable_flags >= 0 AND disable_flags <= 255); \ No newline at end of file diff --git a/sqldb/sqlc/models.go b/sqldb/sqlc/models.go index 24df0d680..359720096 100644 --- a/sqldb/sqlc/models.go +++ b/sqldb/sqlc/models.go @@ -42,6 +42,9 @@ type GraphChannel struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte + Signature []byte + FundingPkScript []byte + MerkleRootHash []byte } type GraphChannelExtraType struct { @@ -72,6 +75,8 @@ type GraphChannelPolicy struct { MessageFlags sql.NullInt16 ChannelFlags sql.NullInt16 Signature []byte + BlockHeight sql.NullInt64 + DisableFlags sql.NullInt16 } type GraphChannelPolicyExtraType struct { @@ -85,13 +90,14 @@ type GraphClosedScid struct { } type GraphNode struct { - ID int64 - Version int16 - PubKey []byte - Alias sql.NullString - LastUpdate sql.NullInt64 - Color sql.NullString - Signature []byte + ID int64 + Version int16 + PubKey []byte + Alias sql.NullString + LastUpdate sql.NullInt64 + Color sql.NullString + Signature []byte + BlockHeight sql.NullInt64 } type GraphNodeAddress struct { diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go index 7b7b06495..d26f845d0 100644 --- a/sqldb/sqlc/querier.go +++ b/sqldb/sqlc/querier.go @@ -13,6 +13,7 @@ import ( type Querier interface { AddSourceNode(ctx context.Context, nodeID int64) error AddV1ChannelProof(ctx context.Context, arg AddV1ChannelProofParams) (sql.Result, error) + AddV2ChannelProof(ctx context.Context, arg AddV2ChannelProofParams) (sql.Result, error) ClearKVInvoiceHashIndex(ctx context.Context) error CountZombieChannels(ctx context.Context, version int16) (int64, error) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) @@ -116,6 +117,7 @@ type Querier interface { InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error) IsClosedChannel(ctx context.Context, scid []byte) (bool, error) IsPublicV1Node(ctx context.Context, pubKey []byte) (bool, error) + IsPublicV2Node(ctx context.Context, pubKey []byte) (bool, error) IsZombieChannel(ctx context.Context, arg IsZombieChannelParams) (bool, error) ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error) ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error) @@ -125,6 +127,7 @@ type Querier interface { ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndPubKeysParams) ([]ListNodeIDsAndPubKeysRow, error) ListNodesPaginated(ctx context.Context, arg ListNodesPaginatedParams) ([]GraphNode, error) NextInvoiceSettleIndex(ctx context.Context) (int64, error) + NodeExists(ctx context.Context, arg NodeExistsParams) (bool, error) OnAMPSubInvoiceCanceled(ctx context.Context, arg OnAMPSubInvoiceCanceledParams) error OnAMPSubInvoiceCreated(ctx context.Context, arg OnAMPSubInvoiceCreatedParams) error OnAMPSubInvoiceSettled(ctx context.Context, arg OnAMPSubInvoiceSettledParams) error diff --git a/sqldb/sqlc/queries/graph.sql b/sqldb/sqlc/queries/graph.sql index a8ff040d9..a4a42a1e3 100644 --- a/sqldb/sqlc/queries/graph.sql +++ b/sqldb/sqlc/queries/graph.sql @@ -5,9 +5,9 @@ -- name: UpsertNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature + version, pub_key, alias, last_update, block_height, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -15,10 +15,13 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, + block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature -WHERE graph_nodes.last_update IS NULL - OR EXCLUDED.last_update > graph_nodes.last_update +WHERE (graph_nodes.last_update IS NULL + OR EXCLUDED.last_update > graph_nodes.last_update) +AND (graph_nodes.block_height IS NULL + OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id; -- We use a separate upsert for our own node since we want to be less strict @@ -26,9 +29,9 @@ RETURNING id; -- update the record even if the last_update is the same as what we have. -- name: UpsertSourceNode :one INSERT INTO graph_nodes ( - version, pub_key, alias, last_update, color, signature + version, pub_key, alias, last_update, block_height, color, signature ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7 ) ON CONFLICT (pub_key, version) -- Update the following fields if a conflict occurs on pub_key @@ -36,10 +39,13 @@ ON CONFLICT (pub_key, version) DO UPDATE SET alias = EXCLUDED.alias, last_update = EXCLUDED.last_update, + block_height = EXCLUDED.block_height, color = EXCLUDED.color, signature = EXCLUDED.signature WHERE graph_nodes.last_update IS NULL OR EXCLUDED.last_update >= graph_nodes.last_update +AND (graph_nodes.block_height IS NULL + OR EXCLUDED.block_height >= graph_nodes.block_height) RETURNING id; -- name: GetNodesByIDs :many @@ -53,6 +59,14 @@ FROM graph_nodes WHERE pub_key = $1 AND version = $2; +-- name: NodeExists :one +SELECT EXISTS ( + SELECT 1 + FROM graph_nodes + WHERE pub_key = $1 + AND version = $2 +) AS node_exists; + -- name: GetNodeIDByPubKey :one SELECT id FROM graph_nodes @@ -87,14 +101,36 @@ SELECT EXISTS ( -- one of the signatures since we only ever set them -- together. WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 AND n.pub_key = $1 UNION ALL SELECT 1 FROM graph_channels c JOIN graph_nodes n ON n.id = c.node_id_2 WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 + AND n.pub_key = $1 +); + +-- name: IsPublicV2Node :one +SELECT EXISTS ( + SELECT 1 + FROM graph_channels c + JOIN graph_nodes n ON n.id = c.node_id_1 + -- NOTE: we hard-code the version here since the clauses + -- here that determine if a node is public is specific + -- to the V2 gossip protocol. + WHERE c.version = 2 + AND COALESCE(length(c.signature), 0) > 0 + AND n.pub_key = $1 + + UNION ALL + + SELECT 1 + FROM graph_channels c + JOIN graph_nodes n ON n.id = c.node_id_2 + WHERE c.version = 2 + AND COALESCE(length(c.signature), 0) > 0 AND n.pub_key = $1 ); @@ -215,7 +251,7 @@ WHERE last_update >= @start_time SELECT 1 FROM graph_channels c WHERE c.version = 1 - AND c.bitcoin_1_signature IS NOT NULL + AND COALESCE(length(c.bitcoin_1_signature), 0) > 0 AND (c.node_id_1 = graph_nodes.id OR c.node_id_2 = graph_nodes.id) ) ) @@ -283,9 +319,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature + bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING id; @@ -298,6 +334,12 @@ SET node_1_signature = $2, WHERE scid = $1 AND version = 1; +-- name: AddV2ChannelProof :execresult +UPDATE graph_channels +SET signature = $2 +WHERE scid = $1 + AND version = 2; + -- name: GetChannelsBySCIDRange :many SELECT sqlc.embed(c), n1.pub_key AS node1_pub_key, @@ -365,6 +407,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -381,7 +425,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -420,6 +466,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -436,7 +484,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -469,6 +519,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 (node_id_2) cp2.id AS policy2_id, @@ -485,7 +537,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -548,6 +602,8 @@ SELECT cp1.message_flags AS policy_1_message_flags, cp1.channel_flags AS policy_1_channel_flags, cp1.signature AS policy_1_signature, + cp1.block_height AS policy_1_block_height, + cp1.disable_flags AS policy_1_disable_flags, -- Node 2 policy cp2.id AS policy_2_id, @@ -564,7 +620,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy_2_signature + cp2.signature AS policy_2_signature, + cp2.block_height AS policy_2_block_height, + cp2.disable_flags AS policy_2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id JOIN graph_nodes n2 ON c.node_id_2 = n2.id @@ -605,6 +663,8 @@ SELECT sqlc.embed(c), cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -621,7 +681,9 @@ SELECT sqlc.embed(c), cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -658,6 +720,8 @@ SELECT sqlc.embed(c), cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -674,7 +738,9 @@ SELECT sqlc.embed(c), cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -689,7 +755,7 @@ WHERE c.version = $1 -- name: GetPublicV1ChannelsBySCID :many SELECT * FROM graph_channels -WHERE node_1_signature IS NOT NULL +WHERE COALESCE(length(node_1_signature), 0) > 0 AND scid >= @start_scid AND scid < @end_scid; @@ -723,6 +789,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, cp1.signature AS policy_1_signature, -- Node 2 policy @@ -740,7 +808,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, cp2.channel_flags AS policy2_channel_flags, - cp2.signature AS policy_2_signature + cp2.signature AS policy_2_signature, + cp2.block_height AS policy_2_block_height, + cp2.disable_flags AS policy_2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -774,6 +844,8 @@ SELECT cp1.inbound_fee_rate_milli_msat AS policy1_inbound_fee_rate_milli_msat, cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Node 2 policy cp2.timelock AS policy_2_timelock, @@ -785,7 +857,9 @@ SELECT cp2.inbound_base_fee_msat AS policy2_inbound_base_fee_msat, cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy2_message_flags, - cp2.channel_flags AS policy2_channel_flags + cp2.channel_flags AS policy2_channel_flags, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -858,9 +932,9 @@ INSERT INTO graph_channel_policies ( base_fee_msat, min_htlc_msat, last_update, disabled, max_htlc_msat, inbound_base_fee_msat, inbound_fee_rate_milli_msat, message_flags, channel_flags, - signature + signature, block_height, disable_flags ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17 ) ON CONFLICT (channel_id, node_id, version) -- Update the following fields if a conflict occurs on channel_id, @@ -877,8 +951,21 @@ ON CONFLICT (channel_id, node_id, version) inbound_fee_rate_milli_msat = EXCLUDED.inbound_fee_rate_milli_msat, message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, - signature = EXCLUDED.signature -WHERE EXCLUDED.last_update > graph_channel_policies.last_update + signature = EXCLUDED.signature, + block_height = EXCLUDED.block_height, + disable_flags = EXCLUDED.disable_flags +WHERE ( + EXCLUDED.version = 1 AND ( + graph_channel_policies.last_update IS NULL + OR EXCLUDED.last_update > graph_channel_policies.last_update + ) +) +OR ( + EXCLUDED.version = 2 AND ( + graph_channel_policies.block_height IS NULL + OR EXCLUDED.block_height >= graph_channel_policies.block_height + ) +) RETURNING id; -- name: GetChannelPolicyByChannelAndNode :one @@ -910,6 +997,8 @@ SELECT cp1.message_flags AS policy1_message_flags, cp1.channel_flags AS policy1_channel_flags, cp1.signature AS policy1_signature, + cp1.block_height AS policy1_block_height, + cp1.disable_flags AS policy1_disable_flags, -- Policy 2 cp2.id AS policy2_id, @@ -926,7 +1015,9 @@ SELECT cp2.inbound_fee_rate_milli_msat AS policy2_inbound_fee_rate_milli_msat, cp2.message_flags AS policy_2_message_flags, cp2.channel_flags AS policy_2_channel_flags, - cp2.signature AS policy2_signature + cp2.signature AS policy2_signature, + cp2.block_height AS policy2_block_height, + cp2.disable_flags AS policy2_disable_flags FROM graph_channels c JOIN graph_nodes n1 ON c.node_id_1 = n1.id @@ -1175,4 +1266,4 @@ ON CONFLICT (channel_id, node_id, version) message_flags = EXCLUDED.message_flags, channel_flags = EXCLUDED.channel_flags, signature = EXCLUDED.signature -RETURNING id; \ No newline at end of file +RETURNING id;