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"
@@ -242,15 +244,15 @@ type FinalizeBatchResp struct {
242244// finalizeBatch uses the public FinalizeBatch planter call to start a caretaker
243245// for an existing batch. The caller must wait for the planter call to complete.
244246func (t * mintingTestHarness ) finalizeBatch (wg * sync.WaitGroup ,
245- respChan chan * FinalizeBatchResp ) {
247+ respChan chan * FinalizeBatchResp , params * tapgarden. FinalizeParams ) {
246248
247249 t .Helper ()
248250
249251 wg .Add (1 )
250252 go func () {
251253 defer wg .Done ()
252254
253- frozenBatch , finalizeErr := t .planter .FinalizeBatch (nil )
255+ frozenBatch , finalizeErr := t .planter .FinalizeBatch (params )
254256 resp := & FinalizeBatchResp {
255257 Batch : frozenBatch ,
256258 Err : finalizeErr ,
@@ -261,7 +263,8 @@ func (t *mintingTestHarness) finalizeBatch(wg *sync.WaitGroup,
261263}
262264
263265func (t * mintingTestHarness ) assertFinalizeBatch (wg * sync.WaitGroup ,
264- respChan chan * FinalizeBatchResp , errString string ) {
266+ respChan chan * FinalizeBatchResp ,
267+ errString string ) * tapgarden.MintingBatch {
265268
266269 t .Helper ()
267270
@@ -271,16 +274,18 @@ func (t *mintingTestHarness) assertFinalizeBatch(wg *sync.WaitGroup,
271274 switch {
272275 case errString == "" :
273276 require .NoError (t , finalizeResp .Err )
277+ return finalizeResp .Batch
274278
275279 default :
276280 require .ErrorContains (t , finalizeResp .Err , errString )
281+ return nil
277282 }
278283}
279284
280285// progressCaretaker uses the mock interfaces to progress a caretaker from start
281286// to TX confirmation.
282- func (t * mintingTestHarness ) progressCaretaker (
283- seedlings [] * tapgarden. Seedling , batchSibling * chainhash. Hash ) func () {
287+ func (t * mintingTestHarness ) progressCaretaker (seedlings [] * tapgarden. Seedling ,
288+ batchSibling * commitment. TapscriptPreimage ) func () {
284289
285290 // Assert that the caretaker has requested a genesis TX to be funded.
286291 _ = t .assertGenesisTxFunded ()
@@ -630,7 +635,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
630635// assertGenesisPsbtFinalized asserts that a request to finalize the genesis
631636// transaction has been requested by a caretaker.
632637func (t * mintingTestHarness ) assertGenesisPsbtFinalized (
633- sibling * chainhash. Hash ) {
638+ sibling * commitment. TapscriptPreimage ) {
634639
635640 t .Helper ()
636641
@@ -1113,7 +1118,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11131118 )
11141119
11151120 // Finalize the pending batch to start a caretaker.
1116- t .finalizeBatch (& wg , respChan )
1121+ t .finalizeBatch (& wg , respChan , nil )
11171122 batchCount ++
11181123
11191124 _ , err := fn .RecvOrTimeout (
@@ -1139,7 +1144,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11391144 // caretaker to TX confirmation. The finalize call should report no
11401145 // error, but the caretaker should propagate the confirmation error to
11411146 // the shared error channel.
1142- t .finalizeBatch (& wg , respChan )
1147+ t .finalizeBatch (& wg , respChan , nil )
11431148 batchCount ++
11441149
11451150 _ = t .progressCaretaker (seedlings , nil )
@@ -1163,7 +1168,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
11631168 t .chain .EmptyConf (true )
11641169
11651170 // Start a new caretaker that should reach TX broadcast.
1166- t .finalizeBatch (& wg , respChan )
1171+ t .finalizeBatch (& wg , respChan , nil )
11671172 batchCount ++
11681173
11691174 sendConfNtfn := t .progressCaretaker (seedlings , nil )
@@ -1185,15 +1190,15 @@ func testFinalizeBatch(t *mintingTestHarness) {
11851190
11861191 // If we try to finalize without a pending batch, the finalize call
11871192 // should return an error.
1188- t .finalizeBatch (& wg , respChan )
1193+ t .finalizeBatch (& wg , respChan , nil )
11891194 t .assertFinalizeBatch (& wg , respChan , "no pending batch" )
11901195 t .assertNumCaretakersActive (caretakerCount )
11911196
11921197 // Queue another batch and drive the caretaker to a successful minting.
11931198 seedlings = t .queueInitialBatch (numSeedlings )
11941199 t .chain .EmptyConf (false )
11951200
1196- t .finalizeBatch (& wg , respChan )
1201+ t .finalizeBatch (& wg , respChan , nil )
11971202 batchCount ++
11981203
11991204 sendConfNtfn = t .progressCaretaker (seedlings , nil )
@@ -1204,8 +1209,136 @@ func testFinalizeBatch(t *mintingTestHarness) {
12041209 t .assertNoPendingBatch ()
12051210 t .assertNumCaretakersActive (caretakerCount )
12061211 t .assertLastBatchState (batchCount , tapgarden .BatchStateFinalized )
1212+ }
1213+
1214+ func testFinalizeWithTapscriptTree (t * mintingTestHarness ) {
1215+ // First, create a new chain planter instance using the supplied test
1216+ // harness.
1217+ t .refreshChainPlanter ()
1218+
1219+ // Create an initial batch of 5 seedlings.
1220+ const numSeedlings = 5
1221+ seedlings := t .queueInitialBatch (numSeedlings )
12071222
1208- // TODO(jhb): add finalize with tapscript root
1223+ var (
1224+ wg sync.WaitGroup
1225+ respChan = make (chan * FinalizeBatchResp , 1 )
1226+ finalizeReq tapgarden.FinalizeParams
1227+ batchCount = 0
1228+ )
1229+
1230+ // Build a standalone tapscript tree object, that matches the tree
1231+ // created by other test helpers.
1232+ sigLockKey := test .RandPubKey (t )
1233+ hashLockWitness := []byte ("foobar" )
1234+ hashLockLeaf := test .ScriptHashLock (t .T , hashLockWitness )
1235+ sigLeaf := test .ScriptSchnorrSig (t .T , sigLockKey )
1236+ tapTreePreimage , err := asset .NewTreePreimageFromLeaves (
1237+ []txscript.TapLeaf {hashLockLeaf , sigLeaf },
1238+ )
1239+ require .NoError (t , err )
1240+
1241+ finalizeReq = tapgarden.FinalizeParams {
1242+ TapTree : fn .Some (* tapTreePreimage ),
1243+ }
1244+
1245+ // Force tapscript tree storage to fail, which should cause batch
1246+ // finalization to fail.
1247+ t .treeStore .FailStore = true
1248+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1249+ finalizeErr := <- t .errChan
1250+ require .ErrorContains (t , finalizeErr , "unable to store tapscript tree" )
1251+
1252+ // Allow tapscript tree storage to succeed, but force tapscript tree
1253+ // loading to fail.
1254+ t .treeStore .FailStore = false
1255+ t .treeStore .FailLoad = true
1256+ wg = sync.WaitGroup {}
1257+ respChan = make (chan * FinalizeBatchResp , 1 )
1258+
1259+ // Receive all the signals needed to progress the caretaker through
1260+ // the batch sprouting, which is when the sibling tapscript tree is
1261+ // used.
1262+ progressCaretakerToTxSigning := func (
1263+ currentSeedlings []* tapgarden.Seedling ) {
1264+
1265+ _ = t .assertGenesisTxFunded ()
1266+
1267+ for i := 0 ; i < len (currentSeedlings ); i ++ {
1268+ t .assertKeyDerived ()
1269+
1270+ if currentSeedlings [i ].EnableEmission {
1271+ t .assertKeyDerived ()
1272+ }
1273+ }
1274+ }
1275+
1276+ // Finalize the batch with a tapscript tree sibling.
1277+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1278+ batchCount ++
1279+
1280+ // The caretaker should fail when computing the Taproot output key.
1281+ progressCaretakerToTxSigning (seedlings )
1282+ t .assertFinalizeBatch (& wg , respChan , "failed to load tapscript tree" )
1283+ t .assertLastBatchState (batchCount , tapgarden .BatchStateFrozen )
1284+ t .assertNoPendingBatch ()
1285+
1286+ // Reset the tapscript tree store to not force load or store failures.
1287+ t .treeStore .FailStore = false
1288+ t .treeStore .FailLoad = false
1289+
1290+ // Construct a tapscript tree with a single leaf that has the structure
1291+ // of a TapLeaf computed from a TapCommitment. This should be rejected
1292+ // by the caretaker, as the genesis TX for the batch should only commit
1293+ // to one TapCommitment.
1294+ var dummyRootSum [8 ]byte
1295+ binary .BigEndian .PutUint64 (dummyRootSum [:], test .RandInt [uint64 ]())
1296+ dummyRootHashParts := [][]byte {
1297+ {byte (asset .V0 )}, commitment .TaprootAssetsMarker [:],
1298+ fn .ByteSlice (test .RandHash ()), dummyRootSum [:],
1299+ }
1300+ dummyTapCommitmentRootHash := bytes .Join (dummyRootHashParts , nil )
1301+ dummyTapLeaf := txscript .NewBaseTapLeaf (dummyTapCommitmentRootHash )
1302+ dummyTapCommitmentPreimage , err := asset .NewTreePreimageFromLeaves (
1303+ []txscript.TapLeaf {dummyTapLeaf },
1304+ )
1305+ require .NoError (t , err )
1306+
1307+ finalizeReq .TapTree = fn .Some (* dummyTapCommitmentPreimage )
1308+
1309+ // Queue another batch, and try to finalize with a sibling that is also
1310+ // a Taproot asset commitment.
1311+ seedlings = t .queueInitialBatch (numSeedlings )
1312+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1313+ batchCount ++
1314+
1315+ progressCaretakerToTxSigning (seedlings )
1316+ t .assertFinalizeBatch (
1317+ & wg , respChan , "preimage is a Taproot Asset commitment" ,
1318+ )
1319+ t .assertNoPendingBatch ()
1320+
1321+ // Queue another batch, and provide a valid sibling tapscript tree.
1322+ seedlings = t .queueInitialBatch (numSeedlings )
1323+ finalizeReq .TapTree = fn .Some (* tapTreePreimage )
1324+ t .finalizeBatch (& wg , respChan , & finalizeReq )
1325+ batchCount ++
1326+
1327+ // Verify that the final genesis TX uses the correct Taproot output key.
1328+ treeRootChildren := test .BuildTapscriptTreeNoReveal (t .T , sigLockKey )
1329+ sendConfNtfn := t .progressCaretaker (
1330+ seedlings , commitment .NewPreimageFromBranch (treeRootChildren ),
1331+ )
1332+ sendConfNtfn ()
1333+
1334+ // Once the TX is broadcast, the caretaker should run to completion,
1335+ // storing issuance proofs and updating the batch state to finalized.
1336+ batchWithSibling := t .assertFinalizeBatch (& wg , respChan , "" )
1337+ require .NotNil (t , batchWithSibling )
1338+ t .assertNoError ()
1339+ t .assertNoPendingBatch ()
1340+ t .assertNumCaretakersActive (0 )
1341+ t .assertLastBatchState (batchCount , tapgarden .BatchStateFinalized )
12091342}
12101343
12111344// mintingStoreTestCase is used to programmatically run a series of test cases
@@ -1238,6 +1371,11 @@ var testCases = []mintingStoreTestCase{
12381371 interval : minterInterval ,
12391372 testFunc : testFinalizeBatch ,
12401373 },
1374+ {
1375+ name : "finalize_with_tapscript_tree" ,
1376+ interval : minterInterval ,
1377+ testFunc : testFinalizeWithTapscriptTree ,
1378+ },
12411379}
12421380
12431381// TestBatchedAssetIssuance runs a test of tests to ensure that the set of
0 commit comments