Skip to content

Commit

Permalink
tapgarden: test minting with batch tapscript tree
Browse files Browse the repository at this point in the history
  • Loading branch information
jharveyb committed Jan 30, 2024
1 parent 5194014 commit 2f06c7c
Showing 1 changed file with 150 additions and 12 deletions.
162 changes: 150 additions & 12 deletions tapgarden/planter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"database/sql"
"encoding/binary"
"encoding/hex"
"fmt"
"math/rand"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/davecgh/go-spew/spew"
tap "github.com/lightninglabs/taproot-assets"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
"github.com/lightninglabs/taproot-assets/proof"
Expand Down Expand Up @@ -242,15 +244,15 @@ type FinalizeBatchResp struct {
// finalizeBatch uses the public FinalizeBatch planter call to start a caretaker
// for an existing batch. The caller must wait for the planter call to complete.
func (t *mintingTestHarness) finalizeBatch(wg *sync.WaitGroup,
respChan chan *FinalizeBatchResp) {
respChan chan *FinalizeBatchResp, params *tapgarden.FinalizeParams) {

t.Helper()

wg.Add(1)
go func() {
defer wg.Done()

frozenBatch, finalizeErr := t.planter.FinalizeBatch(nil)
frozenBatch, finalizeErr := t.planter.FinalizeBatch(params)
resp := &FinalizeBatchResp{
Batch: frozenBatch,
Err: finalizeErr,
Expand All @@ -261,7 +263,8 @@ func (t *mintingTestHarness) finalizeBatch(wg *sync.WaitGroup,
}

func (t *mintingTestHarness) assertFinalizeBatch(wg *sync.WaitGroup,
respChan chan *FinalizeBatchResp, errString string) {
respChan chan *FinalizeBatchResp,
errString string) *tapgarden.MintingBatch {

t.Helper()

Expand All @@ -271,16 +274,18 @@ func (t *mintingTestHarness) assertFinalizeBatch(wg *sync.WaitGroup,
switch {
case errString == "":
require.NoError(t, finalizeResp.Err)
return finalizeResp.Batch

default:
require.ErrorContains(t, finalizeResp.Err, errString)
return nil
}
}

// progressCaretaker uses the mock interfaces to progress a caretaker from start
// to TX confirmation.
func (t *mintingTestHarness) progressCaretaker(
seedlings []*tapgarden.Seedling, batchSibling *chainhash.Hash) func() {
func (t *mintingTestHarness) progressCaretaker(seedlings []*tapgarden.Seedling,
batchSibling *commitment.TapscriptPreimage) func() {

// Assert that the caretaker has requested a genesis TX to be funded.
_ = t.assertGenesisTxFunded()
Expand Down Expand Up @@ -630,7 +635,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
// assertGenesisPsbtFinalized asserts that a request to finalize the genesis
// transaction has been requested by a caretaker.
func (t *mintingTestHarness) assertGenesisPsbtFinalized(
sibling *chainhash.Hash) {
sibling *commitment.TapscriptPreimage) {

t.Helper()

Expand Down Expand Up @@ -1113,7 +1118,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
)

// Finalize the pending batch to start a caretaker.
t.finalizeBatch(&wg, respChan)
t.finalizeBatch(&wg, respChan, nil)
batchCount++

_, err := fn.RecvOrTimeout(
Expand All @@ -1139,7 +1144,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
// caretaker to TX confirmation. The finalize call should report no
// error, but the caretaker should propagate the confirmation error to
// the shared error channel.
t.finalizeBatch(&wg, respChan)
t.finalizeBatch(&wg, respChan, nil)
batchCount++

_ = t.progressCaretaker(seedlings, nil)
Expand All @@ -1163,7 +1168,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
t.chain.EmptyConf(true)

// Start a new caretaker that should reach TX broadcast.
t.finalizeBatch(&wg, respChan)
t.finalizeBatch(&wg, respChan, nil)
batchCount++

sendConfNtfn := t.progressCaretaker(seedlings, nil)
Expand All @@ -1185,15 +1190,15 @@ func testFinalizeBatch(t *mintingTestHarness) {

// If we try to finalize without a pending batch, the finalize call
// should return an error.
t.finalizeBatch(&wg, respChan)
t.finalizeBatch(&wg, respChan, nil)
t.assertFinalizeBatch(&wg, respChan, "no pending batch")
t.assertNumCaretakersActive(caretakerCount)

// Queue another batch and drive the caretaker to a successful minting.
seedlings = t.queueInitialBatch(numSeedlings)
t.chain.EmptyConf(false)

t.finalizeBatch(&wg, respChan)
t.finalizeBatch(&wg, respChan, nil)
batchCount++

sendConfNtfn = t.progressCaretaker(seedlings, nil)
Expand All @@ -1204,8 +1209,136 @@ func testFinalizeBatch(t *mintingTestHarness) {
t.assertNoPendingBatch()
t.assertNumCaretakersActive(caretakerCount)
t.assertLastBatchState(batchCount, tapgarden.BatchStateFinalized)
}

func testFinalizeWithTapscriptTree(t *mintingTestHarness) {
// First, create a new chain planter instance using the supplied test
// harness.
t.refreshChainPlanter()

// Create an initial batch of 5 seedlings.
const numSeedlings = 5
seedlings := t.queueInitialBatch(numSeedlings)

// TODO(jhb): add finalize with tapscript root
var (
wg sync.WaitGroup
respChan = make(chan *FinalizeBatchResp, 1)
finalizeReq tapgarden.FinalizeParams
batchCount = 0
)

// Build a standalone tapscript tree object, that matches the tree
// created by other test helpers.
sigLockKey := test.RandPubKey(t)
hashLockWitness := []byte("foobar")
hashLockLeaf := test.ScriptHashLock(t.T, hashLockWitness)
sigLeaf := test.ScriptSchnorrSig(t.T, sigLockKey)
tapTreePreimage, err := asset.NewTreePreimageFromLeaves(
[]txscript.TapLeaf{hashLockLeaf, sigLeaf},
)
require.NoError(t, err)

finalizeReq = tapgarden.FinalizeParams{
TapTree: fn.Some(*tapTreePreimage),
}

// Force tapscript tree storage to fail, which should cause batch
// finalization to fail.
t.treeStore.FailStore = true
t.finalizeBatch(&wg, respChan, &finalizeReq)
finalizeErr := <-t.errChan
require.ErrorContains(t, finalizeErr, "unable to store tapscript tree")

// Allow tapscript tree storage to succeed, but force tapscript tree
// loading to fail.
t.treeStore.FailStore = false
t.treeStore.FailLoad = true
wg = sync.WaitGroup{}
respChan = make(chan *FinalizeBatchResp, 1)

// Receive all the signals needed to progress the caretaker through
// the batch sprouting, which is when the sibling tapscript tree is
// used.
progressCaretakerToTxSigning := func(
currentSeedlings []*tapgarden.Seedling) {

_ = t.assertGenesisTxFunded()

for i := 0; i < len(currentSeedlings); i++ {
t.assertKeyDerived()

if currentSeedlings[i].EnableEmission {
t.assertKeyDerived()
}
}
}

// Finalize the batch with a tapscript tree sibling.
t.finalizeBatch(&wg, respChan, &finalizeReq)
batchCount++

// The caretaker should fail when computing the Taproot output key.
progressCaretakerToTxSigning(seedlings)
t.assertFinalizeBatch(&wg, respChan, "failed to load tapscript tree")
t.assertLastBatchState(batchCount, tapgarden.BatchStateFrozen)
t.assertNoPendingBatch()

// Reset the tapscript tree store to not force load or store failures.
t.treeStore.FailStore = false
t.treeStore.FailLoad = false

// Construct a tapscript tree with a single leaf that has the structure
// of a TapLeaf computed from a TapCommitment. This should be rejected
// by the caretaker, as the genesis TX for the batch should only commit
// to one TapCommitment.
var dummyRootSum [8]byte
binary.BigEndian.PutUint64(dummyRootSum[:], test.RandInt[uint64]())
dummyRootHashParts := [][]byte{
{byte(asset.V0)}, commitment.TaprootAssetsMarker[:],
fn.ByteSlice(test.RandHash()), dummyRootSum[:],
}
dummyTapCommitmentRootHash := bytes.Join(dummyRootHashParts, nil)
dummyTapLeaf := txscript.NewBaseTapLeaf(dummyTapCommitmentRootHash)
dummyTapCommitmentPreimage, err := asset.NewTreePreimageFromLeaves(
[]txscript.TapLeaf{dummyTapLeaf},
)
require.NoError(t, err)

finalizeReq.TapTree = fn.Some(*dummyTapCommitmentPreimage)

// Queue another batch, and try to finalize with a sibling that is also
// a Taproot asset commitment.
seedlings = t.queueInitialBatch(numSeedlings)
t.finalizeBatch(&wg, respChan, &finalizeReq)
batchCount++

progressCaretakerToTxSigning(seedlings)
t.assertFinalizeBatch(
&wg, respChan, "preimage is a Taproot Asset commitment",
)
t.assertNoPendingBatch()

// Queue another batch, and provide a valid sibling tapscript tree.
seedlings = t.queueInitialBatch(numSeedlings)
finalizeReq.TapTree = fn.Some(*tapTreePreimage)
t.finalizeBatch(&wg, respChan, &finalizeReq)
batchCount++

// Verify that the final genesis TX uses the correct Taproot output key.
treeRootChildren := test.BuildTapscriptTreeNoReveal(t.T, sigLockKey)
sendConfNtfn := t.progressCaretaker(
seedlings, commitment.NewPreimageFromBranch(treeRootChildren),
)
sendConfNtfn()

// Once the TX is broadcast, the caretaker should run to completion,
// storing issuance proofs and updating the batch state to finalized.
batchWithSibling := t.assertFinalizeBatch(&wg, respChan, "")
require.NotNil(t, batchWithSibling)
t.assertNoError()
t.assertNoPendingBatch()
t.assertNumCaretakersActive(0)
t.assertLastBatchState(batchCount, tapgarden.BatchStateFinalized)
}

// mintingStoreTestCase is used to programmatically run a series of test cases
Expand Down Expand Up @@ -1238,6 +1371,11 @@ var testCases = []mintingStoreTestCase{
interval: minterInterval,
testFunc: testFinalizeBatch,
},
{
name: "finalize_with_tapscript_tree",
interval: minterInterval,
testFunc: testFinalizeWithTapscriptTree,
},
}

// TestBatchedAssetIssuance runs a test of tests to ensure that the set of
Expand Down

0 comments on commit 2f06c7c

Please sign in to comment.