From 219ae69a850ebad3f1059753ad557865a19a7dad Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 15:14:47 +0100 Subject: [PATCH 01/16] tapdb: rename unit test functionality for new term "supply" --- tapdb/asset_minting_test.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 9093d309a..595723c5b 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1835,8 +1835,9 @@ func TestTapscriptTreeManager(t *testing.T) { loadTapscriptTreeChecked(t, ctx, assetStore, tree5, tree5Hash) } -// storeMintAnchorUniCommitment stores a mint anchor commitment in the DB. -func storeMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, +// storeMintSupplyPreCommit stores a mint anchor supply pre-commitment in the +// DB. +func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, taprootInternalKey keychain.KeyDescriptor, groupKey []byte) { @@ -1869,9 +1870,10 @@ func storeMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, _ = assetStore.db.ExecTx(ctx, &writeTxOpts, upsertMintAnchorPreCommit) } -// assertMintAnchorUniCommitment is a helper function that reads a mint anchor -// commitment from the DB and asserts that it matches the expected values. -func assertMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, +// assertMintSupplyPreCommit is a helper function that reads a mint anchor +// supply pre-commitment from the DB and asserts that it matches the expected +// values. +func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, preCommitInternalKey keychain.KeyDescriptor, groupPubKeyBytes []byte) { @@ -1914,10 +1916,10 @@ func assertMintAnchorUniCommitment(t *testing.T, assetStore AssetMintingStore, require.Equal(t, groupPubKeyBytes, preCommit.GroupKey) } -// TestUpsertMintAnchorUniCommitment tests the UpsertMintAnchorUniCommitment -// FetchMintAnchorUniCommitment and SQL queries. In particular, it tests that -// upsert works correctly. -func TestUpsertMintAnchorUniCommitment(t *testing.T) { +// TestUpsertMintSupplyPreCommit tests the UpsertSupplyPreCommit and +// FetchSupplyPreCommits SQL queries. In particular, it tests that upsert works +// correctly. +func TestUpsertMintSupplyPreCommit(t *testing.T) { t.Parallel() ctx := context.Background() @@ -1955,13 +1957,13 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { // Upsert a mint anchor commitment for the batch. txOutputIndex := int32(2) - storeMintAnchorUniCommitment( + storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, preCommitInternalKey, groupPubKeyBytes, ) // Retrieve and inspect the mint anchor commitment we just inserted. - assertMintAnchorUniCommitment( + assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, preCommitInternalKey, groupPubKeyBytes, ) @@ -1970,12 +1972,12 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { // overwrite the existing one. internalKey2, _ := test.RandKeyDesc(t) - storeMintAnchorUniCommitment( + storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKeyBytes, ) - assertMintAnchorUniCommitment( + assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKeyBytes, ) @@ -1985,12 +1987,12 @@ func TestUpsertMintAnchorUniCommitment(t *testing.T) { groupPubKey2 := test.RandPubKey(t) groupPubKey2Bytes := groupPubKey2.SerializeCompressed() - storeMintAnchorUniCommitment( + storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKey2Bytes, ) - assertMintAnchorUniCommitment( + assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, groupPubKey2Bytes, ) From a01ea2d46ca029b5eb79b226322da8e884732aac Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 16:32:39 +0100 Subject: [PATCH 02/16] tapdb: rename FetchMintAnchorUniCommitment to FetchMintSupplyPreCommits Rename SQL query to better align with "supply" terminology. --- tapdb/asset_minting.go | 22 +++++++++++----------- tapdb/asset_minting_test.go | 6 +++--- tapdb/sqlc/assets.sql.go | 14 +++++++------- tapdb/sqlc/querier.go | 2 +- tapdb/sqlc/queries/assets.sql | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tapdb/asset_minting.go b/tapdb/asset_minting.go index 16d9a97c1..7d62cd036 100644 --- a/tapdb/asset_minting.go +++ b/tapdb/asset_minting.go @@ -116,9 +116,9 @@ type ( // database ID. ProofUpdateByID = sqlc.UpsertAssetProofByIDParams - // FetchPreCommitParams is a type alias for the params used to fetch - // mint anchor pre-commitments. - FetchPreCommitParams = sqlc.FetchMintAnchorUniCommitmentParams + // FetchMintPreCommitsParams is a type alias for the params used to + // fetch mint anchor supply pre-commitments. + FetchMintPreCommitsParams = sqlc.FetchMintSupplyPreCommitsParams // FetchAssetID is used to fetch the primary key ID of an asset, by // outpoint and tweaked script key. @@ -248,10 +248,10 @@ type PendingAssetStore interface { FetchAssetMetaForAsset(ctx context.Context, assetID []byte) (sqlc.FetchAssetMetaForAssetRow, error) - // FetchMintAnchorUniCommitment fetches mint anchor pre-commitments. - FetchMintAnchorUniCommitment(ctx context.Context, - arg FetchPreCommitParams) ( - []sqlc.FetchMintAnchorUniCommitmentRow, error) + // FetchMintSupplyPreCommits fetches mint anchor pre-commitments. + FetchMintSupplyPreCommits(ctx context.Context, + arg FetchMintPreCommitsParams) ( + []sqlc.FetchMintSupplyPreCommitsRow, error) // UpsertMintAnchorUniCommitment inserts a new or updates an existing // mint anchor uni commitment on disk. @@ -1347,8 +1347,8 @@ func marshalMintingBatch(ctx context.Context, q PendingAssetStore, // the pre-commitment output index from the database. var preCommitOut fn.Option[tapgarden.PreCommitmentOutput] if dbBatch.UniverseCommitments { - fetchRes, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRes, err := q.FetchMintSupplyPreCommits( + ctx, FetchMintPreCommitsParams{ BatchKey: dbBatch.RawKey, }, ) @@ -1545,8 +1545,8 @@ func (a *AssetMintingStore) FetchDelegationKey(ctx context.Context, readOpts := NewAssetStoreReadTx() dbErr := a.db.ExecTx(ctx, &readOpts, func(q PendingAssetStore) error { - fetchRow, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRow, err := q.FetchMintSupplyPreCommits( + ctx, FetchMintPreCommitsParams{ GroupKey: groupKeyBytes, }, ) diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 595723c5b..a58f61640 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1880,10 +1880,10 @@ func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, ctx := context.Background() readOpts := NewAssetStoreReadTx() - var preCommit *sqlc.FetchMintAnchorUniCommitmentRow + var preCommit *sqlc.FetchMintSupplyPreCommitsRow readMintAnchorCommitment := func(q PendingAssetStore) error { - fetchRes, err := q.FetchMintAnchorUniCommitment( - ctx, FetchPreCommitParams{ + fetchRes, err := q.FetchMintSupplyPreCommits( + ctx, FetchMintPreCommitsParams{ BatchKey: batchKey, }, ) diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 10026b20a..a8f2765aa 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1600,7 +1600,7 @@ func (q *Queries) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow return items, nil } -const FetchMintAnchorUniCommitment = `-- name: FetchMintAnchorUniCommitment :many +const FetchMintSupplyPreCommits = `-- name: FetchMintSupplyPreCommits :many SELECT mint_anchor_uni_commitments.id, mint_anchor_uni_commitments.batch_id, @@ -1624,13 +1624,13 @@ WHERE ( ) ` -type FetchMintAnchorUniCommitmentParams struct { +type FetchMintSupplyPreCommitsParams struct { BatchKey []byte GroupKey []byte TaprootInternalKeyRaw []byte } -type FetchMintAnchorUniCommitmentRow struct { +type FetchMintSupplyPreCommitsRow struct { ID int64 BatchID int32 TxOutputIndex int32 @@ -1643,15 +1643,15 @@ type FetchMintAnchorUniCommitmentRow struct { // Fetch records from the mint_anchor_uni_commitments table with optional // filtering. -func (q *Queries) FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) { - rows, err := q.db.QueryContext(ctx, FetchMintAnchorUniCommitment, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) +func (q *Queries) FetchMintSupplyPreCommits(ctx context.Context, arg FetchMintSupplyPreCommitsParams) ([]FetchMintSupplyPreCommitsRow, error) { + rows, err := q.db.QueryContext(ctx, FetchMintSupplyPreCommits, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) if err != nil { return nil, err } defer rows.Close() - var items []FetchMintAnchorUniCommitmentRow + var items []FetchMintSupplyPreCommitsRow for rows.Next() { - var i FetchMintAnchorUniCommitmentRow + var i FetchMintSupplyPreCommitsRow if err := rows.Scan( &i.ID, &i.BatchID, diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index c9cc85d3b..99afcbe90 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -92,7 +92,7 @@ type Querier interface { FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow, error) // Fetch records from the mint_anchor_uni_commitments table with optional // filtering. - FetchMintAnchorUniCommitment(ctx context.Context, arg FetchMintAnchorUniCommitmentParams) ([]FetchMintAnchorUniCommitmentRow, error) + FetchMintSupplyPreCommits(ctx context.Context, arg FetchMintSupplyPreCommitsParams) ([]FetchMintSupplyPreCommitsRow, error) FetchMintingBatch(ctx context.Context, rawKey []byte) (FetchMintingBatchRow, error) FetchMintingBatchesByInverseState(ctx context.Context, batchState int16) ([]FetchMintingBatchesByInverseStateRow, error) FetchMultiverseRoot(ctx context.Context, namespaceRoot string) (FetchMultiverseRootRow, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 9628f3032..2c7d93dcf 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1087,7 +1087,7 @@ ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET outpoint = EXCLUDED.outpoint RETURNING id; --- name: FetchMintAnchorUniCommitment :many +-- name: FetchMintSupplyPreCommits :many -- Fetch records from the mint_anchor_uni_commitments table with optional -- filtering. SELECT From a743144111daee763f383dc1ef25aca034082d4c Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 16:40:59 +0100 Subject: [PATCH 03/16] tapdb: rename query to UpsertMintSupplyPreCommit Rename SQL query UpsertMintAnchorUniCommitment to UpsertMintSupplyPreCommit. This gives room to add UpsertSupplyPreCommit in a later commit. Also, better align with "supply" terminology. --- tapdb/asset_minting.go | 22 +++++++++++----------- tapdb/asset_minting_test.go | 6 +++--- tapdb/sqlc/assets.sql.go | 14 ++++++++------ tapdb/sqlc/querier.go | 2 +- tapdb/sqlc/queries/assets.sql | 8 +++++--- tapdb/supply_commit_test.go | 4 ++-- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/tapdb/asset_minting.go b/tapdb/asset_minting.go index 7d62cd036..53e3e83fb 100644 --- a/tapdb/asset_minting.go +++ b/tapdb/asset_minting.go @@ -132,9 +132,9 @@ type ( // disk. NewAssetMeta = sqlc.UpsertAssetMetaParams - // MintAnchorUniCommitParams wraps the params needed to insert a new - // mint anchor uni commitment on disk. - MintAnchorUniCommitParams = sqlc.UpsertMintAnchorUniCommitmentParams + // UpsertBatchPreCommitParams wraps the params needed to insert a new + // mint batch supply pre-commit on disk. + UpsertBatchPreCommitParams = sqlc.UpsertMintSupplyPreCommitParams ) // PendingAssetStore is a sub-set of the main sqlc.Querier interface that @@ -253,10 +253,10 @@ type PendingAssetStore interface { arg FetchMintPreCommitsParams) ( []sqlc.FetchMintSupplyPreCommitsRow, error) - // UpsertMintAnchorUniCommitment inserts a new or updates an existing - // mint anchor uni commitment on disk. - UpsertMintAnchorUniCommitment(ctx context.Context, - arg MintAnchorUniCommitParams) (int64, error) + // UpsertMintSupplyPreCommit inserts a new or updates an existing + // mint batch supply commit on disk. + UpsertMintSupplyPreCommit(ctx context.Context, + arg UpsertBatchPreCommitParams) (int64, error) } var ( @@ -448,8 +448,8 @@ func insertMintAnchorTx(ctx context.Context, q PendingAssetStore, return fmt.Errorf("unable to encode outpoint: %w", err) } - _, err = q.UpsertMintAnchorUniCommitment( - ctx, MintAnchorUniCommitParams{ + _, err = q.UpsertMintSupplyPreCommit( + ctx, UpsertBatchPreCommitParams{ BatchKey: rawBatchKey, TxOutputIndex: int32(preCommitOut.OutIdx), TaprootInternalKeyID: internalKeyID, @@ -1644,8 +1644,8 @@ func upsertPreCommit(ctx context.Context, q PendingAssetStore, return fmt.Errorf("unable to encode outpoint: %w", err) } - _, err = q.UpsertMintAnchorUniCommitment( - ctx, MintAnchorUniCommitParams{ + _, err = q.UpsertMintSupplyPreCommit( + ctx, UpsertBatchPreCommitParams{ BatchKey: batchKey, TxOutputIndex: int32(preCommit.OutIdx), TaprootInternalKeyID: internalKeyID, diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index a58f61640..2f371e304 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1855,8 +1855,8 @@ func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, }) require.NoError(t, err) - _, err = q.UpsertMintAnchorUniCommitment( - ctx, sqlc.UpsertMintAnchorUniCommitmentParams{ + _, err = q.UpsertMintSupplyPreCommit( + ctx, UpsertBatchPreCommitParams{ BatchKey: batchKey, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, @@ -1916,7 +1916,7 @@ func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, require.Equal(t, groupPubKeyBytes, preCommit.GroupKey) } -// TestUpsertMintSupplyPreCommit tests the UpsertSupplyPreCommit and +// TestUpsertMintSupplyPreCommit tests the UpsertMintSupplyPreCommit and // FetchSupplyPreCommits SQL queries. In particular, it tests that upsert works // correctly. func TestUpsertMintSupplyPreCommit(t *testing.T) { diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index a8f2765aa..05948d93a 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -3237,7 +3237,7 @@ func (q *Queries) UpsertManagedUTXO(ctx context.Context, arg UpsertManagedUTXOPa return utxo_id, err } -const UpsertMintAnchorUniCommitment = `-- name: UpsertMintAnchorUniCommitment :one +const UpsertMintSupplyPreCommit = `-- name: UpsertMintSupplyPreCommit :one WITH target_batch AS ( -- This CTE is used to fetch the ID of a batch, based on the serialized -- internal key associated with the batch. @@ -3246,11 +3246,13 @@ WITH target_batch AS ( WHERE keys.raw_key = $6 ) INSERT INTO mint_anchor_uni_commitments ( - batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint + batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, + outpoint ) VALUES ( (SELECT batch_id FROM target_batch), $1, - $2, $3, $4, $5 + $2, $3, $4, + $5 ) ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET -- The following fields are updated if a conflict occurs. @@ -3260,7 +3262,7 @@ ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET RETURNING id ` -type UpsertMintAnchorUniCommitmentParams struct { +type UpsertMintSupplyPreCommitParams struct { TxOutputIndex int32 TaprootInternalKeyID int64 GroupKey []byte @@ -3272,8 +3274,8 @@ type UpsertMintAnchorUniCommitmentParams struct { // Upsert a record into the mint_anchor_uni_commitments table. // If a record with the same batch ID and tx output index already exists, update // the existing record. Otherwise, insert a new record. -func (q *Queries) UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) { - row := q.db.QueryRowContext(ctx, UpsertMintAnchorUniCommitment, +func (q *Queries) UpsertMintSupplyPreCommit(ctx context.Context, arg UpsertMintSupplyPreCommitParams) (int64, error) { + row := q.db.QueryRowContext(ctx, UpsertMintSupplyPreCommit, arg.TxOutputIndex, arg.TaprootInternalKeyID, arg.GroupKey, diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 99afcbe90..7aeff59e2 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -236,7 +236,7 @@ type Querier interface { // Upsert a record into the mint_anchor_uni_commitments table. // If a record with the same batch ID and tx output index already exists, update // the existing record. Otherwise, insert a new record. - UpsertMintAnchorUniCommitment(ctx context.Context, arg UpsertMintAnchorUniCommitmentParams) (int64, error) + UpsertMintSupplyPreCommit(ctx context.Context, arg UpsertMintSupplyPreCommitParams) (int64, error) UpsertMultiverseLeaf(ctx context.Context, arg UpsertMultiverseLeafParams) (int64, error) UpsertMultiverseRoot(ctx context.Context, arg UpsertMultiverseRootParams) (int64, error) UpsertRootNode(ctx context.Context, arg UpsertRootNodeParams) error diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 2c7d93dcf..80e647d5e 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1062,7 +1062,7 @@ JOIN genesis_assets ON genesis_assets.meta_data_id = assets_meta.meta_id ORDER BY assets_meta.meta_id; --- name: UpsertMintAnchorUniCommitment :one +-- name: UpsertMintSupplyPreCommit :one -- Upsert a record into the mint_anchor_uni_commitments table. -- If a record with the same batch ID and tx output index already exists, update -- the existing record. Otherwise, insert a new record. @@ -1074,11 +1074,13 @@ WITH target_batch AS ( WHERE keys.raw_key = @batch_key ) INSERT INTO mint_anchor_uni_commitments ( - batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint + batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, + outpoint ) VALUES ( (SELECT batch_id FROM target_batch), @tx_output_index, - @taproot_internal_key_id, @group_key, sqlc.narg('spent_by'), sqlc.narg('outpoint') + @taproot_internal_key_id, @group_key, sqlc.narg('spent_by'), + sqlc.narg('outpoint') ) ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET -- The following fields are updated if a conflict occurs. diff --git a/tapdb/supply_commit_test.go b/tapdb/supply_commit_test.go index 095803451..a7f53bf4c 100644 --- a/tapdb/supply_commit_test.go +++ b/tapdb/supply_commit_test.go @@ -242,8 +242,8 @@ func (h *supplyCommitTestHarness) addTestMintAnchorUniCommitment( err = wire.WriteOutPoint(&outpointBuf, 0, 0, &outpoint) require.NoError(h.t, err) - anchorCommitID, err := h.db.UpsertMintAnchorUniCommitment( - h.ctx, sqlc.UpsertMintAnchorUniCommitmentParams{ + anchorCommitID, err := h.db.UpsertMintSupplyPreCommit( + h.ctx, UpsertBatchPreCommitParams{ BatchKey: batchKeyBytes, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, From 54c6bc96ee239465ddf32f574b0b2cb3d2ca6df0 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 11:24:15 +0100 Subject: [PATCH 04/16] tapdb: rename query to FetchUnspentMintSupplyPreCommits Rename SQL query FetchUnspentPrecommits to FetchUnspentMintSupplyPreCommits. This adds the term "Mint" to indicate that these supply pre-commitment entries relate to the local node's minting process. The rename also allows room to add FetchUnspentSupplyPreCommits in a subsequent commit. --- tapdb/sqlc/querier.go | 7 ++++--- tapdb/sqlc/queries/supply_commit.sql | 7 ++++--- tapdb/sqlc/supply_commit.sql.go | 17 +++++++++-------- tapdb/supply_commit.go | 19 +++++++++++-------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 7aeff59e2..78e1642c6 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -114,9 +114,10 @@ type Querier interface { FetchUniverseRoot(ctx context.Context, namespace string) (FetchUniverseRootRow, error) FetchUniverseSupplyRoot(ctx context.Context, namespaceRoot string) (FetchUniverseSupplyRootRow, error) FetchUnknownTypeScriptKeys(ctx context.Context) ([]FetchUnknownTypeScriptKeysRow, error) - // Fetch unspent pre-commitment outputs. A pre-commitment output is a mint - // anchor transaction output which relates to the supply commitment feature. - FetchUnspentPrecommits(ctx context.Context, groupKey []byte) ([]FetchUnspentPrecommitsRow, error) + // Fetch unspent supply pre-commitment outputs. Each pre-commitment output + // comes from a mint anchor transaction and relates to an asset issuance + // where the local node acted as the issuer. + FetchUnspentMintSupplyPreCommits(ctx context.Context, groupKey []byte) ([]FetchUnspentMintSupplyPreCommitsRow, error) FinalizeSupplyCommitTransition(ctx context.Context, transitionID int64) error FreezePendingTransition(ctx context.Context, groupKey []byte) error GenesisAssets(ctx context.Context) ([]GenesisAsset, error) diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 8cccddc73..0065a55c5 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -194,9 +194,10 @@ WHERE transition_id = @transition_id; DELETE FROM supply_update_events WHERE transition_id = @transition_id; --- name: FetchUnspentPrecommits :many --- Fetch unspent pre-commitment outputs. A pre-commitment output is a mint --- anchor transaction output which relates to the supply commitment feature. +-- name: FetchUnspentMintSupplyPreCommits :many +-- Fetch unspent supply pre-commitment outputs. Each pre-commitment output +-- comes from a mint anchor transaction and relates to an asset issuance +-- where the local node acted as the issuer. SELECT mac.tx_output_index, sqlc.embed(ik), diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index d33ae0104..2cfcdc297 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -108,7 +108,7 @@ func (q *Queries) FetchSupplyCommit(ctx context.Context, groupKey []byte) (Fetch return i, err } -const FetchUnspentPrecommits = `-- name: FetchUnspentPrecommits :many +const FetchUnspentMintSupplyPreCommits = `-- name: FetchUnspentMintSupplyPreCommits :many SELECT mac.tx_output_index, ik.key_id, ik.raw_key, ik.key_family, ik.key_index, @@ -127,7 +127,7 @@ WHERE (mac.spent_by IS NULL OR commit_txn.block_hash IS NULL) ` -type FetchUnspentPrecommitsRow struct { +type FetchUnspentMintSupplyPreCommitsRow struct { TxOutputIndex int32 InternalKey InternalKey GroupKey []byte @@ -135,17 +135,18 @@ type FetchUnspentPrecommitsRow struct { RawTx []byte } -// Fetch unspent pre-commitment outputs. A pre-commitment output is a mint -// anchor transaction output which relates to the supply commitment feature. -func (q *Queries) FetchUnspentPrecommits(ctx context.Context, groupKey []byte) ([]FetchUnspentPrecommitsRow, error) { - rows, err := q.db.QueryContext(ctx, FetchUnspentPrecommits, groupKey) +// Fetch unspent supply pre-commitment outputs. Each pre-commitment output +// comes from a mint anchor transaction and relates to an asset issuance +// where the local node acted as the issuer. +func (q *Queries) FetchUnspentMintSupplyPreCommits(ctx context.Context, groupKey []byte) ([]FetchUnspentMintSupplyPreCommitsRow, error) { + rows, err := q.db.QueryContext(ctx, FetchUnspentMintSupplyPreCommits, groupKey) if err != nil { return nil, err } defer rows.Close() - var items []FetchUnspentPrecommitsRow + var items []FetchUnspentMintSupplyPreCommitsRow for rows.Next() { - var i FetchUnspentPrecommitsRow + var i FetchUnspentMintSupplyPreCommitsRow if err := rows.Scan( &i.TxOutputIndex, &i.InternalKey.KeyID, diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 033bb8788..63eacfc89 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -24,9 +24,9 @@ import ( ) type ( - // UnspentPrecommits is an alias for the sqlc type representing an - // unspent pre-commitment row. - UnspentPrecommits = sqlc.FetchUnspentPrecommitsRow + // UnspentMintPreCommits is an alias for the sqlc type representing an + // unspent supply pre-commitment row. + UnspentMintPreCommits = sqlc.FetchUnspentMintSupplyPreCommitsRow // SupplyCommit is an alias for the sqlc type. SupplyCommit = sqlc.FetchSupplyCommitRow @@ -103,10 +103,11 @@ type SupplyCommitStore interface { TreeStore BaseUniverseStore - // FetchUnspentPrecommits fetches all unspent pre-commitments for a - // given group key. - FetchUnspentPrecommits(ctx context.Context, - groupKey []byte) ([]UnspentPrecommits, error) + // FetchUnspentMintSupplyPreCommits fetches all unspent supply + // pre-commitments for the specified asset group key where the local + // node was the issuer. + FetchUnspentMintSupplyPreCommits(ctx context.Context, + groupKey []byte) ([]UnspentMintPreCommits, error) // FetchSupplyCommit fetches the latest confirmed supply commitment for // a given group key. @@ -276,7 +277,9 @@ func (s *SupplyCommitMachine) UnspentPrecommits(ctx context.Context, var preCommits supplycommit.PreCommits readTx := ReadTxOption() dbErr := s.db.ExecTx(ctx, readTx, func(db SupplyCommitStore) error { - rows, err := db.FetchUnspentPrecommits(ctx, groupKeyBytes) + rows, err := db.FetchUnspentMintSupplyPreCommits( + ctx, groupKeyBytes, + ) if err != nil { // It's okay if there are no unspent pre-commits. if errors.Is(err, sql.ErrNoRows) { From 613ffe1e42f3256b76262ad0f67d90207f7e2674 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 11:51:55 +0100 Subject: [PATCH 05/16] tapdb: rename query to MarkMintPreCommitSpentByOutpoint Rename SQL query MarkPreCommitmentSpentByOutpoint to MarkMintPreCommitSpentByOutpoint. This adds the term "Mint" to indicate that these supply pre-commitment entries relate to the local node's minting process. The rename also allows room to add MarkPreCommitSpentByOutpoint in a subsequent commit. --- tapdb/sqlc/querier.go | 6 ++++-- tapdb/sqlc/queries/supply_commit.sql | 6 ++++-- tapdb/sqlc/supply_commit.sql.go | 12 +++++++----- tapdb/supply_commit.go | 13 +++++++------ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 78e1642c6..45c12c67f 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -150,8 +150,10 @@ type Querier interface { LinkDanglingSupplyUpdateEvents(ctx context.Context, arg LinkDanglingSupplyUpdateEventsParams) error LogProofTransferAttempt(ctx context.Context, arg LogProofTransferAttemptParams) error LogServerSync(ctx context.Context, arg LogServerSyncParams) error - // Mark a specific pre-commitment output as spent by its outpoint. - MarkPreCommitmentSpentByOutpoint(ctx context.Context, arg MarkPreCommitmentSpentByOutpointParams) error + // Mark a supply pre-commitment output as spent by its outpoint. The + // pre-commitment corresponds to an asset issuance where the local node acted as + // the issuer. + MarkMintPreCommitSpentByOutpoint(ctx context.Context, arg MarkMintPreCommitSpentByOutpointParams) error NewMintingBatch(ctx context.Context, arg NewMintingBatchParams) error QueryAddr(ctx context.Context, arg QueryAddrParams) (QueryAddrRow, error) // We use a LEFT JOIN here as not every asset has a group key, so this'll diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 0065a55c5..10b3d60b5 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -215,8 +215,10 @@ WHERE mac.group_key = @group_key AND (mac.spent_by IS NULL OR commit_txn.block_hash IS NULL); --- name: MarkPreCommitmentSpentByOutpoint :exec --- Mark a specific pre-commitment output as spent by its outpoint. +-- name: MarkMintPreCommitSpentByOutpoint :exec +-- Mark a supply pre-commitment output as spent by its outpoint. The +-- pre-commitment corresponds to an asset issuance where the local node acted as +-- the issuer. UPDATE mint_anchor_uni_commitments SET spent_by = @spent_by_commit_id WHERE outpoint = @outpoint diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index 2cfcdc297..a6e2800aa 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -311,21 +311,23 @@ func (q *Queries) LinkDanglingSupplyUpdateEvents(ctx context.Context, arg LinkDa return err } -const MarkPreCommitmentSpentByOutpoint = `-- name: MarkPreCommitmentSpentByOutpoint :exec +const MarkMintPreCommitSpentByOutpoint = `-- name: MarkMintPreCommitSpentByOutpoint :exec UPDATE mint_anchor_uni_commitments SET spent_by = $1 WHERE outpoint = $2 AND spent_by IS NULL ` -type MarkPreCommitmentSpentByOutpointParams struct { +type MarkMintPreCommitSpentByOutpointParams struct { SpentByCommitID sql.NullInt64 Outpoint []byte } -// Mark a specific pre-commitment output as spent by its outpoint. -func (q *Queries) MarkPreCommitmentSpentByOutpoint(ctx context.Context, arg MarkPreCommitmentSpentByOutpointParams) error { - _, err := q.db.ExecContext(ctx, MarkPreCommitmentSpentByOutpoint, arg.SpentByCommitID, arg.Outpoint) +// Mark a supply pre-commitment output as spent by its outpoint. The +// pre-commitment corresponds to an asset issuance where the local node acted as +// the issuer. +func (q *Queries) MarkMintPreCommitSpentByOutpoint(ctx context.Context, arg MarkMintPreCommitSpentByOutpointParams) error { + _, err := q.db.ExecContext(ctx, MarkMintPreCommitSpentByOutpoint, arg.SpentByCommitID, arg.Outpoint) return err } diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 63eacfc89..859918ba6 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -213,10 +213,11 @@ type SupplyCommitStore interface { FinalizeSupplyCommitTransition(ctx context.Context, transitionID int64) error - // MarkPreCommitmentSpentByOutpoint marks a pre-commitment as spent - // by its outpoint. - MarkPreCommitmentSpentByOutpoint(ctx context.Context, - arg sqlc.MarkPreCommitmentSpentByOutpointParams) error + // MarkMintPreCommitSpentByOutpoint marks a supply pre-commitment as + // spent by its outpoint. The pre-commitment corresponds to an asset + // issuance where the local node acted as the issuer. + MarkMintPreCommitSpentByOutpoint(ctx context.Context, + arg sqlc.MarkMintPreCommitSpentByOutpointParams) error // QueryExistingPendingTransition fetches the ID of an existing // non-finalized transition for a group key. Returns sql.ErrNoRows if @@ -1792,8 +1793,8 @@ func (s *SupplyCommitMachine) ApplyStateTransition( txIn.PreviousOutPoint.Index) // Mark this specific pre-commitment as spent. - err = db.MarkPreCommitmentSpentByOutpoint(ctx, - sqlc.MarkPreCommitmentSpentByOutpointParams{ + err = db.MarkMintPreCommitSpentByOutpoint(ctx, + sqlc.MarkMintPreCommitSpentByOutpointParams{ SpentByCommitID: sqlInt64( newCommitmentID, ), From dcc3318c0429d66250a8bb907a41b83c321b1619 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 20:22:32 +0100 Subject: [PATCH 06/16] tapdb: rename mint_anchor_uni_commitments to mint_supply_pre_commits Rename SQL table mint_anchor_uni_commitments to mint_supply_pre_commits. This aligns with the "supply" terminology and provides room to add a new supply pre-commits table for assets issued by a remote peer in a subsequent commit. --- tapdb/asset_minting_test.go | 34 ++++++++++---- tapdb/migrations.go | 2 +- tapdb/sqlc/assets.sql.go | 44 ++++++++++-------- .../000046_supply_pre_commit_tables.down.sql | 3 ++ .../000046_supply_pre_commit_tables.up.sql | 3 ++ tapdb/sqlc/models.go | 2 +- tapdb/sqlc/querier.go | 12 +++-- tapdb/sqlc/queries/assets.sql | 46 +++++++++++-------- tapdb/sqlc/queries/supply_commit.sql | 4 +- tapdb/sqlc/schemas/generated_schema.sql | 16 +++---- tapdb/sqlc/supply_commit.sql.go | 4 +- 11 files changed, 104 insertions(+), 66 deletions(-) create mode 100644 tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql create mode 100644 tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 2f371e304..85364e268 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1839,7 +1839,8 @@ func TestTapscriptTreeManager(t *testing.T) { // DB. func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, - taprootInternalKey keychain.KeyDescriptor, groupKey []byte) { + taprootInternalKey keychain.KeyDescriptor, groupKey []byte, + outpoint wire.OutPoint) { ctx := context.Background() @@ -1855,12 +1856,16 @@ func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, }) require.NoError(t, err) + opBytes, err := encodeOutpoint(outpoint) + require.NoError(t, err) + _, err = q.UpsertMintSupplyPreCommit( ctx, UpsertBatchPreCommitParams{ BatchKey: batchKey, TxOutputIndex: txOutputIndex, TaprootInternalKeyID: internalKeyID, GroupKey: groupKey, + Outpoint: opBytes, }, ) require.NoError(t, err) @@ -1950,16 +1955,27 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { }, ) + // Define pre-commit outpoint for the batch mint anchor tx. + txOutputIndex := int32(2) + txidStr := mintingBatch.GenesisPacket.FundedPsbt.Pkt.UnsignedTx.TxID() + + txid, err := chainhash.NewHashFromStr(txidStr) + require.NoError(t, err) + + preCommitOutpoint := wire.OutPoint{ + Hash: *txid, + Index: uint32(txOutputIndex), + } + // Serialize keys into bytes for easier handling. preCommitInternalKey, _ := test.RandKeyDesc(t) groupPubKeyBytes := group.GroupPubKey.SerializeCompressed() // Upsert a mint anchor commitment for the batch. - txOutputIndex := int32(2) storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, - preCommitInternalKey, groupPubKeyBytes, + preCommitInternalKey, groupPubKeyBytes, preCommitOutpoint, ) // Retrieve and inspect the mint anchor commitment we just inserted. @@ -1968,13 +1984,13 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { preCommitInternalKey, groupPubKeyBytes, ) - // Upsert-ing a new taproot internal key for the same batch should - // overwrite the existing one. + // Upsert-ing a new taproot internal key for the same pre-commit + // outpoint should overwrite the existing one. internalKey2, _ := test.RandKeyDesc(t) storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKeyBytes, + groupPubKeyBytes, preCommitOutpoint, ) assertMintSupplyPreCommit( @@ -1982,14 +1998,14 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { groupPubKeyBytes, ) - // Upsert-ing a new group key for the same batch should overwrite the - // existing one. + // Upsert-ing a new group key for the same pre-commit outpoint should + // overwrite the existing one. groupPubKey2 := test.RandPubKey(t) groupPubKey2Bytes := groupPubKey2.SerializeCompressed() storeMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKey2Bytes, + groupPubKey2Bytes, preCommitOutpoint, ) assertMintSupplyPreCommit( diff --git a/tapdb/migrations.go b/tapdb/migrations.go index 4666eb5c5..6c3f948b6 100644 --- a/tapdb/migrations.go +++ b/tapdb/migrations.go @@ -24,7 +24,7 @@ const ( // daemon. // // NOTE: This MUST be updated when a new migration is added. - LatestMigrationVersion = 45 + LatestMigrationVersion = 46 ) // DatabaseBackend is an interface that contains all methods our different diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 05948d93a..96e446437 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1602,24 +1602,24 @@ func (q *Queries) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow const FetchMintSupplyPreCommits = `-- name: FetchMintSupplyPreCommits :many SELECT - mint_anchor_uni_commitments.id, - mint_anchor_uni_commitments.batch_id, - mint_anchor_uni_commitments.tx_output_index, - mint_anchor_uni_commitments.group_key, - mint_anchor_uni_commitments.spent_by, + precommits.id, + precommits.batch_id, + precommits.tx_output_index, + precommits.group_key, + precommits.spent_by, batch_internal_keys.raw_key AS batch_key, - mint_anchor_uni_commitments.taproot_internal_key_id, + precommits.taproot_internal_key_id, taproot_internal_keys.key_id, taproot_internal_keys.raw_key, taproot_internal_keys.key_family, taproot_internal_keys.key_index -FROM mint_anchor_uni_commitments +FROM mint_supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys - ON mint_anchor_uni_commitments.taproot_internal_key_id = taproot_internal_keys.key_id + ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id LEFT JOIN asset_minting_batches batches - ON mint_anchor_uni_commitments.batch_id = batches.batch_id + ON precommits.batch_id = batches.batch_id LEFT JOIN internal_keys batch_internal_keys ON batches.batch_id = batch_internal_keys.key_id WHERE ( (batch_internal_keys.raw_key = $1 OR $1 IS NULL) AND - (mint_anchor_uni_commitments.group_key = $2 OR $2 IS NULL) AND + (precommits.group_key = $2 OR $2 IS NULL) AND (taproot_internal_keys.raw_key = $3 OR $3 IS NULL) ) ` @@ -1641,7 +1641,7 @@ type FetchMintSupplyPreCommitsRow struct { InternalKey InternalKey } -// Fetch records from the mint_anchor_uni_commitments table with optional +// Fetch records from the supply_pre_commits table with optional // filtering. func (q *Queries) FetchMintSupplyPreCommits(ctx context.Context, arg FetchMintSupplyPreCommitsParams) ([]FetchMintSupplyPreCommitsRow, error) { rows, err := q.db.QueryContext(ctx, FetchMintSupplyPreCommits, arg.BatchKey, arg.GroupKey, arg.TaprootInternalKeyRaw) @@ -3241,16 +3241,18 @@ const UpsertMintSupplyPreCommit = `-- name: UpsertMintSupplyPreCommit :one WITH target_batch AS ( -- This CTE is used to fetch the ID of a batch, based on the serialized -- internal key associated with the batch. - SELECT keys.key_id AS batch_id - FROM internal_keys keys - WHERE keys.raw_key = $6 + -- Only yield a batch_id when the batch actually exists. + SELECT b.batch_id + FROM asset_minting_batches AS b + JOIN internal_keys AS k ON k.key_id = b.batch_id + WHERE k.raw_key = $6 ) -INSERT INTO mint_anchor_uni_commitments ( +INSERT INTO mint_supply_pre_commits ( batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint ) VALUES ( - (SELECT batch_id FROM target_batch), $1, + (SELECT batch_id FROM target_batch), $1, $2, $3, $4, $5 ) @@ -3271,9 +3273,13 @@ type UpsertMintSupplyPreCommitParams struct { BatchKey []byte } -// Upsert a record into the mint_anchor_uni_commitments table. -// If a record with the same batch ID and tx output index already exists, update -// the existing record. Otherwise, insert a new record. +// Upsert a supply pre-commit that is tied to a minting batch. +// The batch is resolved from @batch_key +// (internal_keys -> asset_minting_batches). +// The key is (batch_id, tx_output_index), where tx_output_index is the +// pre-commit output index in the batch’s mint anchor transaction. +// If a row exists for the same batch and index, update non-key fields only; +// the batch association is not changed. func (q *Queries) UpsertMintSupplyPreCommit(ctx context.Context, arg UpsertMintSupplyPreCommitParams) (int64, error) { row := q.db.QueryRowContext(ctx, UpsertMintSupplyPreCommit, arg.TxOutputIndex, diff --git a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql new file mode 100644 index 000000000..3abf3c48f --- /dev/null +++ b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql @@ -0,0 +1,3 @@ +-- Undo table rename. +ALTER TABLE mint_supply_pre_commits + RENAME TO mint_anchor_uni_commitments; diff --git a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql new file mode 100644 index 000000000..1d429a45c --- /dev/null +++ b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql @@ -0,0 +1,3 @@ +-- Rename the table to align with "supply" terminology. +ALTER TABLE mint_anchor_uni_commitments + RENAME TO mint_supply_pre_commits; diff --git a/tapdb/sqlc/models.go b/tapdb/sqlc/models.go index fb1e3aeb2..83850be76 100644 --- a/tapdb/sqlc/models.go +++ b/tapdb/sqlc/models.go @@ -304,7 +304,7 @@ type ManagedUtxo struct { RootVersion sql.NullInt16 } -type MintAnchorUniCommitment struct { +type MintSupplyPreCommit struct { ID int64 BatchID int32 TxOutputIndex int32 diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 45c12c67f..8ff722b3c 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -90,7 +90,7 @@ type Querier interface { FetchInternalKeyLocator(ctx context.Context, rawKey []byte) (FetchInternalKeyLocatorRow, error) FetchManagedUTXO(ctx context.Context, arg FetchManagedUTXOParams) (FetchManagedUTXORow, error) FetchManagedUTXOs(ctx context.Context) ([]FetchManagedUTXOsRow, error) - // Fetch records from the mint_anchor_uni_commitments table with optional + // Fetch records from the supply_pre_commits table with optional // filtering. FetchMintSupplyPreCommits(ctx context.Context, arg FetchMintSupplyPreCommitsParams) ([]FetchMintSupplyPreCommitsRow, error) FetchMintingBatch(ctx context.Context, rawKey []byte) (FetchMintingBatchRow, error) @@ -236,9 +236,13 @@ type Querier interface { UpsertGenesisPoint(ctx context.Context, prevOut []byte) (int64, error) UpsertInternalKey(ctx context.Context, arg UpsertInternalKeyParams) (int64, error) UpsertManagedUTXO(ctx context.Context, arg UpsertManagedUTXOParams) (int64, error) - // Upsert a record into the mint_anchor_uni_commitments table. - // If a record with the same batch ID and tx output index already exists, update - // the existing record. Otherwise, insert a new record. + // Upsert a supply pre-commit that is tied to a minting batch. + // The batch is resolved from @batch_key + // (internal_keys -> asset_minting_batches). + // The key is (batch_id, tx_output_index), where tx_output_index is the + // pre-commit output index in the batch’s mint anchor transaction. + // If a row exists for the same batch and index, update non-key fields only; + // the batch association is not changed. UpsertMintSupplyPreCommit(ctx context.Context, arg UpsertMintSupplyPreCommitParams) (int64, error) UpsertMultiverseLeaf(ctx context.Context, arg UpsertMultiverseLeafParams) (int64, error) UpsertMultiverseRoot(ctx context.Context, arg UpsertMultiverseRootParams) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 80e647d5e..f0268476d 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1063,24 +1063,30 @@ JOIN genesis_assets ORDER BY assets_meta.meta_id; -- name: UpsertMintSupplyPreCommit :one --- Upsert a record into the mint_anchor_uni_commitments table. --- If a record with the same batch ID and tx output index already exists, update --- the existing record. Otherwise, insert a new record. +-- Upsert a supply pre-commit that is tied to a minting batch. +-- The batch is resolved from @batch_key +-- (internal_keys -> asset_minting_batches). +-- The key is (batch_id, tx_output_index), where tx_output_index is the +-- pre-commit output index in the batch’s mint anchor transaction. +-- If a row exists for the same batch and index, update non-key fields only; +-- the batch association is not changed. WITH target_batch AS ( -- This CTE is used to fetch the ID of a batch, based on the serialized -- internal key associated with the batch. - SELECT keys.key_id AS batch_id - FROM internal_keys keys - WHERE keys.raw_key = @batch_key + -- Only yield a batch_id when the batch actually exists. + SELECT b.batch_id + FROM asset_minting_batches AS b + JOIN internal_keys AS k ON k.key_id = b.batch_id + WHERE k.raw_key = @batch_key ) -INSERT INTO mint_anchor_uni_commitments ( +INSERT INTO mint_supply_pre_commits ( batch_id, tx_output_index, taproot_internal_key_id, group_key, spent_by, outpoint ) VALUES ( - (SELECT batch_id FROM target_batch), @tx_output_index, + (SELECT batch_id FROM target_batch), @tx_output_index, @taproot_internal_key_id, @group_key, sqlc.narg('spent_by'), - sqlc.narg('outpoint') + @outpoint ) ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET -- The following fields are updated if a conflict occurs. @@ -1090,26 +1096,26 @@ ON CONFLICT(batch_id, tx_output_index) DO UPDATE SET RETURNING id; -- name: FetchMintSupplyPreCommits :many --- Fetch records from the mint_anchor_uni_commitments table with optional +-- Fetch records from the supply_pre_commits table with optional -- filtering. SELECT - mint_anchor_uni_commitments.id, - mint_anchor_uni_commitments.batch_id, - mint_anchor_uni_commitments.tx_output_index, - mint_anchor_uni_commitments.group_key, - mint_anchor_uni_commitments.spent_by, + precommits.id, + precommits.batch_id, + precommits.tx_output_index, + precommits.group_key, + precommits.spent_by, batch_internal_keys.raw_key AS batch_key, - mint_anchor_uni_commitments.taproot_internal_key_id, + precommits.taproot_internal_key_id, sqlc.embed(taproot_internal_keys) -FROM mint_anchor_uni_commitments +FROM mint_supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys - ON mint_anchor_uni_commitments.taproot_internal_key_id = taproot_internal_keys.key_id + ON precommits.taproot_internal_key_id = taproot_internal_keys.key_id LEFT JOIN asset_minting_batches batches - ON mint_anchor_uni_commitments.batch_id = batches.batch_id + ON precommits.batch_id = batches.batch_id LEFT JOIN internal_keys batch_internal_keys ON batches.batch_id = batch_internal_keys.key_id WHERE ( (batch_internal_keys.raw_key = sqlc.narg('batch_key') OR sqlc.narg('batch_key') IS NULL) AND - (mint_anchor_uni_commitments.group_key = sqlc.narg('group_key') OR sqlc.narg('group_key') IS NULL) AND + (precommits.group_key = sqlc.narg('group_key') OR sqlc.narg('group_key') IS NULL) AND (taproot_internal_keys.raw_key = sqlc.narg('taproot_internal_key_raw') OR sqlc.narg('taproot_internal_key_raw') IS NULL) ); diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 10b3d60b5..04b85e3ec 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -204,7 +204,7 @@ SELECT mac.group_key, mint_txn.block_height, mint_txn.raw_tx -FROM mint_anchor_uni_commitments mac +FROM mint_supply_pre_commits mac JOIN asset_minting_batches amb ON mac.batch_id = amb.batch_id JOIN genesis_points gp ON amb.genesis_id = gp.genesis_id JOIN chain_txns mint_txn ON gp.anchor_tx_id = mint_txn.txn_id @@ -219,7 +219,7 @@ WHERE -- Mark a supply pre-commitment output as spent by its outpoint. The -- pre-commitment corresponds to an asset issuance where the local node acted as -- the issuer. -UPDATE mint_anchor_uni_commitments +UPDATE mint_supply_pre_commits SET spent_by = @spent_by_commit_id WHERE outpoint = @outpoint AND spent_by IS NULL; diff --git a/tapdb/sqlc/schemas/generated_schema.sql b/tapdb/sqlc/schemas/generated_schema.sql index 0d686d16e..46d95834f 100644 --- a/tapdb/sqlc/schemas/generated_schema.sql +++ b/tapdb/sqlc/schemas/generated_schema.sql @@ -624,7 +624,14 @@ CREATE TABLE managed_utxos ( lease_expiry TIMESTAMP , root_version SMALLINT); -CREATE TABLE mint_anchor_uni_commitments ( +CREATE INDEX mint_anchor_uni_commitments_outpoint_idx + ON "mint_supply_pre_commits"(outpoint) + WHERE outpoint IS NOT NULL; + +CREATE UNIQUE INDEX mint_anchor_uni_commitments_unique + ON "mint_supply_pre_commits" (batch_id, tx_output_index); + +CREATE TABLE "mint_supply_pre_commits" ( id INTEGER PRIMARY KEY, -- The ID of the minting batch this universe commitment relates to. @@ -639,13 +646,6 @@ CREATE TABLE mint_anchor_uni_commitments ( BIGINT REFERENCES internal_keys(key_id) NOT NULL, spent_by BIGINT REFERENCES supply_commitments(commit_id), outpoint BLOB); -CREATE INDEX mint_anchor_uni_commitments_outpoint_idx - ON mint_anchor_uni_commitments(outpoint) - WHERE outpoint IS NOT NULL; - -CREATE UNIQUE INDEX mint_anchor_uni_commitments_unique - ON mint_anchor_uni_commitments (batch_id, tx_output_index); - CREATE TABLE mssmt_nodes ( -- hash_key is the hash key by which we reference all nodes. hash_key BLOB NOT NULL, diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index a6e2800aa..d64f71b1e 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -115,7 +115,7 @@ SELECT mac.group_key, mint_txn.block_height, mint_txn.raw_tx -FROM mint_anchor_uni_commitments mac +FROM mint_supply_pre_commits mac JOIN asset_minting_batches amb ON mac.batch_id = amb.batch_id JOIN genesis_points gp ON amb.genesis_id = gp.genesis_id JOIN chain_txns mint_txn ON gp.anchor_tx_id = mint_txn.txn_id @@ -312,7 +312,7 @@ func (q *Queries) LinkDanglingSupplyUpdateEvents(ctx context.Context, arg LinkDa } const MarkMintPreCommitSpentByOutpoint = `-- name: MarkMintPreCommitSpentByOutpoint :exec -UPDATE mint_anchor_uni_commitments +UPDATE mint_supply_pre_commits SET spent_by = $1 WHERE outpoint = $2 AND spent_by IS NULL From 8f81e84ea4e9290cee9534190d0827f438118f1b Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 16:56:52 +0100 Subject: [PATCH 07/16] tapdb: return supply pre-commit outpoint from FetchMintSupplyPreCommits Extend FetchMintSupplyPreCommits to also return the supply pre-commit outpoint. --- tapdb/asset_minting_test.go | 13 +++++++++---- tapdb/sqlc/assets.sql.go | 3 +++ tapdb/sqlc/queries/assets.sql | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 85364e268..a61f884e4 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -1880,7 +1880,8 @@ func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, // values. func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, batchKey []byte, txOutputIndex int32, - preCommitInternalKey keychain.KeyDescriptor, groupPubKeyBytes []byte) { + preCommitInternalKey keychain.KeyDescriptor, groupPubKeyBytes []byte, + outpoint wire.OutPoint) { ctx := context.Background() readOpts := NewAssetStoreReadTx() @@ -1919,6 +1920,10 @@ func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore, preCommit.InternalKey.KeyFamily, ) require.Equal(t, groupPubKeyBytes, preCommit.GroupKey) + + opBytes, err := encodeOutpoint(outpoint) + require.NoError(t, err) + require.Equal(t, opBytes, preCommit.Outpoint) } // TestUpsertMintSupplyPreCommit tests the UpsertMintSupplyPreCommit and @@ -1981,7 +1986,7 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { // Retrieve and inspect the mint anchor commitment we just inserted. assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, - preCommitInternalKey, groupPubKeyBytes, + preCommitInternalKey, groupPubKeyBytes, preCommitOutpoint, ) // Upsert-ing a new taproot internal key for the same pre-commit @@ -1995,7 +2000,7 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKeyBytes, + groupPubKeyBytes, preCommitOutpoint, ) // Upsert-ing a new group key for the same pre-commit outpoint should @@ -2010,7 +2015,7 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { assertMintSupplyPreCommit( t, *assetStore, batchKey, txOutputIndex, internalKey2, - groupPubKey2Bytes, + groupPubKey2Bytes, preCommitOutpoint, ) } diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 96e446437..63abc814e 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1609,6 +1609,7 @@ SELECT precommits.spent_by, batch_internal_keys.raw_key AS batch_key, precommits.taproot_internal_key_id, + precommits.outpoint, taproot_internal_keys.key_id, taproot_internal_keys.raw_key, taproot_internal_keys.key_family, taproot_internal_keys.key_index FROM mint_supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys @@ -1638,6 +1639,7 @@ type FetchMintSupplyPreCommitsRow struct { SpentBy sql.NullInt64 BatchKey []byte TaprootInternalKeyID int64 + Outpoint []byte InternalKey InternalKey } @@ -1660,6 +1662,7 @@ func (q *Queries) FetchMintSupplyPreCommits(ctx context.Context, arg FetchMintSu &i.SpentBy, &i.BatchKey, &i.TaprootInternalKeyID, + &i.Outpoint, &i.InternalKey.KeyID, &i.InternalKey.RawKey, &i.InternalKey.KeyFamily, diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index f0268476d..3bcba2216 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1106,6 +1106,7 @@ SELECT precommits.spent_by, batch_internal_keys.raw_key AS batch_key, precommits.taproot_internal_key_id, + precommits.outpoint, sqlc.embed(taproot_internal_keys) FROM mint_supply_pre_commits AS precommits JOIN internal_keys taproot_internal_keys From f69da96607dde5b65678a5313e22a4176c94089d Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 3 Sep 2025 12:53:16 +0100 Subject: [PATCH 08/16] tapdb: add new table supply_pre_commits Add a table to store mint anchor transaction supply pre-commitment outputs for assets not issued by the local node. Data from this table will be used for supply commitment verification. --- .../000046_supply_pre_commit_tables.down.sql | 5 ++++ .../000046_supply_pre_commit_tables.up.sql | 30 +++++++++++++++++++ tapdb/sqlc/models.go | 9 ++++++ tapdb/sqlc/schemas/generated_schema.sql | 27 +++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql index 3abf3c48f..d23075d48 100644 --- a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql +++ b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.down.sql @@ -1,3 +1,8 @@ -- Undo table rename. ALTER TABLE mint_supply_pre_commits RENAME TO mint_anchor_uni_commitments; + +-- Drop the new supply_pre_commits table. +DROP INDEX IF EXISTS supply_pre_commits_idx_group_key; +DROP INDEX IF EXISTS supply_pre_commits_unique_outpoint; +DROP TABLE IF EXISTS supply_pre_commits; \ No newline at end of file diff --git a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql index 1d429a45c..64e4a4376 100644 --- a/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql +++ b/tapdb/sqlc/migrations/000046_supply_pre_commit_tables.up.sql @@ -1,3 +1,33 @@ -- Rename the table to align with "supply" terminology. ALTER TABLE mint_anchor_uni_commitments RENAME TO mint_supply_pre_commits; + +-- Add a new table to track supply pre-commitments generated by +CREATE TABLE supply_pre_commits ( + id INTEGER PRIMARY KEY, + + -- The asset group key for this supply pre-commitment. + -- Stored in canonical 32-byte x-only form as defined in BIP340 + -- (schnorr.SerializePubKey). + group_key BLOB NOT NULL CHECK(length(group_key) = 32), + + -- The taproot internal key of the pre-commitment transaction output. + taproot_internal_key BLOB NOT NULL CHECK(length(taproot_internal_key) = 33), + + -- The pre-commit outpoint from the mint anchor transaction. + outpoint BLOB NOT NULL CHECK(length(outpoint) > 0), + + -- The chain transaction that included this pre-commitment output. + chain_txn_db_id BIGINT NOT NULL REFERENCES chain_txns(txn_id), + + -- Reference to supply commitment which spends this pre-commitment. + spent_by BIGINT REFERENCES supply_commitments(commit_id) +); + +-- Create a unique index on outpoint. +CREATE UNIQUE INDEX supply_pre_commits_unique_outpoint + ON supply_pre_commits(outpoint); + +-- Create an index on group_key for faster lookups. +CREATE INDEX supply_pre_commits_idx_group_key + ON supply_pre_commits(group_key); diff --git a/tapdb/sqlc/models.go b/tapdb/sqlc/models.go index 83850be76..8c6f374d8 100644 --- a/tapdb/sqlc/models.go +++ b/tapdb/sqlc/models.go @@ -415,6 +415,15 @@ type SupplyCommitment struct { SpentCommitment sql.NullInt64 } +type SupplyPreCommit struct { + ID int64 + GroupKey []byte + TaprootInternalKey []byte + Outpoint []byte + ChainTxnDbID int64 + SpentBy sql.NullInt64 +} + type SupplySyncerPushLog struct { ID int64 GroupKey []byte diff --git a/tapdb/sqlc/schemas/generated_schema.sql b/tapdb/sqlc/schemas/generated_schema.sql index 46d95834f..acb519e8b 100644 --- a/tapdb/sqlc/schemas/generated_schema.sql +++ b/tapdb/sqlc/schemas/generated_schema.sql @@ -892,6 +892,33 @@ CREATE UNIQUE INDEX supply_commitments_outpoint_uk CREATE INDEX supply_commitments_spent_commitment_idx ON supply_commitments(spent_commitment); +CREATE TABLE supply_pre_commits ( + id INTEGER PRIMARY KEY, + + -- The asset group key for this supply pre-commitment. + -- Stored in canonical 32-byte x-only form as defined in BIP340 + -- (schnorr.SerializePubKey). + group_key BLOB NOT NULL CHECK(length(group_key) = 32), + + -- The taproot internal key of the pre-commitment transaction output. + taproot_internal_key BLOB NOT NULL CHECK(length(taproot_internal_key) = 33), + + -- The pre-commit outpoint from the mint anchor transaction. + outpoint BLOB NOT NULL CHECK(length(outpoint) > 0), + + -- The chain transaction that included this pre-commitment output. + chain_txn_db_id BIGINT NOT NULL REFERENCES chain_txns(txn_id), + + -- Reference to supply commitment which spends this pre-commitment. + spent_by BIGINT REFERENCES supply_commitments(commit_id) +); + +CREATE INDEX supply_pre_commits_idx_group_key + ON supply_pre_commits(group_key); + +CREATE UNIQUE INDEX supply_pre_commits_unique_outpoint + ON supply_pre_commits(outpoint); + CREATE TABLE supply_syncer_push_log ( id INTEGER PRIMARY KEY, From 7c99667638a908c66bee14e0f4d2d62c7c112ecf Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 20:25:43 +0100 Subject: [PATCH 09/16] tapdb: add UpsertSupplyPreCommit query Adds support for upserting supply pre-commits that are not related to the minting process. These pre-commits pertain to assets issued by peer nodes. --- tapdb/sqlc/assets.sql.go | 46 +++++++++++++++++++++++++++++++++++ tapdb/sqlc/querier.go | 2 ++ tapdb/sqlc/queries/assets.sql | 24 ++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 63abc814e..6db3205eb 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -3344,6 +3344,52 @@ func (q *Queries) UpsertScriptKey(ctx context.Context, arg UpsertScriptKeyParams return script_key_id, err } +const UpsertSupplyPreCommit = `-- name: UpsertSupplyPreCommit :one +INSERT INTO supply_pre_commits ( + group_key, + taproot_internal_key, + outpoint, + chain_txn_db_id, + spent_by +) +VALUES ( + $1, + $2, + $3, + $4, + $5 +) +ON CONFLICT(outpoint) DO UPDATE SET + group_key = EXCLUDED.group_key, + taproot_internal_key = EXCLUDED.taproot_internal_key, + outpoint = EXCLUDED.outpoint, + chain_txn_db_id = EXCLUDED.chain_txn_db_id, + spent_by = EXCLUDED.spent_by +RETURNING id +` + +type UpsertSupplyPreCommitParams struct { + GroupKey []byte + TaprootInternalKey []byte + Outpoint []byte + ChainTxnDbID int64 + SpentBy sql.NullInt64 +} + +// Upsert a supply pre-commit output that is not tied to a minting batch. +func (q *Queries) UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) { + row := q.db.QueryRowContext(ctx, UpsertSupplyPreCommit, + arg.GroupKey, + arg.TaprootInternalKey, + arg.Outpoint, + arg.ChainTxnDbID, + arg.SpentBy, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + const UpsertTapscriptTreeEdge = `-- name: UpsertTapscriptTreeEdge :one INSERT INTO tapscript_edges ( root_hash_id, node_index, raw_node_id diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index 8ff722b3c..a12b27ce5 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -251,6 +251,8 @@ type Querier interface { // Return the ID of the state that was actually set (either inserted or updated), // and the latest commitment ID that was set. UpsertSupplyCommitStateMachine(ctx context.Context, arg UpsertSupplyCommitStateMachineParams) (UpsertSupplyCommitStateMachineRow, error) + // Upsert a supply pre-commit output that is not tied to a minting batch. + UpsertSupplyPreCommit(ctx context.Context, arg UpsertSupplyPreCommitParams) (int64, error) UpsertTapscriptTreeEdge(ctx context.Context, arg UpsertTapscriptTreeEdgeParams) (int64, error) UpsertTapscriptTreeNode(ctx context.Context, rawNode []byte) (int64, error) UpsertTapscriptTreeRootHash(ctx context.Context, arg UpsertTapscriptTreeRootHashParams) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index 3bcba2216..dfce2d3ac 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -1062,6 +1062,30 @@ JOIN genesis_assets ON genesis_assets.meta_data_id = assets_meta.meta_id ORDER BY assets_meta.meta_id; +-- name: UpsertSupplyPreCommit :one +-- Upsert a supply pre-commit output that is not tied to a minting batch. +INSERT INTO supply_pre_commits ( + group_key, + taproot_internal_key, + outpoint, + chain_txn_db_id, + spent_by +) +VALUES ( + @group_key, + @taproot_internal_key, + @outpoint, + @chain_txn_db_id, + sqlc.narg('spent_by') +) +ON CONFLICT(outpoint) DO UPDATE SET + group_key = EXCLUDED.group_key, + taproot_internal_key = EXCLUDED.taproot_internal_key, + outpoint = EXCLUDED.outpoint, + chain_txn_db_id = EXCLUDED.chain_txn_db_id, + spent_by = EXCLUDED.spent_by +RETURNING id; + -- name: UpsertMintSupplyPreCommit :one -- Upsert a supply pre-commit that is tied to a minting batch. -- The batch is resolved from @batch_key From 1e481fa86ac1d6e9691b158da45bb4d139ba5154 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 20:59:57 +0100 Subject: [PATCH 10/16] tapdb: pass proof type into universeUpsertProofLeaf instead of string Passes the proof type as an argument to universeUpsertProofLeaf rather than just its string representation. This refactor will simplify conditional logic based on proof type in a subsequent commit. --- tapdb/burn_tree.go | 11 +++++++++-- tapdb/multiverse.go | 4 ++-- tapdb/supply_tree.go | 8 +++++++- tapdb/universe.go | 7 ++++--- universe/supplycommit/env.go | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/tapdb/burn_tree.go b/tapdb/burn_tree.go index ed19fca9e..fede10d27 100644 --- a/tapdb/burn_tree.go +++ b/tapdb/burn_tree.go @@ -125,9 +125,16 @@ func insertBurnsInternal(ctx context.Context, db BaseUniverseStore, // Call the generic upsert function for the burn sub-tree to // update DB records. MetaReveal is nil for burns. + uniProofType, err := + supplycommit.BurnTreeType.ToUniverseProofType() + if err != nil { + return nil, fmt.Errorf("unable to map burn supply "+ + "tree type to universe proof type: %w", err) + } + _, err = universeUpsertProofLeaf( - ctx, db, subNs, supplycommit.BurnTreeType.String(), - groupKey, leafKey, leaf, nil, blockHeight, + ctx, db, subNs, uniProofType, groupKey, leafKey, leaf, + nil, blockHeight, ) if err != nil { return nil, fmt.Errorf("unable to upsert burn "+ diff --git a/tapdb/multiverse.go b/tapdb/multiverse.go index cb7c2418d..fae413ae5 100644 --- a/tapdb/multiverse.go +++ b/tapdb/multiverse.go @@ -802,7 +802,7 @@ func (b *MultiverseStore) UpsertProofLeaf(ctx context.Context, } uniProof, err = universeUpsertProofLeaf( - ctx, dbTx, id.String(), id.ProofType.String(), + ctx, dbTx, id.String(), id.ProofType, id.GroupKey, key, leaf, metaReveal, blockHeight, ) if err != nil { @@ -883,7 +883,7 @@ func (b *MultiverseStore) UpsertProofLeafBatch(ctx context.Context, // start with. uniProof, err := universeUpsertProofLeaf( ctx, store, item.ID.String(), - item.ID.ProofType.String(), + item.ID.ProofType, item.ID.GroupKey, item.Key, item.Leaf, item.MetaReveal, blockHeight, ) diff --git a/tapdb/supply_tree.go b/tapdb/supply_tree.go index 3134aa976..4644af4ef 100644 --- a/tapdb/supply_tree.go +++ b/tapdb/supply_tree.go @@ -462,8 +462,14 @@ func registerMintSupplyInternal(ctx context.Context, dbTx BaseUniverseStore, subNs := subTreeNamespace(groupKey, supplycommit.MintTreeType) // Upsert the leaf into the mint supply sub-tree SMT and DB. + uniProofType, err := supplycommit.MintTreeType.ToUniverseProofType() + if err != nil { + return nil, fmt.Errorf("unable to map mint supply tree type "+ + "to universe proof type: %w", err) + } + mintSupplyProof, err := universeUpsertProofLeaf( - ctx, dbTx, subNs, supplycommit.MintTreeType.String(), groupKey, + ctx, dbTx, subNs, uniProofType, groupKey, key, leaf, metaReveal, blockHeight, ) if err != nil { diff --git a/tapdb/universe.go b/tapdb/universe.go index 54678177a..ae022b912 100644 --- a/tapdb/universe.go +++ b/tapdb/universe.go @@ -599,7 +599,7 @@ func (b *BaseUniverseTree) UpsertProofLeaf(ctx context.Context, } issuanceProof, err := universeUpsertProofLeaf( - ctx, dbTx, namespace, b.id.ProofType.String(), + ctx, dbTx, namespace, b.id.ProofType, b.id.GroupKey, key, leaf, metaReveal, blockHeight, ) if err != nil { @@ -728,7 +728,8 @@ func upsertMultiverseLeafEntry(ctx context.Context, dbTx BaseUniverseStore, // NOTE: This function accepts a db transaction, as it's used when making // broader DB updates. func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore, - namespace string, proofTypeStr string, groupKey *btcec.PublicKey, + namespace string, proofType universe.ProofType, + groupKey *btcec.PublicKey, key universe.LeafKey, leaf *universe.Leaf, metaReveal *proof.MetaReveal, blockHeight lfn.Option[uint32]) (*universe.Proof, error) { @@ -774,7 +775,7 @@ func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore, NamespaceRoot: namespace, AssetID: fn.ByteSlice(leaf.ID()), GroupKey: groupKeyBytes, - ProofType: sqlStr(proofTypeStr), + ProofType: sqlStr(proofType.String()), }) if err != nil { return nil, err diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index b84176e97..37be5cd4c 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -21,6 +21,7 @@ import ( "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/tapsend" + "github.com/lightninglabs/taproot-assets/universe" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -69,6 +70,22 @@ func (s SupplySubTree) String() string { } } +// ToUniverseProofType converts the supply subtree type to the corresponding +// universe proof type. +func (s SupplySubTree) ToUniverseProofType() (universe.ProofType, error) { + switch s { + case MintTreeType: + return universe.ProofTypeMintSupply, nil + case BurnTreeType: + return universe.ProofTypeBurn, nil + case IgnoreTreeType: + return universe.ProofTypeIgnore, nil + default: + return universe.ProofTypeUnspecified, fmt.Errorf("unknown "+ + "supply subtree type: %s", s) + } +} + // AllSupplySubTrees contains all possible valid SupplySubTree values. var AllSupplySubTrees = []SupplySubTree{ MintTreeType, From 8682cb523393e77e56c98d68278dcc32b48c4fa8 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 2 Sep 2025 20:44:17 +0100 Subject: [PATCH 11/16] 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. --- tapdb/assets_common.go | 7 ++ tapdb/universe.go | 155 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/tapdb/assets_common.go b/tapdb/assets_common.go index 4f3b4ba39..b3d6fef20 100644 --- a/tapdb/assets_common.go +++ b/tapdb/assets_common.go @@ -37,6 +37,9 @@ type UpsertAssetStore interface { // DB. UpsertChainTx(ctx context.Context, arg ChainTxParams) (int64, error) + // FetchChainTx fetches a chain tx from the DB. + FetchChainTx(ctx context.Context, txid []byte) (ChainTx, error) + // UpsertGenesisAsset inserts a new or updates an existing genesis asset // (the base asset info) in the DB, and returns the primary key. // @@ -97,6 +100,10 @@ type UpsertAssetStore interface { // UpsertTapscriptTreeRootHash inserts a new tapscript tree root hash. UpsertTapscriptTreeRootHash(ctx context.Context, arg TapscriptTreeRootHash) (int64, error) + + // UpsertSupplyPreCommit inserts a new supply pre-commit record. + UpsertSupplyPreCommit(ctx context.Context, + arg sqlc.UpsertSupplyPreCommitParams) (int64, error) } var ( diff --git a/tapdb/universe.go b/tapdb/universe.go index ae022b912..e69af4771 100644 --- a/tapdb/universe.go +++ b/tapdb/universe.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapdb/sqlc" "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightninglabs/taproot-assets/universe/supplyverifier" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/keychain" ) @@ -577,6 +578,146 @@ func upsertAssetGen(ctx context.Context, db UpsertAssetStore, return genAssetID, nil } +// shouldInsertPreCommit determines whether a supply pre-commitment +// output should be inserted for a given issuance proof. +func shouldInsertPreCommit(proofType universe.ProofType, + issuanceProof proof.Proof, metaReveal *proof.MetaReveal) bool { + + // Only issuance proofs can carry supply pre-commitment outputs. + if proofType != universe.ProofTypeIssuance { + return false + } + + // Supply pre-commitment outputs apply only to asset groups. + if issuanceProof.Asset.GroupKey == nil { + return false + } + + // Without metadata, we can't determine pre-commitment support. + if metaReveal == nil { + return false + } + + // If the metadata indicates no supply commitment support, stop here. + if !metaReveal.UniverseCommitments { + return false + } + + // A delegation key is mandatory for supply pre-commitment outputs. + if metaReveal.DelegationKey.IsNone() { + return false + } + + return true +} + +// maybeUpsertSupplyPreCommit inserts a supply pre-commitment output if the +// asset group supports supply commitments and this is an issuance proof. +func maybeUpsertSupplyPreCommit(ctx context.Context, dbTx UpsertAssetStore, + proofType universe.ProofType, issuanceProof proof.Proof, + metaReveal *proof.MetaReveal) error { + + if !shouldInsertPreCommit(proofType, issuanceProof, metaReveal) { + return nil + } + + delegationKey, err := metaReveal.DelegationKey.UnwrapOrErr( + errors.New("missing delegation key"), + ) + if err != nil { + return err + } + + preCommitOutput, err := supplyverifier.ExtractPreCommitOutput( + issuanceProof, delegationKey, + ) + if err != nil { + return fmt.Errorf("unable to extract pre-commit "+ + "output: %w", err) + } + + outPointBytes, err := encodeOutpoint(preCommitOutput.OutPoint()) + if err != nil { + return fmt.Errorf("unable to encode supply pre-commit "+ + "outpoint: %w", err) + } + + // Upsert the supply pre-commitment output. + // + // Encode the group key and taproot internal key. + groupKeyBytes := schnorr.SerializePubKey( + &issuanceProof.Asset.GroupKey.GroupPubKey, + ) + taprootInternalKeyBytes := + preCommitOutput.InternalKey.PubKey.SerializeCompressed() + + // Try to fetch an existing chain tx row from the database. We fetch + // first instead of blindly upserting to avoid overwriting existing data + // with null values. + var chainTxDbID fn.Option[int64] + + txIDBytes := fn.ByteSlice(issuanceProof.AnchorTx.TxHash()) + chainTxn, err := dbTx.FetchChainTx(ctx, txIDBytes) + switch { + case errors.Is(err, sql.ErrNoRows): + // No existing chain tx, we'll insert a new one below. + + case err != nil: + return fmt.Errorf("unable to fetch chain tx: %w", err) + + default: + // We found an existing chain tx. If it has a valid block + // height, then we'll use it. Otherwise, we'll insert a new one + // below. + if chainTxn.BlockHeight.Valid { + chainTxDbID = fn.Some(chainTxn.TxnID) + } + } + + // If we didn't find an existing chain tx, then we'll insert a new + // one now. + if chainTxDbID.IsNone() { + blockHash := issuanceProof.BlockHeader.BlockHash() + txBytes, err := fn.Serialize(&issuanceProof.AnchorTx) + if err != nil { + return fmt.Errorf("failed to serialize tx: %w", err) + } + + txDbID, err := dbTx.UpsertChainTx(ctx, ChainTxParams{ + Txid: txIDBytes, + RawTx: txBytes, + BlockHeight: sqlInt32(issuanceProof.BlockHeight), + BlockHash: blockHash.CloneBytes(), + }) + if err != nil { + return fmt.Errorf("unable to upsert chain tx: %w", err) + } + + chainTxDbID = fn.Some(txDbID) + } + + txDbID, err := chainTxDbID.UnwrapOrErr( + errors.New("missing chain tx db id"), + ) + if err != nil { + return err + } + + _, err = dbTx.UpsertSupplyPreCommit( + ctx, sqlc.UpsertSupplyPreCommitParams{ + TaprootInternalKey: taprootInternalKeyBytes, + GroupKey: groupKeyBytes, + Outpoint: outPointBytes, + ChainTxnDbID: txDbID, + }, + ) + if err != nil { + return fmt.Errorf("unable to upsert supply pre-commit: %w", err) + } + + return nil +} + // UpsertProofLeaf inserts or updates a proof leaf within the universe tree, // stored at the base key. The metaReveal type is purely optional, and should be // specified if the genesis proof committed to a non-zero meta hash. @@ -795,6 +936,9 @@ func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore, return nil, fmt.Errorf("unable to decode proof: %w", err) } + // Upsert into the DB: the genesis point, asset genesis, + // group key reveal, and the anchoring transaction for the issuance or + // transfer. assetGenID, err := upsertAssetGen( ctx, dbTx, leaf.Genesis, leaf.GroupKey, &leafProof, ) @@ -802,6 +946,17 @@ func universeUpsertProofLeaf(ctx context.Context, dbTx BaseUniverseStore, return nil, err } + // If the asset group supports supply commitments and this is an + // issuance proof, then we may need to log the supply pre-commitment + // output. + err = maybeUpsertSupplyPreCommit( + ctx, dbTx, proofType, leafProof, metaReveal, + ) + if err != nil { + return nil, fmt.Errorf("unable to upsert supply "+ + "pre-commit: %w", err) + } + // If the block height isn't specified, then we'll attempt to extract it // from the proof itself. if blockHeight.IsNone() && leafProof.BlockHeight > 0 { From 98edcb41d5a05a04edf027394a1d3b4f39542528 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 9 Sep 2025 01:17:34 +0100 Subject: [PATCH 12/16] sqlc: add query FetchUnspentSupplyPreCommits Introduce query to fetch unspent supply pre-commitment outputs. Each output originates from a mint anchor transaction and corresponds to an asset issuance where a peer node, not the local node, acted as the issuer. This differs from FetchUnspentMintSupplyPreCommits, which only returns pre-commitments minted by the local node. --- tapdb/sqlc/querier.go | 5 +++ tapdb/sqlc/queries/supply_commit.sql | 17 +++++++++ tapdb/sqlc/supply_commit.sql.go | 55 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index a12b27ce5..f74c59523 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -118,6 +118,11 @@ type Querier interface { // comes from a mint anchor transaction and relates to an asset issuance // where the local node acted as the issuer. FetchUnspentMintSupplyPreCommits(ctx context.Context, groupKey []byte) ([]FetchUnspentMintSupplyPreCommitsRow, error) + // Fetch unspent supply pre-commitment outputs. Each pre-commitment output + // comes from a mint anchor transaction and relates to an asset issuance + // where a peer node acted as the issuer. Rows in this table do not relate to an + // issuance where the local node acted as the issuer. + FetchUnspentSupplyPreCommits(ctx context.Context, groupKey []byte) ([]FetchUnspentSupplyPreCommitsRow, error) FinalizeSupplyCommitTransition(ctx context.Context, transitionID int64) error FreezePendingTransition(ctx context.Context, groupKey []byte) error GenesisAssets(ctx context.Context) ([]GenesisAsset, error) diff --git a/tapdb/sqlc/queries/supply_commit.sql b/tapdb/sqlc/queries/supply_commit.sql index 04b85e3ec..b5c0eec8b 100644 --- a/tapdb/sqlc/queries/supply_commit.sql +++ b/tapdb/sqlc/queries/supply_commit.sql @@ -194,6 +194,23 @@ WHERE transition_id = @transition_id; DELETE FROM supply_update_events WHERE transition_id = @transition_id; +-- name: FetchUnspentSupplyPreCommits :many +-- Fetch unspent supply pre-commitment outputs. Each pre-commitment output +-- comes from a mint anchor transaction and relates to an asset issuance +-- where a peer node acted as the issuer. Rows in this table do not relate to an +-- issuance where the local node acted as the issuer. +SELECT + chain_txns.block_height, + chain_txns.raw_tx, + pre_commit.outpoint, + pre_commit.taproot_internal_key, + pre_commit.group_key +FROM supply_pre_commits pre_commit + JOIN chain_txns ON pre_commit.chain_txn_db_id = chain_txns.txn_id +WHERE + pre_commit.group_key = @group_key AND + pre_commit.spent_by IS NULL; + -- name: FetchUnspentMintSupplyPreCommits :many -- Fetch unspent supply pre-commitment outputs. Each pre-commitment output -- comes from a mint anchor transaction and relates to an asset issuance diff --git a/tapdb/sqlc/supply_commit.sql.go b/tapdb/sqlc/supply_commit.sql.go index d64f71b1e..960aa28ff 100644 --- a/tapdb/sqlc/supply_commit.sql.go +++ b/tapdb/sqlc/supply_commit.sql.go @@ -170,6 +170,61 @@ func (q *Queries) FetchUnspentMintSupplyPreCommits(ctx context.Context, groupKey return items, nil } +const FetchUnspentSupplyPreCommits = `-- name: FetchUnspentSupplyPreCommits :many +SELECT + chain_txns.block_height, + chain_txns.raw_tx, + pre_commit.outpoint, + pre_commit.taproot_internal_key, + pre_commit.group_key +FROM supply_pre_commits pre_commit + JOIN chain_txns ON pre_commit.chain_txn_db_id = chain_txns.txn_id +WHERE + pre_commit.group_key = $1 AND + pre_commit.spent_by IS NULL +` + +type FetchUnspentSupplyPreCommitsRow struct { + BlockHeight sql.NullInt32 + RawTx []byte + Outpoint []byte + TaprootInternalKey []byte + GroupKey []byte +} + +// Fetch unspent supply pre-commitment outputs. Each pre-commitment output +// comes from a mint anchor transaction and relates to an asset issuance +// where a peer node acted as the issuer. Rows in this table do not relate to an +// issuance where the local node acted as the issuer. +func (q *Queries) FetchUnspentSupplyPreCommits(ctx context.Context, groupKey []byte) ([]FetchUnspentSupplyPreCommitsRow, error) { + rows, err := q.db.QueryContext(ctx, FetchUnspentSupplyPreCommits, groupKey) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FetchUnspentSupplyPreCommitsRow + for rows.Next() { + var i FetchUnspentSupplyPreCommitsRow + if err := rows.Scan( + &i.BlockHeight, + &i.RawTx, + &i.Outpoint, + &i.TaprootInternalKey, + &i.GroupKey, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const FinalizeSupplyCommitTransition = `-- name: FinalizeSupplyCommitTransition :exec UPDATE supply_commit_transitions SET finalized = TRUE From 423306a3dfd0d184a4c35b0637642919d4a48789 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 9 Sep 2025 01:19:38 +0100 Subject: [PATCH 13/16] tapdb: extend UnspentPrecommits to include remote issuers Update method SupplyCommitMachine.UnspentPrecommits to return pre-commitment outputs generated by remote issuer nodes, in addition to those from the local node. --- tapdb/supply_commit.go | 111 +++++++++++++++++--- tapdb/supply_commit_test.go | 32 ++++-- universe/supplycommit/env.go | 4 +- universe/supplycommit/mock.go | 5 +- universe/supplycommit/state_machine_test.go | 1 + universe/supplycommit/transitions.go | 2 +- universe/supplyverifier/env.go | 3 +- universe/supplyverifier/verifier.go | 2 +- 8 files changed, 131 insertions(+), 29 deletions(-) diff --git a/tapdb/supply_commit.go b/tapdb/supply_commit.go index 859918ba6..3603f5b62 100644 --- a/tapdb/supply_commit.go +++ b/tapdb/supply_commit.go @@ -10,6 +10,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" @@ -20,14 +21,21 @@ import ( "github.com/lightninglabs/taproot-assets/universe/supplycommit" "github.com/lightninglabs/taproot-assets/universe/supplyverifier" lfn "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnutils" ) type ( // UnspentMintPreCommits is an alias for the sqlc type representing an - // unspent supply pre-commitment row. + // unspent supply pre-commitment row where the local node was the + // issuer. UnspentMintPreCommits = sqlc.FetchUnspentMintSupplyPreCommitsRow + // UnspentPreCommits is an alias for the sqlc type representing an + // unspent supply pre-commitment row where a remote node was the + // issuer. + UnspentPreCommits = sqlc.FetchUnspentSupplyPreCommitsRow + // SupplyCommit is an alias for the sqlc type. SupplyCommit = sqlc.FetchSupplyCommitRow @@ -109,6 +117,12 @@ type SupplyCommitStore interface { FetchUnspentMintSupplyPreCommits(ctx context.Context, groupKey []byte) ([]UnspentMintPreCommits, error) + // FetchUnspentSupplyPreCommits fetches all unspent supply + // pre-commitments for the specified asset group key where a remote + // node was the issuer. + FetchUnspentSupplyPreCommits(ctx context.Context, + groupKey []byte) ([]UnspentPreCommits, error) + // FetchSupplyCommit fetches the latest confirmed supply commitment for // a given group key. FetchSupplyCommit(ctx context.Context, @@ -267,7 +281,8 @@ func NewSupplyCommitMachine(db BatchedSupplyCommitStore) *SupplyCommitMachine { // asset spec. The asset spec will only specify a group key, and not also an // asset ID. func (s *SupplyCommitMachine) UnspentPrecommits(ctx context.Context, - assetSpec asset.Specifier) lfn.Result[supplycommit.PreCommits] { + assetSpec asset.Specifier, + localIssuerOnly bool) lfn.Result[supplycommit.PreCommits] { groupKey := assetSpec.UnwrapGroupKeyToPtr() if groupKey == nil { @@ -278,23 +293,25 @@ func (s *SupplyCommitMachine) UnspentPrecommits(ctx context.Context, var preCommits supplycommit.PreCommits readTx := ReadTxOption() dbErr := s.db.ExecTx(ctx, readTx, func(db SupplyCommitStore) error { - rows, err := db.FetchUnspentMintSupplyPreCommits( + mintRows, err := db.FetchUnspentMintSupplyPreCommits( ctx, groupKeyBytes, ) - if err != nil { - // It's okay if there are no unspent pre-commits. - if errors.Is(err, sql.ErrNoRows) { - return nil - } - return fmt.Errorf("error fetching unspent "+ - "precommits: %w", err) + switch { + case errors.Is(err, sql.ErrNoRows): + // No unspent pre-commits minted by this local node + // exist for this group key. Proceed to query for + // pre-commits from other issuers. + + case err != nil: + return fmt.Errorf("failed to fetch unspent local node "+ + "issued pre-commit outputs: %w", err) } // For each pre-commitment, parse the internal key and group // key, and assemble the final struct as needed by the // interface. - preCommits = make(supplycommit.PreCommits, 0, len(rows)) - for _, row := range rows { + preCommits = make(supplycommit.PreCommits, 0, len(mintRows)) + for _, row := range mintRows { internalKey, err := parseInternalKey(row.InternalKey) if err != nil { return fmt.Errorf("failed to parse "+ @@ -326,6 +343,76 @@ func (s *SupplyCommitMachine) UnspentPrecommits(ctx context.Context, preCommits = append(preCommits, preCommit) } + // If any pre-commits were found where we acted as the issuer, + // return early and skip querying for pre-commits from other + // issuers. Also return early if the caller explicitly requested + // only pre-commits issued by the local node. + if len(preCommits) > 0 || localIssuerOnly { + return nil + } + + // No pre-commits found where we were the issuer. So now + // we'll query for pre-commits from other issuers. + rows, err := db.FetchUnspentSupplyPreCommits( + ctx, schnorr.SerializePubKey(groupKey), + ) + switch { + case errors.Is(err, sql.ErrNoRows): + // No unspent pre-commits minted by peer issuer nodes + // exist for this group key. Return early. + return nil + + case err != nil: + return fmt.Errorf("failed to fetch unspent remote "+ + "node issued pre-commit outputs: %w", err) + } + + // Parse rows into pre-commitment structs. + for _, row := range rows { + pubKey, err := btcec.ParsePubKey(row.TaprootInternalKey) + if err != nil { + return fmt.Errorf("failed to parse internal "+ + "raw key: %w", err) + } + + internalKey := keychain.KeyDescriptor{ + PubKey: pubKey, + } + + groupPubKey, err := schnorr.ParsePubKey(row.GroupKey) + if err != nil { + return fmt.Errorf("error parsing group key: %w", + err) + } + + var mintingTx wire.MsgTx + err = mintingTx.Deserialize(bytes.NewReader(row.RawTx)) + if err != nil { + return fmt.Errorf("error deserializing "+ + "minting tx: %w", err) + } + + var outpoint wire.OutPoint + err = readOutPoint( + bytes.NewReader(row.Outpoint), 0, 0, &outpoint, + ) + if err != nil { + return fmt.Errorf("%w: %w", ErrReadOutpoint, + err) + } + + preCommit := supplycommit.PreCommitment{ + BlockHeight: uint32( + row.BlockHeight.Int32, + ), + MintingTxn: &mintingTx, + OutIdx: outpoint.Index, + InternalKey: internalKey, + GroupPubKey: *groupPubKey, + } + preCommits = append(preCommits, preCommit) + } + return nil }) if dbErr != nil { diff --git a/tapdb/supply_commit_test.go b/tapdb/supply_commit_test.go index a7f53bf4c..b03c457ac 100644 --- a/tapdb/supply_commit_test.go +++ b/tapdb/supply_commit_test.go @@ -1705,7 +1705,9 @@ func TestSupplyCommitApplyStateTransition(t *testing.T) { // Verify we have all three unspent pre-commitments before the // transition. - precommitsRes := h.commitMachine.UnspentPrecommits(h.ctx, h.assetSpec) + precommitsRes := h.commitMachine.UnspentPrecommits( + h.ctx, h.assetSpec, true, + ) precommits, err := precommitsRes.Unpack() require.NoError(t, err) require.Len( @@ -1730,7 +1732,9 @@ func TestSupplyCommitApplyStateTransition(t *testing.T) { // After the first transition, only the two pre-commitments that were // included in the transaction inputs should be marked as spent. // The extra pre-commitment should remain unspent. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, h.assetSpec) + precommitsRes = h.commitMachine.UnspentPrecommits( + h.ctx, h.assetSpec, true, + ) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Len( @@ -1760,7 +1764,9 @@ func TestSupplyCommitApplyStateTransition(t *testing.T) { ) // Verify we have the extra one from before plus the new one. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, h.assetSpec) + precommitsRes = h.commitMachine.UnspentPrecommits( + h.ctx, h.assetSpec, true, + ) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Len( @@ -1783,7 +1789,9 @@ func TestSupplyCommitApplyStateTransition(t *testing.T) { // After the second transition, the new pre-commitment should also be // spent. Finally, verify that no unspent pre-commitments remain. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, h.assetSpec) + precommitsRes = h.commitMachine.UnspentPrecommits( + h.ctx, h.assetSpec, true, + ) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Empty( @@ -1805,7 +1813,7 @@ func TestSupplyCommitUnspentPrecommits(t *testing.T) { ) // To start with, we shouldn't have any precommits. - precommitsRes := h.commitMachine.UnspentPrecommits(h.ctx, spec) + precommitsRes := h.commitMachine.UnspentPrecommits(h.ctx, spec, true) precommits, err := precommitsRes.Unpack() require.NoError(t, err) require.Empty(t, precommits) @@ -1819,7 +1827,7 @@ func TestSupplyCommitUnspentPrecommits(t *testing.T) { ) // At this point, we should find a single pre commitment on disk. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec) + precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec, true) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Len(t, precommits, 1) @@ -1839,7 +1847,7 @@ func TestSupplyCommitUnspentPrecommits(t *testing.T) { ) // We should now find two pre-commitments. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec) + precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec, true) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Len(t, precommits, 2) @@ -1861,7 +1869,7 @@ func TestSupplyCommitUnspentPrecommits(t *testing.T) { // As the transaction was confirmed above, we should now only have a // single pre commitment on disk. - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec) + precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, spec, true) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Len(t, precommits, 1) @@ -1872,14 +1880,18 @@ func TestSupplyCommitUnspentPrecommits(t *testing.T) { otherSpec := asset.NewSpecifierOptionalGroupPubKey( asset.RandID(t), otherGroupKey, ) - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, otherSpec) + precommitsRes = h.commitMachine.UnspentPrecommits( + h.ctx, otherSpec, true, + ) precommits, err = precommitsRes.Unpack() require.NoError(t, err) require.Empty(t, precommits) // Finally, trying with a missing group key should yield an error. emptySpec := asset.NewSpecifierOptionalGroupKey(asset.RandID(t), nil) - precommitsRes = h.commitMachine.UnspentPrecommits(h.ctx, emptySpec) + precommitsRes = h.commitMachine.UnspentPrecommits( + h.ctx, emptySpec, true, + ) require.ErrorIs(t, precommitsRes.Err(), ErrMissingGroupKey) } diff --git a/universe/supplycommit/env.go b/universe/supplycommit/env.go index 37be5cd4c..05e06738c 100644 --- a/universe/supplycommit/env.go +++ b/universe/supplycommit/env.go @@ -645,8 +645,8 @@ type CommitmentTracker interface { // UnspentPrecommits returns the set of unspent pre-commitments for a // given asset spec. The asset spec will only specify a group key, and // not also an asset ID. - UnspentPrecommits(ctx context.Context, - assetSpec asset.Specifier) lfn.Result[PreCommits] + UnspentPrecommits(ctx context.Context, assetSpec asset.Specifier, + localIssuerOnly bool) lfn.Result[PreCommits] // SupplyCommit returns the root commitment for a given asset spec. From // the PoV of the chain, this is a singleton instance. diff --git a/universe/supplycommit/mock.go b/universe/supplycommit/mock.go index 6660e0a4d..974ff2cf0 100644 --- a/universe/supplycommit/mock.go +++ b/universe/supplycommit/mock.go @@ -66,9 +66,10 @@ type mockCommitmentTracker struct { } func (m *mockCommitmentTracker) UnspentPrecommits(ctx context.Context, - assetSpec asset.Specifier) lfn.Result[PreCommits] { + assetSpec asset.Specifier, + localIssuerOnly bool) lfn.Result[PreCommits] { - args := m.Called(ctx, assetSpec) + args := m.Called(ctx, assetSpec, localIssuerOnly) return args.Get(0).(lfn.Result[PreCommits]) } diff --git a/universe/supplycommit/state_machine_test.go b/universe/supplycommit/state_machine_test.go index 73e4ec83e..b3a62f2a5 100644 --- a/universe/supplycommit/state_machine_test.go +++ b/universe/supplycommit/state_machine_test.go @@ -397,6 +397,7 @@ func (h *supplyCommitTestHarness) expectTreeFetches() { func (h *supplyCommitTestHarness) expectCommitmentFetches() { h.mockCommits.On( "UnspentPrecommits", mock.Anything, mock.Anything, + mock.Anything, ).Return( lfn.Ok[PreCommits](nil), ).Once() diff --git a/universe/supplycommit/transitions.go b/universe/supplycommit/transitions.go index c9b6684f9..9f80837a7 100644 --- a/universe/supplycommit/transitions.go +++ b/universe/supplycommit/transitions.go @@ -719,7 +719,7 @@ func (c *CommitTxCreateState) ProcessEvent(event Event, // commitments, the current supply root (which may not exist), // and the new supply root. preCommits, err := env.Commitments.UnspentPrecommits( - ctx, env.AssetSpec, + ctx, env.AssetSpec, true, ).Unpack() if err != nil { return nil, fmt.Errorf("unable to fetch "+ diff --git a/universe/supplyverifier/env.go b/universe/supplyverifier/env.go index 013ec3400..12b816c78 100644 --- a/universe/supplyverifier/env.go +++ b/universe/supplyverifier/env.go @@ -30,7 +30,8 @@ type SupplyCommitView interface { // UnspentPrecommits returns the set of unspent pre-commitments for a // given asset spec. UnspentPrecommits(ctx context.Context, - assetSpec asset.Specifier) lfn.Result[supplycommit.PreCommits] + assetSpec asset.Specifier, + localIssuerOnly bool) lfn.Result[supplycommit.PreCommits] // SupplyCommit returns the latest supply commitment for a given asset // spec. diff --git a/universe/supplyverifier/verifier.go b/universe/supplyverifier/verifier.go index aa1db54da..daf9ad836 100644 --- a/universe/supplyverifier/verifier.go +++ b/universe/supplyverifier/verifier.go @@ -97,7 +97,7 @@ func (v *Verifier) ensurePrecommitsSpent(ctx context.Context, // Fetch all unspent pre-commitment outputs for the asset group. allPreCommits, err := v.cfg.SupplyCommitView.UnspentPrecommits( - ctx, assetSpec, + ctx, assetSpec, false, ).Unpack() if err != nil { return fmt.Errorf("unable to fetch unspent pre-commitments: %w", From bdf5a0e94da465940c5f81cc827779fc08327072 Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 9 Sep 2025 11:35:19 +0100 Subject: [PATCH 14/16] supplyverifier: assert initial supply commits spend pre-commit outputs Now that supply pre-commitments are stored for remotely issued assets, we can assert in the supply verifier that if a supply commitment is the initial one (i.e., the spent commitment field is None), then the corresponding transaction must spend some pre-commitment outputs. This enforces a link between the initial supply commitment transaction and a mint anchor transaction. --- universe/supplyverifier/verifier.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/universe/supplyverifier/verifier.go b/universe/supplyverifier/verifier.go index daf9ad836..8d9114c6e 100644 --- a/universe/supplyverifier/verifier.go +++ b/universe/supplyverifier/verifier.go @@ -104,11 +104,12 @@ func (v *Verifier) ensurePrecommitsSpent(ctx context.Context, err) } - // TODO(ffranr): If commitment.SpentCommitment is none, then we - // should ensure that at least one pre-commitment is spent. - // Before implementing this check, we need to ensure that - // remote issued supply pre-commitments are correctly populated and - // retrieved from the db. + // If no supply-commitment spend is recorded, require at least one + // unspent mint pre-commitment output for the initial supply commitment. + if commitment.SpentCommitment.IsNone() && len(allPreCommits) == 0 { + return fmt.Errorf("no unspent supply pre-commitment outputs " + + "for the initial supply commitment") + } // Filter pre-commits to only include those that are at block heights // less than or equal to the commitment's anchor block height. All From 8770e5d5bea524694697a2b7ce15bc7709ab513f Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 10 Sep 2025 09:09:18 +0100 Subject: [PATCH 15/16] supplyverifier: fix issuance leaf/proof group key comparison check --- universe/supplyverifier/verifier.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/universe/supplyverifier/verifier.go b/universe/supplyverifier/verifier.go index 8d9114c6e..ebd89ce78 100644 --- a/universe/supplyverifier/verifier.go +++ b/universe/supplyverifier/verifier.go @@ -423,7 +423,7 @@ func (v *Verifier) verifyIssuanceLeaf(ctx context.Context, _, err = issuanceProof.Verify(ctx, nil, lookup, vCtx) if err != nil { - return fmt.Errorf("burn leaf proof failed verification: %w", + return fmt.Errorf("issuance proof failed verification: %w", err) } @@ -440,7 +440,7 @@ func (v *Verifier) verifyIssuanceLeaf(ctx context.Context, } if issuanceLeaf.IsBurn { - return fmt.Errorf("IsBurn is enexpectedly true for issuance " + + return fmt.Errorf("IsBurn is unexpectedly true for issuance " + "leaf") } @@ -463,7 +463,9 @@ func (v *Verifier) verifyIssuanceLeaf(ctx context.Context, return fmt.Errorf("missing group key in issuance leaf") } - if issuanceProof.Asset.GroupKey == issuanceLeaf.GroupKey { + proofGroupPubKey := issuanceProof.Asset.GroupKey.GroupPubKey + leafGroupPubKey := issuanceLeaf.GroupKey.GroupPubKey + if !proofGroupPubKey.IsEqual(&leafGroupPubKey) { return fmt.Errorf("group key in issuance leaf does not match " + "group key in issuance proof") } From e8ea035192c6091a400ecd52f76f86e2d22883cd Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 9 Sep 2025 11:53:42 +0100 Subject: [PATCH 16/16] docs: add release notes --- docs/release-notes/release-notes-0.7.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/release-notes-0.7.0.md b/docs/release-notes/release-notes-0.7.0.md index aa5d59c12..cd0b35b9b 100644 --- a/docs/release-notes/release-notes-0.7.0.md +++ b/docs/release-notes/release-notes-0.7.0.md @@ -72,6 +72,7 @@ - https://github.com/lightninglabs/taproot-assets/pull/1716 - https://github.com/lightninglabs/taproot-assets/pull/1675 - https://github.com/lightninglabs/taproot-assets/pull/1674 + - https://github.com/lightninglabs/taproot-assets/pull/1784 - A new [address version 2 was introduced that supports grouped assets and custom (sender-defined)