Skip to content

Commit 2f06c7c

Browse files
committed
tapgarden: test minting with batch tapscript tree
1 parent 5194014 commit 2f06c7c

File tree

1 file changed

+150
-12
lines changed

1 file changed

+150
-12
lines changed

tapgarden/planter_test.go

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
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.
244246
func (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

263265
func (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.
632637
func (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

Comments
 (0)