graph/db: update SQLStore to read and write v2 channels
Extends the SQL store to support v2 (taproot) channel announcements: - Add version validation in AddChannelEdge - Store v2-specific fields: FundingPkScript, MerkleRootHash, Signature - Update buildEdgeInfoWithBatchData to reconstruct v2 channels from DB with optional bitcoin keys and funding script handling - Add WithMerkleRootHash edge modifier for ChannelV2Fields
This commit is contained in:
2 files changed
+147
-58
No files matched your search
@@ -137,6 +137,13 @@ func WithFundingScript(script []byte) EdgeModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// ChannelV1Fields contains the fields that are specific to v1 channel
|
||||||
// announcements.
|
// announcements.
|
||||||
type ChannelV1Fields struct {
|
type ChannelV1Fields struct {
|
||||||
@@ -201,6 +208,14 @@ type ChannelV2Fields struct {
|
|||||||
// BitcoinKey2Bytes is the raw public key of the second node.
|
// BitcoinKey2Bytes is the raw public key of the second node.
|
||||||
BitcoinKey2Bytes fn.Option[route.Vertex]
|
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
|
// ExtraSignedFields is a map of extra fields that are covered by the
|
||||||
// node announcement's signature that we have not explicitly parsed.
|
// node announcement's signature that we have not explicitly parsed.
|
||||||
//
|
//
|
||||||
@@ -219,6 +234,8 @@ func NewV2Channel(chanID uint64, chainHash chainhash.Hash, node1,
|
|||||||
NodeKey2Bytes: node2,
|
NodeKey2Bytes: node2,
|
||||||
BitcoinKey1Bytes: v2Fields.BitcoinKey1Bytes,
|
BitcoinKey1Bytes: v2Fields.BitcoinKey1Bytes,
|
||||||
BitcoinKey2Bytes: v2Fields.BitcoinKey2Bytes,
|
BitcoinKey2Bytes: v2Fields.BitcoinKey2Bytes,
|
||||||
|
FundingScript: v2Fields.FundingScript,
|
||||||
|
MerkleRootHash: v2Fields.MerkleRootHash,
|
||||||
ChannelID: chanID,
|
ChannelID: chanID,
|
||||||
ChainHash: chainHash,
|
ChainHash: chainHash,
|
||||||
Features: lnwire.EmptyFeatureVector(),
|
Features: lnwire.EmptyFeatureVector(),
|
||||||
|
|||||||
+130
-58
@@ -703,6 +703,11 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime, endTime time.Time,
|
|||||||
func (s *SQLStore) AddChannelEdge(ctx context.Context,
|
func (s *SQLStore) AddChannelEdge(ctx context.Context,
|
||||||
edge *models.ChannelEdgeInfo, opts ...batch.SchedulerOption) error {
|
edge *models.ChannelEdgeInfo, opts ...batch.SchedulerOption) error {
|
||||||
|
|
||||||
|
if !isKnownGossipVersion(edge.Version) {
|
||||||
|
return fmt.Errorf("unsupported gossip version: %d",
|
||||||
|
edge.Version)
|
||||||
|
}
|
||||||
|
|
||||||
var alreadyExists bool
|
var alreadyExists bool
|
||||||
r := &batch.Request[SQLQueries]{
|
r := &batch.Request[SQLQueries]{
|
||||||
Opts: batch.NewSchedulerOptions(opts...),
|
Opts: batch.NewSchedulerOptions(opts...),
|
||||||
@@ -720,7 +725,7 @@ func (s *SQLStore) AddChannelEdge(ctx context.Context,
|
|||||||
_, err := tx.GetChannelBySCID(
|
_, err := tx.GetChannelBySCID(
|
||||||
ctx, sqlc.GetChannelBySCIDParams{
|
ctx, sqlc.GetChannelBySCIDParams{
|
||||||
Scid: chanIDB,
|
Scid: chanIDB,
|
||||||
Version: int16(lnwire.GossipVersion1),
|
Version: int16(edge.Version),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -4259,26 +4264,16 @@ func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) {
|
|||||||
func insertChannel(ctx context.Context, db SQLQueries,
|
func insertChannel(ctx context.Context, db SQLQueries,
|
||||||
edge *models.ChannelEdgeInfo) error {
|
edge *models.ChannelEdgeInfo) error {
|
||||||
|
|
||||||
v := lnwire.GossipVersion1
|
v := edge.Version
|
||||||
|
|
||||||
// For now, we only support V1 channel edges in the SQL store.
|
|
||||||
if edge.Version != v {
|
|
||||||
return fmt.Errorf("only V1 channel edges supported, got V%d",
|
|
||||||
edge.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that at least a "shell" entry for each node is present in
|
// Make sure that at least a "shell" entry for each node is present in
|
||||||
// the nodes table.
|
// the nodes table.
|
||||||
node1DBID, err := maybeCreateShellNode(
|
node1DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey1Bytes)
|
||||||
ctx, db, v, edge.NodeKey1Bytes,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create shell node: %w", err)
|
return fmt.Errorf("unable to create shell node: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
node2DBID, err := maybeCreateShellNode(
|
node2DBID, err := maybeCreateShellNode(ctx, db, v, edge.NodeKey2Bytes)
|
||||||
ctx, db, v, edge.NodeKey2Bytes,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create shell node: %w", err)
|
return fmt.Errorf("unable to create shell node: %w", err)
|
||||||
}
|
}
|
||||||
@@ -4302,6 +4297,12 @@ func insertChannel(ctx context.Context, db SQLQueries,
|
|||||||
edge.BitcoinKey2Bytes.WhenSome(func(vertex route.Vertex) {
|
edge.BitcoinKey2Bytes.WhenSome(func(vertex route.Vertex) {
|
||||||
createParams.BitcoinKey2 = vertex[:]
|
createParams.BitcoinKey2 = vertex[:]
|
||||||
})
|
})
|
||||||
|
edge.FundingScript.WhenSome(func(script []byte) {
|
||||||
|
createParams.FundingPkScript = script
|
||||||
|
})
|
||||||
|
edge.MerkleRootHash.WhenSome(func(hash chainhash.Hash) {
|
||||||
|
createParams.MerkleRootHash = hash[:]
|
||||||
|
})
|
||||||
|
|
||||||
if edge.AuthProof != nil {
|
if edge.AuthProof != nil {
|
||||||
proof := edge.AuthProof
|
proof := edge.AuthProof
|
||||||
@@ -4310,6 +4311,7 @@ func insertChannel(ctx context.Context, db SQLQueries,
|
|||||||
createParams.Node2Signature = proof.NodeSig2()
|
createParams.Node2Signature = proof.NodeSig2()
|
||||||
createParams.Bitcoin1Signature = proof.BitcoinSig1()
|
createParams.Bitcoin1Signature = proof.BitcoinSig1()
|
||||||
createParams.Bitcoin2Signature = proof.BitcoinSig2()
|
createParams.Bitcoin2Signature = proof.BitcoinSig2()
|
||||||
|
createParams.Signature = proof.Sig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the new channel record.
|
// Insert the new channel record.
|
||||||
@@ -4333,10 +4335,13 @@ func insertChannel(ctx context.Context, db SQLQueries,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally, insert any extra TLV fields in the channel announcement.
|
// Finally, insert any extra TLV fields in the channel announcement.
|
||||||
extra, err := marshalExtraOpaqueData(edge.ExtraOpaqueData)
|
extra := edge.ExtraSignedFields
|
||||||
if err != nil {
|
if v == lnwire.GossipVersion1 {
|
||||||
return fmt.Errorf("unable to marshal extra opaque data: %w",
|
extra, err = marshalExtraOpaqueData(edge.ExtraOpaqueData)
|
||||||
err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal extra opaque "+
|
||||||
|
"data: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for tlvType, value := range extra {
|
for tlvType, value := range extra {
|
||||||
@@ -4448,9 +4453,9 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash,
|
|||||||
dbChan sqlc.GraphChannel, node1, node2 route.Vertex,
|
dbChan sqlc.GraphChannel, node1, node2 route.Vertex,
|
||||||
batchData *batchChannelData) (*models.ChannelEdgeInfo, error) {
|
batchData *batchChannelData) (*models.ChannelEdgeInfo, error) {
|
||||||
|
|
||||||
if dbChan.Version != int16(lnwire.GossipVersion1) {
|
v := lnwire.GossipVersion(dbChan.Version)
|
||||||
return nil, fmt.Errorf("unsupported channel version: %d",
|
if !isKnownGossipVersion(v) {
|
||||||
dbChan.Version)
|
return nil, fmt.Errorf("unknown channel version: %d", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pre-loaded features and extras types.
|
// Use pre-loaded features and extras types.
|
||||||
@@ -4474,45 +4479,50 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
recs, err := lnwire.CustomRecords(extras).Serialize()
|
// Build the appropriate channel based on version.
|
||||||
if err != nil {
|
var channel *models.ChannelEdgeInfo
|
||||||
return nil, fmt.Errorf("unable to serialize extra signed "+
|
switch v {
|
||||||
"fields: %w", err)
|
case lnwire.GossipVersion1:
|
||||||
}
|
// For v1, serialize extras into ExtraOpaqueData.
|
||||||
if recs == nil {
|
recs, err := lnwire.CustomRecords(extras).Serialize()
|
||||||
recs = make([]byte, 0)
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("unable to serialize extra "+
|
||||||
|
"signed fields: %w", err)
|
||||||
|
}
|
||||||
|
if recs == nil {
|
||||||
|
recs = make([]byte, 0)
|
||||||
|
}
|
||||||
|
|
||||||
btcKey1, err := route.NewVertexFromBytes(dbChan.BitcoinKey1)
|
// Bitcoin keys are required for v1.
|
||||||
if err != nil {
|
btcKey1, err := route.NewVertexFromBytes(dbChan.BitcoinKey1)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
btcKey2, err := route.NewVertexFromBytes(dbChan.BitcoinKey2)
|
}
|
||||||
if err != nil {
|
btcKey2, err := route.NewVertexFromBytes(dbChan.BitcoinKey2)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
channel, err := models.NewV1Channel(
|
channel, err = models.NewV1Channel(
|
||||||
byteOrder.Uint64(dbChan.Scid), chain, node1, node2,
|
byteOrder.Uint64(dbChan.Scid), chain, node1, node2,
|
||||||
&models.ChannelV1Fields{
|
&models.ChannelV1Fields{
|
||||||
BitcoinKey1Bytes: btcKey1,
|
BitcoinKey1Bytes: btcKey1,
|
||||||
BitcoinKey2Bytes: btcKey2,
|
BitcoinKey2Bytes: btcKey2,
|
||||||
ExtraOpaqueData: recs,
|
ExtraOpaqueData: recs,
|
||||||
},
|
},
|
||||||
models.WithChannelPoint(*op),
|
models.WithChannelPoint(*op),
|
||||||
models.WithCapacity(btcutil.Amount(dbChan.Capacity.Int64)),
|
models.WithCapacity(
|
||||||
models.WithFeatures(fv.RawFeatureVector),
|
btcutil.Amount(dbChan.Capacity.Int64),
|
||||||
)
|
),
|
||||||
if err != nil {
|
models.WithFeatures(fv.RawFeatureVector),
|
||||||
return nil, err
|
)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// We always set all the signatures at the same time, so we can
|
// For v1 channels, attach the auth proof if all four
|
||||||
// safely check if one signature is present to determine if we have the
|
// signatures are present.
|
||||||
// rest of the signatures for the auth proof.
|
if len(dbChan.Bitcoin1Signature) > 0 {
|
||||||
if len(dbChan.Bitcoin1Signature) > 0 {
|
|
||||||
// For v1 channels, we have four signatures.
|
|
||||||
if dbChan.Version == int16(lnwire.GossipVersion1) {
|
|
||||||
channel.AuthProof = models.NewV1ChannelAuthProof(
|
channel.AuthProof = models.NewV1ChannelAuthProof(
|
||||||
dbChan.Node1Signature,
|
dbChan.Node1Signature,
|
||||||
dbChan.Node2Signature,
|
dbChan.Node2Signature,
|
||||||
@@ -4520,7 +4530,69 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash,
|
|||||||
dbChan.Bitcoin2Signature,
|
dbChan.Bitcoin2Signature,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO(elle): Add v2 support when needed.
|
|
||||||
|
case lnwire.GossipVersion2:
|
||||||
|
v2Fields := &models.ChannelV2Fields{
|
||||||
|
ExtraSignedFields: extras,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For v2, bitcoin keys are optional.
|
||||||
|
if len(dbChan.BitcoinKey1) > 0 {
|
||||||
|
btcKey1, err := route.NewVertexFromBytes(
|
||||||
|
dbChan.BitcoinKey1,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v2Fields.BitcoinKey1Bytes = fn.Some(btcKey1)
|
||||||
|
}
|
||||||
|
if len(dbChan.BitcoinKey2) > 0 {
|
||||||
|
btcKey2, err := route.NewVertexFromBytes(
|
||||||
|
dbChan.BitcoinKey2,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v2Fields.BitcoinKey2Bytes = fn.Some(btcKey2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse funding script if present.
|
||||||
|
if len(dbChan.FundingPkScript) > 0 {
|
||||||
|
v2Fields.FundingScript = fn.Some(dbChan.FundingPkScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse merkle root hash if present.
|
||||||
|
if len(dbChan.MerkleRootHash) > 0 {
|
||||||
|
var hash chainhash.Hash
|
||||||
|
copy(hash[:], dbChan.MerkleRootHash)
|
||||||
|
v2Fields.MerkleRootHash = fn.Some(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []models.EdgeModifier{
|
||||||
|
models.WithChannelPoint(*op),
|
||||||
|
models.WithCapacity(btcutil.Amount(
|
||||||
|
dbChan.Capacity.Int64,
|
||||||
|
)),
|
||||||
|
models.WithFeatures(fv.RawFeatureVector),
|
||||||
|
}
|
||||||
|
|
||||||
|
// For v2 channels, attach the auth proof if the signature is
|
||||||
|
// present.
|
||||||
|
if len(dbChan.Signature) > 0 {
|
||||||
|
proof := models.NewV2ChannelAuthProof(dbChan.Signature)
|
||||||
|
opts = append(opts, models.WithChanProof(proof))
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err = models.NewV2Channel(
|
||||||
|
byteOrder.Uint64(dbChan.Scid), chain, node1, node2,
|
||||||
|
v2Fields, opts...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported channel version: %d", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return channel, nil
|
return channel, nil
|
||||||
|
|||||||
Reference in new issue
Block a user