Skip to content

Commit

Permalink
itest: reduce test harness overhead for minting
Browse files Browse the repository at this point in the history
In this commit, we update the logic used in all itests to mint assets
to reduce the number of RPC calls made and total time spent on asserts.
We also separate waiting for the planter to change state and checking
the daemon state.
  • Loading branch information
jharveyb committed Jun 12, 2023
1 parent cc90d7c commit b299962
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 70 deletions.
105 changes: 71 additions & 34 deletions itest/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"fmt"
"testing"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lntest/wait"
Expand Down Expand Up @@ -92,55 +94,90 @@ func assetScriptKeyIsLocalCheck(isLocal bool) assetCheck {
}
}

// sortAssetsByName converts an unordered list of assets to a map of lists of
// assets, where all assets in a list have the same name.
func sortAssetsByName(assets []*taprpc.Asset) map[string][]*taprpc.Asset {
assetLists := make(map[string][]*taprpc.Asset)
for _, a := range assets {
if _, ok := assetLists[a.AssetGenesis.Name]; !ok {
assetLists[a.AssetGenesis.Name] = []*taprpc.Asset{}
}

assetLists[a.AssetGenesis.Name] = append(
assetLists[a.AssetGenesis.Name], a,
)
}

return assetLists
}

// assertAssetState makes sure that an asset with the given (possibly
// non-unique!) name exists in the list of assets and then performs the given
// additional checks on that asset.
func assertAssetState(t *harnessTest, tapd *tapdHarness, name string,
metaHash []byte, assetChecks ...assetCheck) *taprpc.Asset {
func assertAssetState(t *harnessTest, assets map[string][]*taprpc.Asset,
name string, metaHash []byte, assetChecks ...assetCheck) *taprpc.Asset {

t.t.Helper()
var a *taprpc.Asset
matchingAssets, ok := assets[name]
require.True(
t.t, ok, fmt.Errorf("no assets named %s in asset list", name),
)

ctxb := context.Background()
for _, rpcAsset := range matchingAssets {
rpcGen := rpcAsset.AssetGenesis
if bytes.Equal(rpcGen.MetaHash, metaHash[:]) {
a = rpcAsset

var a *taprpc.Asset
err := wait.NoError(func() error {
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
defer cancel()
for _, check := range assetChecks {
err := check(rpcAsset)
require.NoError(t.t, err)
}

listResp, err := tapd.ListAssets(
ctxt, &taprpc.ListAssetRequest{},
)
if err != nil {
return err
break
}
}

for _, rpcAsset := range listResp.Assets {
rpcGen := rpcAsset.AssetGenesis
if rpcGen.Name == name &&
bytes.Equal(rpcGen.MetaHash, metaHash[:]) {
require.NotNil(t.t, a, fmt.Errorf("asset with matching metadata not"+
"found in asset list"))

a = rpcAsset
return a
}

for _, check := range assetChecks {
if err := check(rpcAsset); err != nil {
return err
}
}
// waitForBatchState polls until the planter has reached the desired state with
// the current batch.
func waitForBatchState(t *harnessTest, ctx context.Context, tapd *tapdHarness,
timeout time.Duration, targetState mintrpc.BatchState) bool {

break
}
}
breakTimeout := time.After(timeout)
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()

if a == nil {
return fmt.Errorf("asset with name %s not found in "+
"asset list", name)
}
isTargetState := func(b *mintrpc.MintingBatch) bool {
return b.State == targetState
}

return nil
}, defaultWaitTimeout)
require.NoError(t.t, err)
batchCount := func() int {
batchResp, err := tapd.ListBatches(
ctx, &mintrpc.ListBatchRequest{},
)
require.NoError(t.t, err)

return a
return fn.Count(batchResp.Batches, isTargetState)
}

initialBatchCount := batchCount()

for {
select {
case <-breakTimeout:
return false
case <-ticker.C:
currentBatchCount := batchCount()
if currentBatchCount-initialBatchCount == 1 {
return true
}
}
}
}

// commitmentKey returns the asset's commitment key given an RPC asset
Expand Down
39 changes: 35 additions & 4 deletions itest/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,29 @@ func mintAssetsConfirmBatch(t *harnessTest, tapd *tapdHarness,
require.NoError(t.t, err)
require.NotEmpty(t.t, batchResp.BatchKey)

waitForBatchState(
t, ctxt, tapd, defaultWaitTimeout,
mintrpc.BatchState_BATCH_STATE_BROADCAST,
)
hashes, err := waitForNTxsInMempool(
t.lndHarness.Miner.Client, 1, defaultWaitTimeout,
)
require.NoError(t.t, err)

// Make sure the assets were all minted within the same anchor but don't
// yet have a block hash associated with them.
listRespUnconfirmed, err := tapd.ListAssets(
ctxt, &taprpc.ListAssetRequest{},
)
require.NoError(t.t, err)

unconfirmedAssets := sortAssetsByName(listRespUnconfirmed.Assets)
for _, assetRequest := range assetRequests {
metaHash := (&proof.MetaReveal{
Data: assetRequest.Asset.AssetMeta.Data,
}).MetaHash()
assertAssetState(
t, tapd, assetRequest.Asset.Name,
t, unconfirmedAssets, assetRequest.Asset.Name,
metaHash[:],
assetAmountCheck(assetRequest.Asset.Amount),
assetTypeCheck(assetRequest.Asset.AssetType),
Expand All @@ -178,6 +188,10 @@ func mintAssetsConfirmBatch(t *harnessTest, tapd *tapdHarness,
// Mine a block to confirm the assets.
block := mineBlocks(t, t.lndHarness, 1, 1)[0]
blockHash := block.BlockHash()
waitForBatchState(
t, ctxt, tapd, defaultWaitTimeout,
mintrpc.BatchState_BATCH_STATE_FINALIZED,
)

// The rest of the anchor information should now be populated as well.
// We also check that the anchor outpoint of all assets is the same,
Expand All @@ -186,13 +200,20 @@ func mintAssetsConfirmBatch(t *harnessTest, tapd *tapdHarness,
firstOutpoint string
assetList []*taprpc.Asset
)

listRespConfirmed, err := tapd.ListAssets(
ctxt, &taprpc.ListAssetRequest{},
)
require.NoError(t.t, err)
confirmedAssets := sortAssetsByName(listRespConfirmed.Assets)

for _, assetRequest := range assetRequests {
metaHash := (&proof.MetaReveal{
Data: assetRequest.Asset.AssetMeta.Data,
}).MetaHash()
mintedAsset := assertAssetState(
t, tapd, assetRequest.Asset.Name, metaHash[:],
assetAnchorCheck(*hashes[0], blockHash),
t, confirmedAssets, assetRequest.Asset.Name,
metaHash[:], assetAnchorCheck(*hashes[0], blockHash),
assetScriptKeyIsLocalCheck(true),
func(a *taprpc.Asset) error {
anchor := a.ChainAnchor
Expand Down Expand Up @@ -248,18 +269,28 @@ func transferAssetProofs(t *harnessTest, src, dst *tapdHarness,
GenesisPoint: gen.GenesisPoint,
})
require.NoError(t.t, err)
}

listResp, err := dst.ListAssets(
ctxt, &taprpc.ListAssetRequest{},
)
require.NoError(t.t, err)

importedAssets := sortAssetsByName(listResp.Assets)
for _, existingAsset := range assets {
gen := existingAsset.AssetGenesis
anchorTxHash, err := chainhash.NewHashFromStr(
existingAsset.ChainAnchor.AnchorTxid,
)
require.NoError(t.t, err)

anchorBlockHash, err := chainhash.NewHash(
existingAsset.ChainAnchor.AnchorBlockHash,
)
require.NoError(t.t, err)

assertAssetState(
t, dst, gen.Name, gen.MetaHash,
t, importedAssets, gen.Name, gen.MetaHash,
assetAmountCheck(existingAsset.Amount),
assetTypeCheck(existingAsset.AssetType),
assetAnchorCheck(*anchorTxHash, *anchorBlockHash),
Expand Down
60 changes: 29 additions & 31 deletions itest/multi_asset_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,37 +85,11 @@ func testMintMultiAssetGroups(t *harnessTest) {
// Now that we've verified the group count, size, and balance, we also
// need to check that the intended asset was used as the group anchor.
// We can do this by re-deriving the tweaked group key.
matchingName := func(asset *taprpc.Asset, name string) bool {
return asset.AssetGenesis.Name == name
}
normalAnchorName := issuableAssets[0].Asset.Name
normalAnchor := verifyGroupAnchor(t.t, mintedBatch, normalAnchorName)

// We need to fetch the minted group anchor asset, which includes the
// genesis information used to compute the tweak for the group key.
normalAnchor, err := fn.First(
mintedBatch, func(asset *taprpc.Asset) bool {
return matchingName(asset, issuableAssets[0].Asset.Name)
},
)
require.NoError(t.t, err)
normalAnchorGen := parseGenInfo(t.t, normalAnchor.AssetGenesis)
normalAnchorGen.Type = asset.Type(normalAnchor.AssetType)
assertGroupAnchor(
t.t, normalAnchorGen, normalAnchor.AssetGroup.RawGroupKey,
normalAnchor.AssetGroup.TweakedGroupKey,
)

collectAnchor, err := fn.First(
mintedBatch, func(asset *taprpc.Asset) bool {
return matchingName(asset, issuableAssets[1].Asset.Name)
},
)
require.NoError(t.t, err)
collectAnchorGen := parseGenInfo(t.t, collectAnchor.AssetGenesis)
collectAnchorGen.Type = asset.Type(collectAnchor.AssetType)
assertGroupAnchor(
t.t, collectAnchorGen, collectAnchor.AssetGroup.RawGroupKey,
collectAnchor.AssetGroup.TweakedGroupKey,
)
collectAnchorName := issuableAssets[1].Asset.Name
collectAnchor := verifyGroupAnchor(t.t, mintedBatch, collectAnchorName)

// Finally, we send some assets from the multi-asset group to Bob to
// ensure that they can be sent and received correctly.
Expand Down Expand Up @@ -165,7 +139,9 @@ func testMintMultiAssetGroups(t *harnessTest) {
// We want to select the one collectible that is in the same group as
// the collectible group anchor, and is not the anchor itself.
isCollectGroupMember := func(asset *taprpc.Asset) bool {
isNotAnchor := asset.AssetGenesis.Name != collectAnchorGen.Tag
isNotAnchor := asset.AssetGenesis.Name !=
collectAnchor.AssetGenesis.Name

if asset.AssetGroup == nil {
return false
}
Expand Down Expand Up @@ -201,6 +177,28 @@ func testMintMultiAssetGroups(t *harnessTest) {
)
}

// Verify that the correct asset was used as the group anchor by re-deriving the
// group key.
func verifyGroupAnchor(t *testing.T, assets []*taprpc.Asset,
anchorName string) *taprpc.Asset {

anchor, err := fn.First(
assets, func(asset *taprpc.Asset) bool {
return asset.AssetGenesis.Name == anchorName
},
)
require.NoError(t, err)

anchorGen := parseGenInfo(t, anchor.AssetGenesis)
anchorGen.Type = asset.Type(anchor.AssetType)
assertGroupAnchor(
t, anchorGen, anchor.AssetGroup.RawGroupKey,
anchor.AssetGroup.TweakedGroupKey,
)

return anchor
}

// createMultiAssetGroup creates a list of minting requests that represent a
// multi-asset group, using the anchor asset to generate parameters for the
// other assets in the group.
Expand Down
3 changes: 2 additions & 1 deletion itest/psbt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,9 @@ func runPsbtInteractiveFullValueSendTest(ctxt context.Context, t *harnessTest,
)
require.NoError(t.t, err)
require.Len(t.t, receiverAssets.Assets, numReceiverAssets)
receivedAssets := sortAssetsByName(receiverAssets.Assets)
assertAssetState(
t, receiver, genInfo.Name, genInfo.MetaHash,
t, receivedAssets, genInfo.Name, genInfo.MetaHash,
assetAmountCheck(fullAmt),
)
}
Expand Down

0 comments on commit b299962

Please sign in to comment.