44 "bytes"
55 "context"
66 "database/sql"
7+ "encoding/binary"
78 "encoding/hex"
89 "fmt"
910 "math/rand"
@@ -21,6 +22,7 @@ import (
2122 "github.com/davecgh/go-spew/spew"
2223 tap "github.com/lightninglabs/taproot-assets"
2324 "github.com/lightninglabs/taproot-assets/asset"
25+ "github.com/lightninglabs/taproot-assets/commitment"
2426 "github.com/lightninglabs/taproot-assets/fn"
2527 "github.com/lightninglabs/taproot-assets/internal/test"
2628 "github.com/lightninglabs/taproot-assets/proof"
@@ -32,6 +34,7 @@ import (
3234 "github.com/lightningnetwork/lnd/build"
3335 "github.com/lightningnetwork/lnd/keychain"
3436 "github.com/lightningnetwork/lnd/lntest/wait"
37+ "github.com/lightningnetwork/lnd/lnwallet/chainfee"
3538 "github.com/lightningnetwork/lnd/ticker"
3639 "github.com/stretchr/testify/require"
3740)
@@ -244,15 +247,26 @@ type FinalizeBatchResp struct {
244247// finalizeBatch uses the public FinalizeBatch planter call to start a caretaker
245248// for an existing batch. The caller must wait for the planter call to complete.
246249func (t * mintingTestHarness ) finalizeBatch (wg * sync.WaitGroup ,
247- respChan chan * FinalizeBatchResp ) {
250+ respChan chan * FinalizeBatchResp , params * tapgarden. FinalizeParams ) {
248251
249252 t .Helper ()
250253
251254 wg .Add (1 )
252255 go func () {
253256 defer wg .Done ()
254257
255- frozenBatch , finalizeErr := t .planter .FinalizeBatch (nil )
258+ finalizeParams := tapgarden.FinalizeParams {
259+ FeeRate : fn .None [chainfee.SatPerKWeight ](),
260+ SiblingTapTree : fn .None [asset.TapscriptTreeNodes ](),
261+ }
262+
263+ if params != nil {
264+ finalizeParams = * params
265+ }
266+
267+ frozenBatch , finalizeErr := t .planter .FinalizeBatch (
268+ finalizeParams ,
269+ )
256270 resp := & FinalizeBatchResp {
257271 Batch : frozenBatch ,
258272 Err : finalizeErr ,
@@ -263,7 +277,8 @@ func (t *mintingTestHarness) finalizeBatch(wg *sync.WaitGroup,
263277}
264278
265279func (t * mintingTestHarness ) assertFinalizeBatch (wg * sync.WaitGroup ,
266- respChan chan * FinalizeBatchResp , errString string ) {
280+ respChan chan * FinalizeBatchResp ,
281+ errString string ) * tapgarden.MintingBatch {
267282
268283 t .Helper ()
269284
@@ -273,16 +288,18 @@ func (t *mintingTestHarness) assertFinalizeBatch(wg *sync.WaitGroup,
273288 switch {
274289 case errString == "" :
275290 require .NoError (t , finalizeResp .Err )
291+ return finalizeResp .Batch
276292
277293 default :
278294 require .ErrorContains (t , finalizeResp .Err , errString )
295+ return nil
279296 }
280297}
281298
282299// progressCaretaker uses the mock interfaces to progress a caretaker from start
283300// to TX confirmation.
284- func (t * mintingTestHarness ) progressCaretaker (
285- seedlings [] * tapgarden. Seedling , batchSibling * chainhash. Hash ) func () {
301+ func (t * mintingTestHarness ) progressCaretaker (seedlings [] * tapgarden. Seedling ,
302+ batchSibling * commitment. TapscriptPreimage ) func () {
286303
287304 // Assert that the caretaker has requested a genesis TX to be funded.
288305 _ = t .assertGenesisTxFunded ()
@@ -632,7 +649,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
632649// assertGenesisPsbtFinalized asserts that a request to finalize the genesis
633650// transaction has been requested by a caretaker.
634651func (t * mintingTestHarness ) assertGenesisPsbtFinalized (
635- sibling * chainhash. Hash ) {
652+ sibling * commitment. TapscriptPreimage ) {
636653
637654 t .Helper ()
638655
@@ -1115,7 +1132,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11151132 )
11161133
11171134 // Finalize the pending batch to start a caretaker.
1118- t .finalizeBatch (& wg , respChan )
1135+ t .finalizeBatch (& wg , respChan , nil )
11191136 batchCount ++
11201137
11211138 _ , err := fn .RecvOrTimeout (
@@ -1141,7 +1158,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11411158 // caretaker to TX confirmation. The finalize call should report no
11421159 // error, but the caretaker should propagate the confirmation error to
11431160 // the shared error channel.
1144- t .finalizeBatch (& wg , respChan )
1161+ t .finalizeBatch (& wg , respChan , nil )
11451162 batchCount ++
11461163
11471164 _ = t .progressCaretaker (seedlings , nil )
@@ -1165,7 +1182,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11651182 t .chain .EmptyConf (true )
11661183
11671184 // Start a new caretaker that should reach TX broadcast.
1168- t .finalizeBatch (& wg , respChan )
1185+ t .finalizeBatch (& wg , respChan , nil )
11691186 batchCount ++
11701187
11711188 sendConfNtfn := t .progressCaretaker (seedlings , nil )
@@ -1187,15 +1204,15 @@ func testFinalizeBatch(t *mintingTestHarness) {
11871204
11881205 // If we try to finalize without a pending batch, the finalize call
11891206 // should return an error.
1190- t .finalizeBatch (& wg , respChan )
1207+ t .finalizeBatch (& wg , respChan , nil )
11911208 t .assertFinalizeBatch (& wg , respChan , "no pending batch" )
11921209 t .assertNumCaretakersActive (caretakerCount )
11931210
11941211 // Queue another batch and drive the caretaker to a successful minting.
11951212 seedlings = t .queueInitialBatch (numSeedlings )
11961213 t .chain .EmptyConf (false )
11971214
1198- t .finalizeBatch (& wg , respChan )
1215+ t .finalizeBatch (& wg , respChan , nil )
11991216 batchCount ++
12001217
12011218 sendConfNtfn = t .progressCaretaker (seedlings , nil )
@@ -1208,6 +1225,156 @@ func testFinalizeBatch(t *mintingTestHarness) {
12081225 t .assertLastBatchState (batchCount , tapgarden .BatchStateFinalized )
12091226}
12101227
1228+ func testFinalizeWithTapscriptTree (t * mintingTestHarness ) {
1229+ // First, create a new chain planter instance using the supplied test
1230+ // harness.
1231+ t .refreshChainPlanter ()
1232+
1233+ // Create an initial batch of 5 seedlings.
1234+ const numSeedlings = 5
1235+ seedlings := t .queueInitialBatch (numSeedlings )
1236+
1237+ var (
1238+ wg sync.WaitGroup
1239+ respChan = make (chan * FinalizeBatchResp , 1 )
1240+ finalizeReq tapgarden.FinalizeParams
1241+ batchCount = 0
1242+ )
1243+
1244+ // Build a standalone tapscript tree object, that matches the tree
1245+ // created by other test helpers.
1246+ sigLockKey := test .RandPubKey (t )
1247+ hashLockWitness := []byte ("foobar" )
1248+ hashLockLeaf := test .ScriptHashLock (t .T , hashLockWitness )
1249+ sigLeaf := test .ScriptSchnorrSig (t .T , sigLockKey )
1250+ tapTreePreimage , err := asset .TapTreeNodesFromLeaves (
1251+ []txscript.TapLeaf {hashLockLeaf , sigLeaf },
1252+ )
1253+ require .NoError (t , err )
1254+
1255+ finalizeReq = tapgarden.FinalizeParams {
1256+ SiblingTapTree : fn .Some (* tapTreePreimage ),
1257+ }
1258+
1259+ // Force tapscript tree storage to fail, which should cause batch
1260+ // finalization to fail.
1261+ t .treeStore .FailStore = true
1262+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1263+ finalizeErr := <- respChan
1264+ require .ErrorContains (t , finalizeErr .Err , "unable to store" )
1265+
1266+ // Empty the main error channel before reattempting a mint.
1267+ select {
1268+ case <- t .errChan :
1269+ default :
1270+ }
1271+
1272+ // Allow tapscript tree storage to succeed, but force tapscript tree
1273+ // loading to fail.
1274+ t .treeStore .FailStore = false
1275+ t .treeStore .FailLoad = true
1276+
1277+ // Receive all the signals needed to progress the caretaker through
1278+ // the batch sprouting, which is when the sibling tapscript tree is
1279+ // used.
1280+ progressCaretakerToTxSigning := func (
1281+ currentSeedlings []* tapgarden.Seedling ) {
1282+
1283+ _ = t .assertGenesisTxFunded ()
1284+
1285+ for i := 0 ; i < len (currentSeedlings ); i ++ {
1286+ t .assertKeyDerived ()
1287+
1288+ if currentSeedlings [i ].EnableEmission {
1289+ t .assertKeyDerived ()
1290+ }
1291+ }
1292+ }
1293+
1294+ // Finalize the batch with a tapscript tree sibling.
1295+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1296+ batchCount ++
1297+
1298+ // The caretaker should fail when computing the Taproot output key.
1299+ progressCaretakerToTxSigning (seedlings )
1300+ t .assertFinalizeBatch (& wg , respChan , "failed to load tapscript tree" )
1301+ t .assertLastBatchState (batchCount , tapgarden .BatchStateFrozen )
1302+ t .assertNoPendingBatch ()
1303+
1304+ // Reset the tapscript tree store to not force load or store failures.
1305+ t .treeStore .FailStore = false
1306+ t .treeStore .FailLoad = false
1307+
1308+ // Construct a tapscript tree with a single leaf that has the structure
1309+ // of a TapLeaf computed from a TapCommitment. This should be rejected
1310+ // by the caretaker, as the genesis TX for the batch should only commit
1311+ // to one TapCommitment.
1312+ var dummyRootSum [8 ]byte
1313+ binary .BigEndian .PutUint64 (dummyRootSum [:], test .RandInt [uint64 ]())
1314+ dummyRootHashParts := [][]byte {
1315+ {byte (asset .V0 )}, commitment .TaprootAssetsMarker [:],
1316+ fn .ByteSlice (test .RandHash ()), dummyRootSum [:],
1317+ }
1318+ dummyTapCommitmentRootHash := bytes .Join (dummyRootHashParts , nil )
1319+ dummyTapLeaf := txscript .NewBaseTapLeaf (dummyTapCommitmentRootHash )
1320+ dummyTapCommitmentPreimage , err := asset .TapTreeNodesFromLeaves (
1321+ []txscript.TapLeaf {dummyTapLeaf },
1322+ )
1323+ require .NoError (t , err )
1324+
1325+ finalizeReq .SiblingTapTree = fn .Some (* dummyTapCommitmentPreimage )
1326+
1327+ // Queue another batch, and try to finalize with a sibling that is also
1328+ // a Taproot asset commitment.
1329+ seedlings = t .queueInitialBatch (numSeedlings )
1330+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1331+ batchCount ++
1332+
1333+ progressCaretakerToTxSigning (seedlings )
1334+ t .assertFinalizeBatch (
1335+ & wg , respChan , "preimage is a Taproot Asset commitment" ,
1336+ )
1337+ t .assertNoPendingBatch ()
1338+
1339+ // Queue another batch, and provide a valid sibling tapscript tree.
1340+ seedlings = t .queueInitialBatch (numSeedlings )
1341+ finalizeReq .SiblingTapTree = fn .Some (* tapTreePreimage )
1342+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1343+ batchCount ++
1344+
1345+ // Verify that the final genesis TX uses the correct Taproot output key.
1346+ treeRootChildren := test .BuildTapscriptTreeNoReveal (t .T , sigLockKey )
1347+ siblingPreimage := commitment .NewPreimageFromBranch (treeRootChildren )
1348+ sendConfNtfn := t .progressCaretaker (seedlings , & siblingPreimage )
1349+ sendConfNtfn ()
1350+
1351+ // Once the TX is broadcast, the caretaker should run to completion,
1352+ // storing issuance proofs and updating the batch state to finalized.
1353+ batchWithSibling := t .assertFinalizeBatch (& wg , respChan , "" )
1354+ require .NotNil (t , batchWithSibling )
1355+ t .assertNoError ()
1356+ t .assertNoPendingBatch ()
1357+ t .assertNumCaretakersActive (0 )
1358+ t .assertLastBatchState (batchCount , tapgarden .BatchStateFinalized )
1359+
1360+ // Verify that the final minting output key matches what we would derive
1361+ // manually.
1362+ batchRootCommitment := batchWithSibling .RootAssetCommitment
1363+ require .NotNil (t , batchRootCommitment )
1364+ siblingHash , err := siblingPreimage .TapHash ()
1365+ require .NoError (t , err )
1366+ batchScriptRoot := batchRootCommitment .TapscriptRoot (siblingHash )
1367+ batchOutputKeyExpected := txscript .ComputeTaprootOutputKey (
1368+ batchWithSibling .BatchKey .PubKey , batchScriptRoot [:],
1369+ )
1370+ batchOutputKey , _ , err := batchWithSibling .MintingOutputKey (nil )
1371+ require .NoError (t , err )
1372+ require .Equal (
1373+ t , batchOutputKeyExpected .SerializeCompressed (),
1374+ batchOutputKey .SerializeCompressed (),
1375+ )
1376+ }
1377+
12111378// mintingStoreTestCase is used to programmatically run a series of test cases
12121379// that are parametrized based on a fresh minting store.
12131380type mintingStoreTestCase struct {
@@ -1238,6 +1405,11 @@ var testCases = []mintingStoreTestCase{
12381405 interval : minterInterval ,
12391406 testFunc : testFinalizeBatch ,
12401407 },
1408+ {
1409+ name : "finalize_with_tapscript_tree" ,
1410+ interval : minterInterval ,
1411+ testFunc : testFinalizeWithTapscriptTree ,
1412+ },
12411413}
12421414
12431415// TestBatchedAssetIssuance runs a test of tests to ensure that the set of
0 commit comments