Skip to content

Commit 8682cb5

Browse files
committed
tapdb: extend universe leaf upsert with maybeUpsertSupplyPreCommit
Extend universe leaf upsert with a method to conditionally upsert issuance supply pre-commitment records. This allows a node to record supply pre-commitments when syncing issuance proofs for assets it did not issue. These records are essential for verifying supply commitment transactions.
1 parent 1e481fa commit 8682cb5

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

tapdb/assets_common.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ type UpsertAssetStore interface {
3737
// DB.
3838
UpsertChainTx(ctx context.Context, arg ChainTxParams) (int64, error)
3939

40+
// FetchChainTx fetches a chain tx from the DB.
41+
FetchChainTx(ctx context.Context, txid []byte) (ChainTx, error)
42+
4043
// UpsertGenesisAsset inserts a new or updates an existing genesis asset
4144
// (the base asset info) in the DB, and returns the primary key.
4245
//
@@ -97,6 +100,10 @@ type UpsertAssetStore interface {
97100
// UpsertTapscriptTreeRootHash inserts a new tapscript tree root hash.
98101
UpsertTapscriptTreeRootHash(ctx context.Context,
99102
arg TapscriptTreeRootHash) (int64, error)
103+
104+
// UpsertSupplyPreCommit inserts a new supply pre-commit record.
105+
UpsertSupplyPreCommit(ctx context.Context,
106+
arg sqlc.UpsertSupplyPreCommitParams) (int64, error)
100107
}
101108

102109
var (

tapdb/universe.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/lightninglabs/taproot-assets/proof"
1717
"github.com/lightninglabs/taproot-assets/tapdb/sqlc"
1818
"github.com/lightninglabs/taproot-assets/universe"
19+
"github.com/lightninglabs/taproot-assets/universe/supplyverifier"
1920
lfn "github.com/lightningnetwork/lnd/fn/v2"
2021
"github.com/lightningnetwork/lnd/keychain"
2122
)
@@ -577,6 +578,146 @@ func upsertAssetGen(ctx context.Context, db UpsertAssetStore,
577578
return genAssetID, nil
578579
}
579580

581+
// shouldInsertPreCommit determines whether a supply pre-commitment
582+
// output should be inserted for a given issuance proof.
583+
func shouldInsertPreCommit(proofType universe.ProofType,
584+
issuanceProof proof.Proof, metaReveal *proof.MetaReveal) bool {
585+
586+
// Only issuance proofs can carry supply pre-commitment outputs.
587+
if proofType != universe.ProofTypeIssuance {
588+
return false
589+
}
590+
591+
// Supply pre-commitment outputs apply only to asset groups.
592+
if issuanceProof.Asset.GroupKey == nil {
593+
return false
594+
}
595+
596+
// Without metadata, we can't determine pre-commitment support.
597+
if metaReveal == nil {
598+
return false
599+
}
600+
601+
// If the metadata indicates no supply commitment support, stop here.
602+
if !metaReveal.UniverseCommitments {
603+
return false
604+
}
605+
606+
// A delegation key is mandatory for supply pre-commitment outputs.
607+
if metaReveal.DelegationKey.IsNone() {
608+
return false
609+
}
610+
611+
return true
612+
}
613+
614+
// maybeUpsertSupplyPreCommit inserts a supply pre-commitment output if the
615+
// asset group supports supply commitments and this is an issuance proof.
616+
func maybeUpsertSupplyPreCommit(ctx context.Context, dbTx UpsertAssetStore,
617+
proofType universe.ProofType, issuanceProof proof.Proof,
618+
metaReveal *proof.MetaReveal) error {
619+
620+
if !shouldInsertPreCommit(proofType, issuanceProof, metaReveal) {
621+
return nil
622+
}
623+
624+
delegationKey, err := metaReveal.DelegationKey.UnwrapOrErr(
625+
errors.New("missing delegation key"),
626+
)
627+
if err != nil {
628+
return err
629+
}
630+
631+
preCommitOutput, err := supplyverifier.ExtractPreCommitOutput(
632+
issuanceProof, delegationKey,
633+
)
634+
if err != nil {
635+
return fmt.Errorf("unable to extract pre-commit "+
636+
"output: %w", err)
637+
}
638+
639+
outPointBytes, err := encodeOutpoint(preCommitOutput.OutPoint())
640+
if err != nil {
641+
return fmt.Errorf("unable to encode supply pre-commit "+
642+
"outpoint: %w", err)
643+
}
644+
645+
// Upsert the supply pre-commitment output.
646+
//
647+
// Encode the group key and taproot internal key.
648+
groupKeyBytes := schnorr.SerializePubKey(
649+
&issuanceProof.Asset.GroupKey.GroupPubKey,
650+
)
651+
taprootInternalKeyBytes :=
652+
preCommitOutput.InternalKey.PubKey.SerializeCompressed()
653+
654+
// Try to fetch an existing chain tx row from the database. We fetch
655+
// first instead of blindly upserting to avoid overwriting existing data
656+
// with null values.
657+
var chainTxDbID fn.Option[int64]
658+
659+
txIDBytes := fn.ByteSlice(issuanceProof.AnchorTx.TxHash())
660+
chainTxn, err := dbTx.FetchChainTx(ctx, txIDBytes)
661+
switch {
662+
case errors.Is(err, sql.ErrNoRows):
663+
// No existing chain tx, we'll insert a new one below.
664+
665+
case err != nil:
666+
return fmt.Errorf("unable to fetch chain tx: %w", err)
667+
668+
default:
669+
// We found an existing chain tx. If it has a valid block
670+
// height, then we'll use it. Otherwise, we'll insert a new one
671+
// below.
672+
if chainTxn.BlockHeight.Valid {
673+
chainTxDbID = fn.Some(chainTxn.TxnID)
674+
}
675+
}
676+
677+
// If we didn't find an existing chain tx, then we'll insert a new
678+
// one now.
679+
if chainTxDbID.IsNone() {
680+
blockHash := issuanceProof.BlockHeader.BlockHash()
681+
txBytes, err := fn.Serialize(&issuanceProof.AnchorTx)
682+
if err != nil {
683+
return fmt.Errorf("failed to serialize tx: %w", err)
684+
}
685+
686+
txDbID, err := dbTx.UpsertChainTx(ctx, ChainTxParams{
687+
Txid: txIDBytes,
688+
RawTx: txBytes,
689+
BlockHeight: sqlInt32(issuanceProof.BlockHeight),
690+
BlockHash: blockHash.CloneBytes(),
691+
})
692+
if err != nil {
693+
return fmt.Errorf("unable to upsert chain tx: %w", err)
694+
}
695+
696+
chainTxDbID = fn.Some(txDbID)
697+
}
698+
699+
txDbID, err := chainTxDbID.UnwrapOrErr(
700+
errors.New("missing chain tx db id"),
701+
)
702+
if err != nil {
703+
return err
704+
}
705+
706+
_, err = dbTx.UpsertSupplyPreCommit(
707+
ctx, sqlc.UpsertSupplyPreCommitParams{
708+
TaprootInternalKey: taprootInternalKeyBytes,
709+
GroupKey: groupKeyBytes,
710+
Outpoint: outPointBytes,
711+
ChainTxnDbID: txDbID,
712+
},
713+
)
714+
if err != nil {
715+
return fmt.Errorf("unable to upsert supply pre-commit: %w", err)
716+
}
717+
718+
return nil
719+
}
720+
580721
// UpsertProofLeaf inserts or updates a proof leaf within the universe tree,
581722
// stored at the base key. The metaReveal type is purely optional, and should be
582723
// specified if the genesis proof committed to a non-zero meta hash.
@@ -795,13 +936,27 @@ func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore,
795936
return nil, fmt.Errorf("unable to decode proof: %w", err)
796937
}
797938

939+
// Upsert into the DB: the genesis point, asset genesis,
940+
// group key reveal, and the anchoring transaction for the issuance or
941+
// transfer.
798942
assetGenID, err := upsertAssetGen(
799943
ctx, dbTx, leaf.Genesis, leaf.GroupKey, &leafProof,
800944
)
801945
if err != nil {
802946
return nil, err
803947
}
804948

949+
// If the asset group supports supply commitments and this is an
950+
// issuance proof, then we may need to log the supply pre-commitment
951+
// output.
952+
err = maybeUpsertSupplyPreCommit(
953+
ctx, dbTx, proofType, leafProof, metaReveal,
954+
)
955+
if err != nil {
956+
return nil, fmt.Errorf("unable to upsert supply "+
957+
"pre-commit: %w", err)
958+
}
959+
805960
// If the block height isn't specified, then we'll attempt to extract it
806961
// from the proof itself.
807962
if blockHeight.IsNone() && leafProof.BlockHeight > 0 {

0 commit comments

Comments
 (0)