diff --git a/tapdb/asset_minting_test.go b/tapdb/asset_minting_test.go index 4d6e3acbe..54a0211c8 100644 --- a/tapdb/asset_minting_test.go +++ b/tapdb/asset_minting_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha256" "database/sql" + "fmt" "math/rand" "os" "testing" @@ -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, ) @@ -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) @@ -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, ) @@ -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, ) @@ -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, ) @@ -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) { @@ -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, @@ -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) { @@ -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( @@ -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. @@ -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)) @@ -1962,32 +2014,34 @@ 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 @@ -1995,12 +2049,12 @@ func TestUpsertMintSupplyPreCommit(t *testing.T) { 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, ) @@ -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, ) } diff --git a/tapgarden/batch_test.go b/tapgarden/batch_test.go index 1cf277dfc..096e61370 100644 --- a/tapgarden/batch_test.go +++ b/tapgarden/batch_test.go @@ -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, }, } diff --git a/tapgarden/mock.go b/tapgarden/mock.go index 1b228dae1..2d6a4c547 100644 --- a/tapgarden/mock.go +++ b/tapgarden/mock.go @@ -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" @@ -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. @@ -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 { @@ -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 } @@ -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{} @@ -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 @@ -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. diff --git a/tapgarden/planter.go b/tapgarden/planter.go index 6a5a414da..ca3366495 100644 --- a/tapgarden/planter.go +++ b/tapgarden/planter.go @@ -921,24 +921,101 @@ func fetchPreCommitGroupKey( return fn.Some(groupAnchorSeedling.GroupInfo.GroupPubKey), nil } +// anchorTxFeeRate computes the fee rate for the anchor transaction. If a fee +// rate is manually assigned for the batch, it is used. Otherwise, the fee rate +// is estimated based on the current network conditions. +func (c *ChainPlanter) anchorTxFeeRate(ctx context.Context, + manualFeeRateOpt fn.Option[chainfee.SatPerKWeight]) ( + chainfee.SatPerKWeight, error) { + + var zero chainfee.SatPerKWeight + + // First, we'll fetch the minimum relay fee for the target chain. + // We'll use this to ensure that the fee rate we use meets the + // minimum requirements. + minRelayFee, err := c.cfg.Wallet.MinRelayFee(ctx) + if err != nil { + return zero, fmt.Errorf("unable to obtain min relay fee: %w", + err) + } + + // If provided and valid, use the manual fee rate. + if manualFeeRateOpt.IsSome() { + manualFeeRate, err := manualFeeRateOpt.UnwrapOrErr( + fmt.Errorf("code error: no manual fee rate"), + ) + if err != nil { + return zero, err + } + + log.Debug("Manual fee rate specified for batch anchor tx: %s", + manualFeeRate.String()) + + // Ensure that the manual fee rate is above the minimum relay + // fee. + if manualFeeRate < minRelayFee { + return zero, fmt.Errorf("manual fee rate less than "+ + "min relay fee: (manual_fee_rate=%s, "+ + "min_relay_fee=%s)", manualFeeRate.String(), + minRelayFee.String()) + } + + return manualFeeRate, nil + } + + log.Debug("No manual fee rate specified for batch, " + + "querying chain backend for fee rate") + + // We'll ask the chain backend to estimate a fee rate that should get + // the batch anchor tx into the next block. + chainFeeRate, err := c.cfg.ChainBridge.EstimateFee( + ctx, GenesisConfTarget, + ) + if err != nil { + return zero, fmt.Errorf("failed to call chain backend for "+ + "fee estimate: %w", err) + } + + log.Debugf("Chain backend returned fee rate: %s", chainFeeRate.String()) + + // If the chain backend provided fee rate is less than the minimum relay + // fee, we'll use the min relay fee instead. + if chainFeeRate < minRelayFee { + log.Debugf("Chain backend provided fee rate less than min "+ + "relay fee, using min relay fee "+ + "(chain_backend_fee_rate=%s, min_relay_fee=%s)", + chainFeeRate.String(), minRelayFee.String()) + return minRelayFee, nil + } + + // Otherwise, we'll use the fee rate as provided by the chain + // backend. + log.Debugf("Using fee rate from chain backend: %s", + chainFeeRate.String()) + return chainFeeRate, nil +} + +// WalletFundPsbt is a function that funds a PSBT packet. +type WalletFundPsbt = func(ctx context.Context, + anchorPkt psbt.Packet) (tapsend.FundedPsbt, error) + // fundGenesisPsbt generates a PSBT packet we'll use to create an asset. In // order to be able to create an asset, we need an initial genesis outpoint. To // obtain this we'll ask the wallet to fund a PSBT template for GenesisAmtSats // (all outputs need to hold some BTC to not be dust), and with a dummy script. // We need to use a dummy script as we can't know the actual script key since // that's dependent on the genesis outpoint. -func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, - batchKey asset.SerializedKey, - manualFeeRate *chainfee.SatPerKWeight) (FundedMintAnchorPsbt, error) { +func fundGenesisPsbt(ctx context.Context, chainParams address.ChainParams, + pendingBatch *MintingBatch, + walletFundPsbt WalletFundPsbt) (FundedMintAnchorPsbt, error) { var zero FundedMintAnchorPsbt - log.Infof("Attempting to fund batch: %x", batchKey) // If universe commitments are enabled, we formulate a pre-commitment // output. This output is spent by the universe commitment transaction. var delegationKey fn.Option[DelegationKey] - if c.pendingBatch != nil && c.pendingBatch.SupplyCommitments { - delegationK, err := fetchDelegationKey(c.pendingBatch) + if pendingBatch != nil && pendingBatch.SupplyCommitments { + delegationK, err := fetchDelegationKey(pendingBatch) if err != nil { return zero, fmt.Errorf("unable to create "+ "pre-commitment output: %w", err) @@ -975,59 +1052,7 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, } log.Tracef("Unfunded batch anchor PSBT: %v", spew.Sdump(genesisPkt)) - // Compute the anchor transaction fee rate. - var feeRate chainfee.SatPerKWeight - switch { - // If a fee rate was manually assigned for this batch, use that instead - // of a fee rate estimate. - case manualFeeRate != nil: - feeRate = *manualFeeRate - log.Infof("using manual fee rate for batch: %x, %s, %d sat/vB", - batchKey[:], feeRate.String(), - feeRate.FeePerKVByte()/1000) - - default: - feeRate, err = c.cfg.ChainBridge.EstimateFee( - ctx, GenesisConfTarget, - ) - if err != nil { - return zero, fmt.Errorf("unable to estimate fee: %w", - err) - } - - log.Infof("estimated fee rate for batch: %x, %s", - batchKey[:], feeRate.FeePerKVByte().String()) - } - - minRelayFee, err := c.cfg.Wallet.MinRelayFee(ctx) - if err != nil { - return zero, fmt.Errorf("unable to obtain minrelayfee: %w", err) - } - - // If the fee rate is below the minimum relay fee, we'll - // bump it up. - if feeRate < minRelayFee { - switch { - // If a fee rate was manually assigned for this batch, we err - // out, otherwise we silently bump the feerate. - case manualFeeRate != nil: - // This case should already have been handled by the - // `checkFeeRateSanity` of `rpcserver.go`. We check here - // again to be safe. - return zero, fmt.Errorf("feerate does not meet "+ - "minrelayfee: (fee_rate=%s, minrelayfee=%s)", - feeRate.String(), minRelayFee.String()) - default: - log.Infof("Bump fee rate for batch %x to meet "+ - "minrelayfee from %s to %s", batchKey[:], - feeRate.String(), minRelayFee.String()) - feeRate = minRelayFee - } - } - - fundedGenesisPkt, err := c.cfg.Wallet.FundPsbt( - ctx, &genesisPkt, 1, feeRate, -1, - ) + fundedGenesisPkt, err := walletFundPsbt(ctx, genesisPkt) if err != nil { return zero, fmt.Errorf("unable to fund psbt: %w", err) } @@ -1038,12 +1063,11 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, "funded anchor transaction") } - log.Infof("Funded GenesisPacket for batch: %x", batchKey) log.Tracef("GenesisPacket: %v", spew.Sdump(fundedGenesisPkt)) // Classify anchor transaction output indexes. anchorOutIndexes, err := anchorTxOutputIndexes( - *fundedGenesisPkt, preCommitmentTxOut, + fundedGenesisPkt, preCommitmentTxOut, ) if err != nil { return zero, fmt.Errorf("unable to determine output indexes: "+ @@ -1076,7 +1100,7 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, // If there is a group pub key to associate with the pre-commitment // output, fetch it now. - preCommitGroupPubKey, err := fetchPreCommitGroupKey(c.pendingBatch) + preCommitGroupPubKey, err := fetchPreCommitGroupKey(pendingBatch) if err != nil { return zero, fmt.Errorf("unable to fetch pre-commitment "+ "group key: %w", err) @@ -1110,7 +1134,7 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, // we just need to set the corresponding fields in the PSBT. bip32Derivation, trBip32Derivation := tappsbt.Bip32DerivationFromKeyDesc( - dKey, c.cfg.ChainParams.HDCoinType, + dKey, chainParams.HDCoinType, ) pOut := &fundedGenesisPkt.Pkt.Outputs[outIdx] @@ -1124,7 +1148,7 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context, // Formulate a funded minting anchor PSBT from the funded PSBT. fundedMintAnchorPsbt, err := NewFundedMintAnchorPsbt( - *fundedGenesisPkt, anchorOutIndexes, preCommitOut, + fundedGenesisPkt, anchorOutIndexes, preCommitOut, ) if err != nil { return zero, fmt.Errorf("unable to create funded minting "+ @@ -2104,7 +2128,6 @@ func (c *ChainPlanter) fundBatch(ctx context.Context, params FundParams, workingBatch *MintingBatch) error { var ( - feeRate *chainfee.SatPerKWeight rootHash *chainhash.Hash err error ) @@ -2112,7 +2135,6 @@ func (c *ChainPlanter) fundBatch(ctx context.Context, params FundParams, // If a tapscript tree was specified for this batch, we'll store it on // disk. The caretaker we start for this batch will use it when deriving // the final Taproot output key. - feeRate = params.FeeRate.UnwrapToPtr() params.SiblingTapTree.WhenSome(func(tn asset.TapscriptTreeNodes) { rootHash, err = c.cfg.TreeStore.StoreTapscriptTree(ctx, tn) }) @@ -2130,13 +2152,42 @@ func (c *ChainPlanter) fundBatch(ctx context.Context, params FundParams, } // Fund the batch with the specified fee rate. + feeRate, err := c.anchorTxFeeRate(ctx, params.FeeRate) + if err != nil { + return fmt.Errorf("unable to determine anchor TX "+ + "fee rate: %w", err) + } + batchKey := asset.ToSerialized(batch.BatchKey.PubKey) - mintAnchorTx, err := c.fundGenesisPsbt(ctx, batchKey, feeRate) + + // walletFundPsbt is a closure that will be used to fund the + // batch with the specified fee rate. + walletFundPsbt := func(ctx context.Context, + anchorPkt psbt.Packet) (tapsend.FundedPsbt, error) { + + var zero tapsend.FundedPsbt + + fundedPkt, err := c.cfg.Wallet.FundPsbt( + ctx, &anchorPkt, 1, feeRate, -1, + ) + if err != nil { + return zero, err + } + + return *fundedPkt, nil + } + + log.Infof("Attempting to fund batch: %x", batchKey) + mintAnchorTx, err := fundGenesisPsbt( + ctx, c.cfg.ChainParams, c.pendingBatch, + walletFundPsbt, + ) if err != nil { return fmt.Errorf("unable to fund minting PSBT for "+ "batch: %x %w", batchKey[:], err) } + log.Infof("Funded GenesisPacket for batch: %x", batchKey) batch.GenesisPacket = &mintAnchorTx return nil diff --git a/tapgarden/planter_test.go b/tapgarden/planter_test.go index 4ee67d536..8fee83d60 100644 --- a/tapgarden/planter_test.go +++ b/tapgarden/planter_test.go @@ -1453,7 +1453,7 @@ func testFinalizeBatch(t *mintingTestHarness) { t.assertNoPendingBatch() t.assertNumCaretakersActive(caretakerCount) t.assertLastBatchState(batchCount, tapgarden.BatchStateFrozen) - t.assertFinalizeBatch(&wg, respChan, "unable to estimate fee") + t.assertFinalizeBatch(&wg, respChan, "failed to estimate fee") // Queue another batch, reset fee estimation behavior, and set TX // confirmation registration to fail.