Merge pull request #10414 from lightningnetwork/elle-g175Prep-base

[g175] graph/db: merge g175 types-prep side branch
This commit is contained in:
Yong authored and GitHub committed 2026-02-14 00:22:59 +08:00
commit f9e3825601
52 files changed
+4676 -2016

No files matched your search

+20 -10
View File
@@ -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(),
+1 -1
View File
@@ -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 {
+2 -4
View File
@@ -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
+59 -77
View File
@@ -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)
+12 -5
View File
@@ -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
@@ -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
+27 -27
View File
@@ -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
+197 -173
View File
@@ -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,
+68 -66
View File
@@ -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 {
+10 -3
View File
@@ -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,
}
}
+368 -41
View File
@@ -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.
+6 -6
View File
@@ -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
+19 -14
View File
@@ -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),
}
+871 -523
View File
File diff suppressed because it is too large. Load diff
+57 -26
View File
@@ -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
+264 -73
View File
@@ -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")
+29 -20
View File
@@ -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,
+93 -11
View File
@@ -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)
}
+358 -8
View File
@@ -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
}
+147
View File
@@ -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)
})
}
+91 -31
View File
@@ -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)
}
+58 -1
View File
@@ -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)
+86 -11
View File
@@ -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,
})
}
+13 -8
View File
@@ -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)
}
+807 -294
View File
File diff suppressed because it is too large. Load diff
+4 -1
View File
@@ -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)
+5 -2
View File
@@ -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 {
+5 -2
View File
@@ -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,
+99 -77
View File
@@ -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,
+2 -2
View File
@@ -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)
+14 -11
View File
@@ -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,
+18 -3
View File
@@ -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,
+16 -2
View File
@@ -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.
+18 -5
View File
@@ -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,
+3 -37
View File
@@ -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
}
+19 -22
View File
@@ -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)
+31 -12
View File
@@ -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
}
+93 -49
View File
@@ -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,
+90 -63
View File
@@ -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())
+47 -44
View File
@@ -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
}
+3 -3
View File
@@ -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)
+2 -2
View File
@@ -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,
}
+20 -18
View File
@@ -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
}
+14 -7
View File
@@ -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
+5
View File
@@ -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
-126
View File
@@ -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
+327 -62
View File
File diff suppressed because it is too large. Load diff
@@ -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;
@@ -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);
+13 -7
View File
@@ -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 {
+3
View File
@@ -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
+117 -26
View File
@@ -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;
RETURNING id;