Skip to content
116 changes: 85 additions & 31 deletions tapdb/asset_minting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"crypto/sha256"
"database/sql"
"fmt"
"math/rand"
"os"
"testing"
Expand Down Expand Up @@ -503,7 +504,9 @@ func TestCommitMintingBatchSeedlings(t *testing.T) {
// First, we'll write a new minting batch to disk, including an
// internal key and a set of seedlings. One random seedling will
// be a reissuance into a specific group.
mintingBatch := tapgarden.RandSeedlingMintingBatch(t, numSeedlings)
mintingBatch := tapgarden.RandMintingBatch(
t, tapgarden.WithTotalSeedlings(numSeedlings),
)
_, randGroup, _ := addRandGroupToBatch(
t, assetStore, ctx, mintingBatch.Seedlings,
)
Expand Down Expand Up @@ -604,7 +607,9 @@ func TestCommitMintingBatchSeedlings(t *testing.T) {

// Insert another normal batch into the database. We should get this
// batch back if we query for the set of non-final batches.
mintingBatch = tapgarden.RandSeedlingMintingBatch(t, numSeedlings)
mintingBatch = tapgarden.RandMintingBatch(
t, tapgarden.WithTotalSeedlings(numSeedlings),
)
err = assetStore.CommitMintingBatch(ctx, mintingBatch)
require.NoError(t, err)
mintingBatches = noError1(t, assetStore.FetchNonFinalBatches, ctx)
Expand Down Expand Up @@ -851,7 +856,9 @@ func TestAddSproutsToBatch(t *testing.T) {

// First, we'll create a new batch, then add some sample seedlings.
// One random seedling will be a reissuance into a specific group.
mintingBatch := tapgarden.RandSeedlingMintingBatch(t, numSeedlings)
mintingBatch := tapgarden.RandMintingBatch(
t, tapgarden.WithTotalSeedlings(numSeedlings),
)
_, seedlingGroups, _ := addRandGroupToBatch(
t, assetStore, ctx, mintingBatch.Seedlings,
)
Expand Down Expand Up @@ -949,7 +956,9 @@ type randAssetCtx struct {
func addRandAssets(t *testing.T, ctx context.Context,
assetStore *AssetMintingStore, numAssets int) randAssetCtx {

mintingBatch := tapgarden.RandSeedlingMintingBatch(t, numAssets)
mintingBatch := tapgarden.RandMintingBatch(
t, tapgarden.WithTotalSeedlings(numAssets),
)
genAmt, seedlingGroups, group := addRandGroupToBatch(
t, assetStore, ctx, mintingBatch.Seedlings,
)
Expand Down Expand Up @@ -1463,7 +1472,9 @@ func TestGroupAnchors(t *testing.T) {
// internal key and a set of seedlings. One random seedling will
// be a reissuance into a specific group. Two other seedlings will form
// a multi-asset group.
mintingBatch := tapgarden.RandSeedlingMintingBatch(t, numSeedlings)
mintingBatch := tapgarden.RandMintingBatch(
t, tapgarden.WithTotalSeedlings(numSeedlings),
)
_, seedlingGroups, _ := addRandGroupToBatch(
t, assetStore, ctx, mintingBatch.Seedlings,
)
Expand Down Expand Up @@ -1839,7 +1850,7 @@ func TestTapscriptTreeManager(t *testing.T) {
// storeMintSupplyPreCommit stores a mint anchor supply pre-commitment in the
// DB.
func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore,
batchKey []byte, txOutputIndex int32,
batchKey []byte, txOutputIndex uint32,
taprootInternalKey keychain.KeyDescriptor, groupKey []byte,
outpoint wire.OutPoint) {

Expand All @@ -1863,7 +1874,7 @@ func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore,
_, err = q.UpsertMintSupplyPreCommit(
ctx, UpsertBatchPreCommitParams{
BatchKey: batchKey,
TxOutputIndex: txOutputIndex,
TxOutputIndex: int32(txOutputIndex),
TaprootInternalKeyID: internalKeyID,
GroupKey: groupKey,
Outpoint: opBytes,
Expand All @@ -1880,7 +1891,7 @@ func storeMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore,
// 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,
batchKey []byte, txOutputIndex uint32,
preCommitInternalKey keychain.KeyDescriptor, groupPubKeyBytes []byte,
outpoint wire.OutPoint) {

Expand All @@ -1906,7 +1917,7 @@ func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore,
// Ensure the mint anchor commitment matches the one we inserted.
require.NotNil(t, preCommit)
require.Equal(t, batchKey, preCommit.BatchKey)
require.Equal(t, txOutputIndex, preCommit.TxOutputIndex)
require.EqualValues(t, txOutputIndex, preCommit.TxOutputIndex)

rawInternalKey := preCommitInternalKey.PubKey.SerializeCompressed()
require.Equal(
Expand All @@ -1927,6 +1938,39 @@ func assertMintSupplyPreCommit(t *testing.T, assetStore AssetMintingStore,
require.Equal(t, opBytes, preCommit.Outpoint)
}

// storeSeedlingGroupGenesis stores the group genesis and an initial asset
// associated with the given seedling in the DB. This is necessary before we can
// commit a minting batch that contains the seedling.
func storeSeedlingGroupGenesis(t *testing.T, ctx context.Context,
assetStore *AssetMintingStore, seedling tapgarden.Seedling) {

genesis := seedling.GroupInfo.Genesis
groupKey := seedling.GroupInfo.GroupKey
initialAsset := asset.RandAssetWithValues(
t, *genesis, groupKey, asset.RandScriptKey(t),
)

upsertAsset := func(q PendingAssetStore) error {
_, err := maybeUpsertAssetMeta(ctx, q, genesis, nil)
require.NoError(t, err)

// Insert a random managed UTXO.
utxoID := addRandomManagedUTXO(t, ctx, q, initialAsset)

_, _, err = upsertAssetsWithGenesis(
ctx, q, genesis.FirstPrevOut,
[]*asset.Asset{initialAsset},
[]sql.NullInt64{sqlInt64(utxoID)},
)
require.NoError(t, err)
return nil
}

var writeTxOpts AssetStoreTxOptions
err := assetStore.db.ExecTx(ctx, &writeTxOpts, upsertAsset)
require.NoError(t, err)
}

// TestUpsertMintSupplyPreCommit tests the UpsertMintSupplyPreCommit and
// FetchSupplyPreCommits SQL queries. In particular, it tests that upsert works
// correctly.
Expand All @@ -1937,13 +1981,21 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) {
assetStore, _, _ := newAssetStore(t)

// Create a new batch with one asset group seedling.
mintingBatch := tapgarden.RandSeedlingMintingBatch(t, 1)
mintingBatch.SupplyCommitments = true

_, _, group := addRandGroupToBatch(
t, assetStore, ctx, mintingBatch.Seedlings,
mintingBatch := tapgarden.RandMintingBatch(
t, tapgarden.WithTotalGroups([]int{1}),
tapgarden.WithUniverseCommitments(true),
)

// Store group genesis associated with seedling. This is necessary
// before we can commit the batch.
require.Len(t, mintingBatch.Seedlings, 1)
var seedling tapgarden.Seedling
for _, s := range mintingBatch.Seedlings {
seedling = *s
break
}
storeSeedlingGroupGenesis(t, ctx, assetStore, seedling)

// Commit batch.
require.NoError(t, assetStore.CommitMintingBatch(ctx, mintingBatch))

Expand All @@ -1962,45 +2014,47 @@ 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()
genesisPkt := mintingBatch.GenesisPacket
require.NotNil(t, genesisPkt)

preCommitOut, err := genesisPkt.PreCommitmentOutput.UnwrapOrErr(
fmt.Errorf("no pre-commitment output"),
)
require.NoError(t, err)

txidStr := genesisPkt.FundedPsbt.Pkt.UnsignedTx.TxID()
txid, err := chainhash.NewHashFromStr(txidStr)
require.NoError(t, err)

preCommitOutpoint := wire.OutPoint{
Hash: *txid,
Index: uint32(txOutputIndex),
Index: preCommitOut.OutIdx,
}

// Serialize keys into bytes for easier handling.
preCommitInternalKey, _ := test.RandKeyDesc(t)

groupPubKeyBytes := schnorr.SerializePubKey(&group.GroupPubKey)

// Upsert a mint anchor commitment for the batch.
storeMintSupplyPreCommit(
t, *assetStore, batchKey, txOutputIndex,
preCommitInternalKey, groupPubKeyBytes, preCommitOutpoint,
preCommitGroupKey, err := preCommitOut.GroupPubKey.UnwrapOrErr(
fmt.Errorf("no group key"),
)
require.NoError(t, err)
groupPubKeyBytes := schnorr.SerializePubKey(&preCommitGroupKey)

// Retrieve and inspect the mint anchor commitment we just inserted.
assertMintSupplyPreCommit(
t, *assetStore, batchKey, txOutputIndex,
preCommitInternalKey, groupPubKeyBytes, preCommitOutpoint,
t, *assetStore, batchKey, preCommitOut.OutIdx,
preCommitOut.InternalKey, groupPubKeyBytes, preCommitOutpoint,
)

// 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,
t, *assetStore, batchKey, preCommitOut.OutIdx, internalKey2,
groupPubKeyBytes, preCommitOutpoint,
)

assertMintSupplyPreCommit(
t, *assetStore, batchKey, txOutputIndex, internalKey2,
t, *assetStore, batchKey, preCommitOut.OutIdx, internalKey2,
groupPubKeyBytes, preCommitOutpoint,
)

Expand All @@ -2010,12 +2064,12 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) {
groupPubKey2Bytes := schnorr.SerializePubKey(groupPubKey2)

storeMintSupplyPreCommit(
t, *assetStore, batchKey, txOutputIndex, internalKey2,
t, *assetStore, batchKey, preCommitOut.OutIdx, internalKey2,
groupPubKey2Bytes, preCommitOutpoint,
)

assertMintSupplyPreCommit(
t, *assetStore, batchKey, txOutputIndex, internalKey2,
t, *assetStore, batchKey, preCommitOut.OutIdx, internalKey2,
groupPubKey2Bytes, preCommitOutpoint,
)
}
Expand Down
6 changes: 3 additions & 3 deletions tapgarden/batch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ func TestValidateUniCommitment(t *testing.T) {
// Ensures that a group anchor candidate seedling
// with universe commitments can be added to an empty
// batch.
name: "empty batch; candidate seedling is group " +
"anchor; is valid",
name: "empty unfunded batch; candidate seedling is " +
"group anchor; is valid",

candidateSeedling: RandGroupAnchorSeedling(
t, "some-anchor-name", true,
),
batch: RandMintingBatch(t),
batch: RandMintingBatch(t, WithSkipFunding()),
expectErr: false,
},
}
Expand Down
70 changes: 44 additions & 26 deletions tapgarden/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/taproot-assets/address"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
Expand Down Expand Up @@ -163,6 +164,9 @@ type MintBatchOptions struct {
// universeCommitments specifies whether to generate universe
// commitments for the asset groups in this minting batch.
universeCommitments bool

// skipFunding specifies whether to skip funding the genesis PSBT.
skipFunding bool
}

// MintBatchOption is a functional option for creating a new minting batch.
Expand Down Expand Up @@ -198,6 +202,13 @@ func WithUniverseCommitments(enabled bool) MintBatchOption {
}
}

// WithSkipFunding specifies whether to skip funding the genesis PSBT.
func WithSkipFunding() MintBatchOption {
return func(options *MintBatchOptions) {
options.skipFunding = true
}
}

// RandMintingBatch creates a new minting batch with only random seedlings
// populated for testing.
func RandMintingBatch(t testing.TB, opts ...MintBatchOption) *MintingBatch {
Expand Down Expand Up @@ -259,6 +270,32 @@ func RandMintingBatch(t testing.TB, opts ...MintBatchOption) *MintingBatch {
// requested amount. This check might help debug flakes in tests.
require.Equal(t, options.totalSeedlings, len(batch.Seedlings))

// Return early if funding is to be skipped.
if options.skipFunding {
return batch
}

walletFundPsbt := func(ctx context.Context,
anchorPkt psbt.Packet) (tapsend.FundedPsbt, error) {

changeOutputIdx := FundGenesisTx(
&anchorPkt, chainfee.FeePerKwFloor,
)

return tapsend.FundedPsbt{
Pkt: &anchorPkt,
ChangeOutputIndex: int32(changeOutputIdx),
}, nil
}

// Fund genesis packet.
ctx := context.Background()
fundedPsbt, err := fundGenesisPsbt(
ctx, address.TestNet3Tap, batch, walletFundPsbt,
)
require.NoError(t, err)
batch.GenesisPacket = &fundedPsbt

return batch
}

Expand Down Expand Up @@ -286,29 +323,6 @@ func RandSeedlings(t testing.TB, numSeedlings int) map[string]*Seedling {
return seedlings
}

// RandSeedlingMintingBatch creates a new minting batch with only random
// seedlings populated for testing.
//
// TODO(ffranr): Replace this function with RandMintingBatch. Note also function
// addRandGroupToBatch.
func RandSeedlingMintingBatch(t testing.TB, numSeedlings int) *MintingBatch {
genesisTx := NewGenesisTx(t, chainfee.FeePerKwFloor)
BatchKey, _ := test.RandKeyDesc(t)
return &MintingBatch{
BatchKey: BatchKey,
Seedlings: RandSeedlings(t, numSeedlings),
HeightHint: test.RandInt[uint32](),
CreationTime: time.Now(),
GenesisPacket: &FundedMintAnchorPsbt{
FundedPsbt: tapsend.FundedPsbt{
Pkt: &genesisTx,
ChangeOutputIndex: 1,
},
AssetAnchorOutIdx: 0,
},
}
}

type MockWalletAnchor struct {
FundPsbtSignal chan *tapsend.FundedPsbt
SignPsbtSignal chan struct{}
Expand Down Expand Up @@ -345,8 +359,9 @@ func NewGenesisTx(t testing.TB, feeRate chainfee.SatPerKWeight) psbt.Packet {
return *genesisPkt
}

// FundGenesisTx add a genesis input and change output to a 1-output TX.
func FundGenesisTx(packet *psbt.Packet, feeRate chainfee.SatPerKWeight) {
// FundGenesisTx add a genesis input and change output to a 1-output TX and
// returns the index of the change output.
func FundGenesisTx(packet *psbt.Packet, feeRate chainfee.SatPerKWeight) uint32 {
const anchorBalance = int64(100000)

// Take the PSBT packet and add an additional input and output to
Expand Down Expand Up @@ -382,7 +397,10 @@ func FundGenesisTx(packet *psbt.Packet, feeRate chainfee.SatPerKWeight) {
[][]byte{tapsend.GenesisDummyScript}, packet.UnsignedTx.TxOut,
feeRate,
)
packet.UnsignedTx.TxOut[1].Value -= int64(fee)
changeOutputIdx := len(packet.UnsignedTx.TxOut) - 1
packet.UnsignedTx.TxOut[changeOutputIdx].Value -= int64(fee)

return uint32(changeOutputIdx)
}

// FundPsbt funds a PSBT.
Expand Down
Loading
Loading