diff --git a/Dockerfile b/Dockerfile index 6d9a750f4..4b8fb1d99 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ # /make/builder.Dockerfile # /taprpc/Dockerfile # /tools/Dockerfile +# /itest/loadtest/Dockerfile FROM golang:1.21.0-alpine as builder # Force Go to use the cgo based DNS resolver. This is required to ensure DNS diff --git a/Makefile b/Makefile index ed3bc5540..5e2596a1f 100644 --- a/Makefile +++ b/Makefile @@ -95,6 +95,9 @@ build-itest: @$(call print, "Building itest lnd.") CGO_ENABLED=0 $(GOBUILD) -tags="$(ITEST_TAGS)" -o itest/lnd-itest $(DEV_LDFLAGS) $(LND_PKG)/cmd/lnd +build-loadtest: + CGO_ENABLED=0 $(GOTEST) -c -tags="$(LOADTEST_TAGS)" -o loadtest $(PKG)/itest/loadtest + install: @$(call print, "Installing tapd and tapcli.") $(GOINSTALL) -tags="${tags}" -ldflags="$(RELEASE_LDFLAGS)" $(PKG)/cmd/tapd diff --git a/itest/addrs_test.go b/itest/addrs_test.go index c10f3b655..6f5651cfd 100644 --- a/itest/addrs_test.go +++ b/itest/addrs_test.go @@ -19,8 +19,9 @@ func testAddresses(t *harnessTest) { // We mint all of them in individual batches to avoid needing to sign // for multiple internal asset transfers when only sending one of them // to an external address. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], issuableAssets[0], }, ) @@ -53,7 +54,7 @@ func testAddresses(t *harnessTest) { require.NoError(t.t, err) addresses = append(addresses, addr) - assertAddrCreated(t.t, secondTapd, a, addr) + AssertAddrCreated(t.t, secondTapd, a, addr) sendResp := sendAssetsToAddr(t, t.tapd, addr) sendRespJSON, err := formatProtoJSON(sendResp) @@ -66,7 +67,7 @@ func testAddresses(t *harnessTest) { AssertAddrEvent(t.t, secondTapd, addr, 1, statusDetected) // Mine a block to make sure the events are marked as confirmed. - mineBlocks(t, t.lndHarness, 1, 1) + MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 1) // Eventually the event should be marked as confirmed. AssertAddrEvent(t.t, secondTapd, addr, 1, statusConfirmed) @@ -141,8 +142,9 @@ func testAddresses(t *harnessTest) { // same time. func testMultiAddress(t *harnessTest) { // First, mint an asset, so we have one to create addresses for. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], issuableAssets[0], }, ) @@ -190,7 +192,7 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice, }) require.NoError(t.t, err) bobAddresses = append(bobAddresses, bobAddr1) - assertAddrCreated(t.t, bob, mintedAsset, bobAddr1) + AssertAddrCreated(t.t, bob, mintedAsset, bobAddr1) bobAddr2, err := bob.NewAddr(ctxt, &taprpc.NewAddrRequest{ AssetId: genInfo.AssetId, @@ -198,7 +200,7 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice, }) require.NoError(t.t, err) bobAddresses = append(bobAddresses, bobAddr2) - assertAddrCreated(t.t, bob, mintedAsset, bobAddr2) + AssertAddrCreated(t.t, bob, mintedAsset, bobAddr2) // To test that Alice can also receive to multiple addresses in a single // transaction as well, we also add two addresses for her. @@ -207,14 +209,14 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice, Amt: sendAmt, }) require.NoError(t.t, err) - assertAddrCreated(t.t, alice, mintedAsset, aliceAddr1) + AssertAddrCreated(t.t, alice, mintedAsset, aliceAddr1) aliceAddr2, err := alice.NewAddr(ctxt, &taprpc.NewAddrRequest{ AssetId: genInfo.AssetId, Amt: sendAmt, }) require.NoError(t.t, err) - assertAddrCreated(t.t, alice, mintedAsset, aliceAddr2) + AssertAddrCreated(t.t, alice, mintedAsset, aliceAddr2) sendResp := sendAssetsToAddr( t, alice, bobAddr1, bobAddr2, aliceAddr1, aliceAddr2, @@ -230,14 +232,14 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice, AssertAddrEvent(t.t, alice, aliceAddr2, 1, statusDetected) // Mine a block to make sure the events are marked as confirmed. - _ = mineBlocks(t, t.lndHarness, 1, 1)[0] + _ = MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 1)[0] // Eventually the events should be marked as confirmed. - assertAddrEventByStatus(t.t, bob, statusConfirmed, 2) + AssertAddrEventByStatus(t.t, bob, statusConfirmed, 2) // For local addresses, we should already have the proof in the DB at // this point, so the status should go to completed directly. - assertAddrEventByStatus(t.t, alice, statusCompleted, numRuns*2) + AssertAddrEventByStatus(t.t, alice, statusCompleted, numRuns*2) // To complete the transfer, we'll export the proof from the sender and // import it into the receiver for each asset set. This should not be diff --git a/itest/assertions.go b/itest/assertions.go index 6a21d0b7b..6aa197620 100644 --- a/itest/assertions.go +++ b/itest/assertions.go @@ -6,12 +6,14 @@ import ( "encoding/hex" "fmt" "reflect" + "sort" "testing" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/asset" @@ -32,11 +34,11 @@ var ( statusCompleted = taprpc.AddrEventStatus_ADDR_EVENT_STATUS_COMPLETED ) -// assetCheck is a function type that checks an RPC asset's property. -type assetCheck func(a *taprpc.Asset) error +// AssetCheck is a function type that checks an RPC asset's property. +type AssetCheck func(a *taprpc.Asset) error -// assetAmountCheck returns a check function that tests an asset's amount. -func assetAmountCheck(amt uint64) assetCheck { +// AssetAmountCheck returns a check function that tests an asset's amount. +func AssetAmountCheck(amt uint64) AssetCheck { return func(a *taprpc.Asset) error { if a.Amount != amt { return fmt.Errorf("unexpected asset amount, got %d "+ @@ -47,8 +49,8 @@ func assetAmountCheck(amt uint64) assetCheck { } } -// assetTypeCheck returns a check function that tests an asset's type. -func assetTypeCheck(assetType taprpc.AssetType) assetCheck { +// AssetTypeCheck returns a check function that tests an asset's type. +func AssetTypeCheck(assetType taprpc.AssetType) AssetCheck { return func(a *taprpc.Asset) error { if a.AssetType != assetType { return fmt.Errorf("unexpected asset type, got %v "+ @@ -59,8 +61,8 @@ func assetTypeCheck(assetType taprpc.AssetType) assetCheck { } } -// assetAnchorCheck returns a check function that tests an asset's anchor. -func assetAnchorCheck(txid, blockHash chainhash.Hash) assetCheck { +// AssetAnchorCheck returns a check function that tests an asset's anchor. +func AssetAnchorCheck(txid, blockHash chainhash.Hash) AssetCheck { return func(a *taprpc.Asset) error { if a.ChainAnchor == nil { return fmt.Errorf("asset is missing chain anchor field") @@ -82,9 +84,9 @@ func assetAnchorCheck(txid, blockHash chainhash.Hash) assetCheck { } } -// assetScriptKeyIsLocalCheck returns a check function that tests an asset's +// AssetScriptKeyIsLocalCheck returns a check function that tests an asset's // script key for being a local key. -func assetScriptKeyIsLocalCheck(isLocal bool) assetCheck { +func AssetScriptKeyIsLocalCheck(isLocal bool) AssetCheck { return func(a *taprpc.Asset) error { if a.ScriptKeyIsLocal != isLocal { return fmt.Errorf("unexpected script key, wanted "+ @@ -96,9 +98,9 @@ func assetScriptKeyIsLocalCheck(isLocal bool) assetCheck { } } -// assetScriptKeyIsBurnCheck returns a check function that tests an asset's +// AssetScriptKeyIsBurnCheck returns a check function that tests an asset's // script key for being a burn key. -func assetScriptKeyIsBurnCheck(isBurn bool) assetCheck { +func AssetScriptKeyIsBurnCheck(isBurn bool) AssetCheck { return func(a *taprpc.Asset) error { if a.IsBurn != isBurn { return fmt.Errorf("unexpected script key, wanted "+ @@ -110,9 +112,9 @@ func assetScriptKeyIsBurnCheck(isBurn bool) assetCheck { } } -// groupAssetsByName converts an unordered list of assets to a map of lists of +// GroupAssetsByName converts an unordered list of assets to a map of lists of // assets, where all assets in a list have the same name. -func groupAssetsByName(assets []*taprpc.Asset) map[string][]*taprpc.Asset { +func GroupAssetsByName(assets []*taprpc.Asset) map[string][]*taprpc.Asset { assetLists := make(map[string][]*taprpc.Asset) for idx := range assets { a := assets[idx] @@ -128,7 +130,7 @@ func groupAssetsByName(assets []*taprpc.Asset) map[string][]*taprpc.Asset { // non-unique!) name exists in the list of assets and then performs the given // additional checks on that asset. func AssertAssetState(t *testing.T, assets map[string][]*taprpc.Asset, - name string, metaHash []byte, assetChecks ...assetCheck) *taprpc.Asset { + name string, metaHash []byte, assetChecks ...AssetCheck) *taprpc.Asset { var a *taprpc.Asset @@ -158,7 +160,7 @@ func AssertAssetState(t *testing.T, assets map[string][]*taprpc.Asset, // non-unique!) name exists in the list of assets and then performs the given // additional checks on that asset. func AssertAssetStateByScriptKey(t *testing.T, assets []*taprpc.Asset, - scriptKey []byte, assetChecks ...assetCheck) *taprpc.Asset { + scriptKey []byte, assetChecks ...AssetCheck) *taprpc.Asset { var a *taprpc.Asset for _, rpcAsset := range assets { @@ -180,6 +182,23 @@ func AssertAssetStateByScriptKey(t *testing.T, assets []*taprpc.Asset, return a } +// AssertTxInBlock checks that a given transaction can be found in the block's +// transaction list. +func AssertTxInBlock(t *testing.T, block *wire.MsgBlock, + txid *chainhash.Hash) *wire.MsgTx { + + for _, tx := range block.Transactions { + sha := tx.TxHash() + if bytes.Equal(txid[:], sha[:]) { + return tx + } + } + + require.Fail(t, "tx was not included in block") + + return nil +} + // WaitForBatchState polls until the planter has reached the desired state with // the given batch. func WaitForBatchState(t *testing.T, ctx context.Context, @@ -287,7 +306,7 @@ func AssertAssetProofs(t *testing.T, tapClient taprpc.TaprootAssetsClient, ) require.NoError(t, err) - file, snapshot := verifyProofBlob( + file, snapshot := VerifyProofBlob( t, tapClient, chainClient, a, exportResp.RawProofFile, ) @@ -304,9 +323,9 @@ func AssertAssetProofs(t *testing.T, tapClient taprpc.TaprootAssetsClient, return exportResp.RawProofFile } -// assertAssetProofsInvalid makes sure the proofs for the given asset can be +// AssertAssetProofsInvalid makes sure the proofs for the given asset can be // retrieved from the given daemon but fail to validate. -func assertAssetProofsInvalid(t *testing.T, tapd *tapdHarness, +func AssertAssetProofsInvalid(t *testing.T, tapd *tapdHarness, a *taprpc.Asset) { t.Helper() @@ -332,9 +351,9 @@ func assertAssetProofsInvalid(t *testing.T, tapd *tapdHarness, require.False(t, verifyResp.Valid) } -// verifyProofBlob parses the given proof blob into a file, verifies it and +// VerifyProofBlob parses the given proof blob into a file, verifies it and // returns the resulting last asset snapshot together with the parsed file. -func verifyProofBlob(t *testing.T, tapClient taprpc.TaprootAssetsClient, +func VerifyProofBlob(t *testing.T, tapClient taprpc.TaprootAssetsClient, chainClient chainrpc.ChainKitClient, a *taprpc.Asset, blob proof.Blob) (*proof.File, *proof.AssetSnapshot) { @@ -408,9 +427,9 @@ func verifyProofBlob(t *testing.T, tapClient taprpc.TaprootAssetsClient, return f, snapshot } -// assertAddrCreated makes sure an address was created correctly for the given +// AssertAddrCreated makes sure an address was created correctly for the given // asset. -func assertAddrCreated(t *testing.T, client taprpc.TaprootAssetsClient, +func AssertAddrCreated(t *testing.T, client taprpc.TaprootAssetsClient, expected *taprpc.Asset, actual *taprpc.Addr) { // Was the address created correctly? @@ -489,9 +508,9 @@ func AssertAddrEvent(t *testing.T, client taprpc.TaprootAssetsClient, require.NoError(t, err) } -// assertAddrEventByStatus makes sure the given number of events exist with the +// AssertAddrEventByStatus makes sure the given number of events exist with the // given status. -func assertAddrEventByStatus(t *testing.T, client taprpc.TaprootAssetsClient, +func AssertAddrEventByStatus(t *testing.T, client taprpc.TaprootAssetsClient, filterStatus taprpc.AddrEventStatus, numEvents int) { ctxb := context.Background() @@ -519,39 +538,43 @@ func assertAddrEventByStatus(t *testing.T, client taprpc.TaprootAssetsClient, require.NoError(t, err) } -// confirmAndAssertOutboundTransfer makes sure the given outbound transfer has +// ConfirmAndAssertOutboundTransfer makes sure the given outbound transfer has // the correct state before confirming it and then asserting the confirmed state // with the node. -func confirmAndAssertOutboundTransfer(t *harnessTest, sender *tapdHarness, +func ConfirmAndAssertOutboundTransfer(t *testing.T, + minerClient *rpcclient.Client, sender TapdClient, sendResp *taprpc.SendAssetResponse, assetID []byte, expectedAmounts []uint64, currentTransferIdx, numTransfers int) *wire.MsgBlock { - return confirmAndAssetOutboundTransferWithOutputs( - t, sender, sendResp, assetID, expectedAmounts, + return ConfirmAndAssetOutboundTransferWithOutputs( + t, minerClient, sender, sendResp, assetID, expectedAmounts, currentTransferIdx, numTransfers, 2, ) } -// confirmAndAssetOutboundTransferWithOutputs makes sure the given outbound +// ConfirmAndAssetOutboundTransferWithOutputs makes sure the given outbound // transfer has the correct state and number of outputs before confirming it and // then asserting the confirmed state with the node. -func confirmAndAssetOutboundTransferWithOutputs(t *harnessTest, - sender *tapdHarness, sendResp *taprpc.SendAssetResponse, - assetID []byte, expectedAmounts []uint64, currentTransferIdx, +func ConfirmAndAssetOutboundTransferWithOutputs(t *testing.T, + minerClient *rpcclient.Client, sender TapdClient, + sendResp *taprpc.SendAssetResponse, assetID []byte, + expectedAmounts []uint64, currentTransferIdx, numTransfers, numOutputs int) *wire.MsgBlock { - return assertAssetOutboundTransferWithOutputs( - t, sender, sendResp.Transfer, assetID, expectedAmounts, - currentTransferIdx, numTransfers, numOutputs, true, + return AssertAssetOutboundTransferWithOutputs( + t, minerClient, sender, sendResp.Transfer, assetID, + expectedAmounts, currentTransferIdx, numTransfers, numOutputs, + true, ) } -// assertAssetOutboundTransferWithOutputs makes sure the given outbound transfer +// AssertAssetOutboundTransferWithOutputs makes sure the given outbound transfer // has the correct state and number of outputs. -func assertAssetOutboundTransferWithOutputs(t *harnessTest, - sender *tapdHarness, transfer *taprpc.AssetTransfer, - assetID []byte, expectedAmounts []uint64, currentTransferIdx, +func AssertAssetOutboundTransferWithOutputs(t *testing.T, + minerClient *rpcclient.Client, sender TapdClient, + transfer *taprpc.AssetTransfer, assetID []byte, + expectedAmounts []uint64, currentTransferIdx, numTransfers, numOutputs int, confirm bool) *wire.MsgBlock { ctxb := context.Background() @@ -559,60 +582,60 @@ func assertAssetOutboundTransferWithOutputs(t *harnessTest, // Check that we now have two new outputs, and that they differ // in outpoints and scripts. outputs := transfer.Outputs - require.Len(t.t, outputs, numOutputs) + require.Len(t, outputs, numOutputs) outpoints := make(map[string]struct{}) scripts := make(map[string]struct{}) for _, o := range outputs { _, ok := scripts[string(o.ScriptKey)] - require.False(t.t, ok) + require.False(t, ok) outpoints[o.Anchor.Outpoint] = struct{}{} scripts[string(o.ScriptKey)] = struct{}{} } sendRespJSON, err := formatProtoJSON(transfer) - require.NoError(t.t, err) + require.NoError(t, err) t.Logf("Got response from sending assets: %v", sendRespJSON) // Mine a block to force the send event to complete (confirm on-chain). var newBlock *wire.MsgBlock if confirm { - newBlock = mineBlocks(t, t.lndHarness, 1, 1)[0] + newBlock = MineBlocks(t, minerClient, 1, 1)[0] } // Confirm that we can externally view the transfer. - require.Eventually(t.t, func() bool { + require.Eventually(t, func() bool { resp, err := sender.ListTransfers( ctxb, &taprpc.ListTransfersRequest{}, ) - require.NoError(t.t, err) - require.Len(t.t, resp.Transfers, numTransfers) + require.NoError(t, err) + require.Len(t, resp.Transfers, numTransfers) // Assert the new outpoint, script and amount is in the // list. transfer := resp.Transfers[currentTransferIdx] - require.Len(t.t, transfer.Outputs, numOutputs) - require.Len(t.t, expectedAmounts, numOutputs) + require.Len(t, transfer.Outputs, numOutputs) + require.Len(t, expectedAmounts, numOutputs) for idx := range transfer.Outputs { out := transfer.Outputs[idx] - require.Contains(t.t, outpoints, out.Anchor.Outpoint) - require.Contains(t.t, scripts, string(out.ScriptKey)) - require.Equal(t.t, expectedAmounts[idx], out.Amount) + require.Contains(t, outpoints, out.Anchor.Outpoint) + require.Contains(t, scripts, string(out.ScriptKey)) + require.Equal(t, expectedAmounts[idx], out.Amount) } firstIn := transfer.Inputs[0] return bytes.Equal(firstIn.AssetId, assetID) }, defaultTimeout, wait.PollInterval) - require.NoError(t.t, err) + require.NoError(t, err) transferResp, err := sender.ListTransfers( ctxb, &taprpc.ListTransfersRequest{}, ) - require.NoError(t.t, err) + require.NoError(t, err) transferRespJSON, err := formatProtoJSON(transferResp) - require.NoError(t.t, err) + require.NoError(t, err) t.Logf("Got response from list transfers: %v", transferRespJSON) return newBlock @@ -880,9 +903,9 @@ func AssertGroupAnchor(t *testing.T, anchorGen *asset.Genesis, // match. type MatchRpcAsset func(asset *taprpc.Asset) bool -// assertListAssets checks that the assets returned by ListAssets match the +// AssertListAssets checks that the assets returned by ListAssets match the // expected assets. -func assertListAssets(t *testing.T, ctx context.Context, +func AssertListAssets(t *testing.T, ctx context.Context, client taprpc.TaprootAssetsClient, matchAssets []MatchRpcAsset) { resp, err := client.ListAssets(ctx, &taprpc.ListAssetRequest{}) @@ -1152,3 +1175,213 @@ func AssertUniverseAssetStats(t *testing.T, node *tapdHarness, require.Equal(t, todayStr, s.Date) require.EqualValues(t, len(assets), s.NewProofEvents) } + +// VerifyGroupAnchor verifies 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 +} + +// AssertAssetsMinted makes sure all assets in the minting request were in fact +// minted in the given anchor TX and block. The function returns the list of +// minted assets. +func AssertAssetsMinted(t *testing.T, + tapClient TapdClient, + assetRequests []*mintrpc.MintAssetRequest, mintTXID, + blockHash chainhash.Hash) []*taprpc.Asset { + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) + defer cancel() + + // 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, + // since they were all minted in the same batch. + var ( + firstOutpoint string + assetList []*taprpc.Asset + ) + + listRespConfirmed, err := tapClient.ListAssets( + ctxt, &taprpc.ListAssetRequest{}, + ) + require.NoError(t, err) + confirmedAssets := GroupAssetsByName(listRespConfirmed.Assets) + + for _, assetRequest := range assetRequests { + metaHash := (&proof.MetaReveal{ + Data: assetRequest.Asset.AssetMeta.Data, + }).MetaHash() + mintedAsset := AssertAssetState( + t, confirmedAssets, assetRequest.Asset.Name, + metaHash[:], AssetAnchorCheck(mintTXID, blockHash), + AssetScriptKeyIsLocalCheck(true), + func(a *taprpc.Asset) error { + anchor := a.ChainAnchor + + if anchor.AnchorOutpoint == "" { + return fmt.Errorf("missing anchor " + + "outpoint") + } + + if firstOutpoint == "" { + firstOutpoint = anchor.AnchorOutpoint + + return nil + } + + if anchor.AnchorOutpoint != firstOutpoint { + return fmt.Errorf("unexpected anchor "+ + "outpoint, got %v wanted %v", + anchor.AnchorOutpoint, + firstOutpoint) + } + + return nil + }, + ) + + assetList = append(assetList, mintedAsset) + } + + return assetList +} + +func AssertAssetBalances(t *testing.T, client taprpc.TaprootAssetsClient, + simpleAssets, issuableAssets []*taprpc.Asset) { + + t.Helper() + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) + defer cancel() + + // First, we'll ensure that we're able to get the balances of all the + // assets grouped by their asset IDs. + balanceReq := &taprpc.ListBalancesRequest_AssetId{ + AssetId: true, + } + assetIDBalances, err := client.ListBalances( + ctxt, &taprpc.ListBalancesRequest{ + GroupBy: balanceReq, + }, + ) + require.NoError(t, err) + + var allAssets []*taprpc.Asset + allAssets = append(allAssets, simpleAssets...) + allAssets = append(allAssets, issuableAssets...) + + require.Equal(t, len(allAssets), len(assetIDBalances.AssetBalances)) + + for _, balance := range assetIDBalances.AssetBalances { + for _, rpcAsset := range allAssets { + if balance.AssetGenesis.Name == rpcAsset.AssetGenesis.Name { + require.Equal( + t, balance.Balance, rpcAsset.Amount, + ) + require.Equal( + t, + balance.AssetGenesis, + rpcAsset.AssetGenesis, + ) + } + } + } + + // We'll also ensure that we're able to get the balance by key group + // for all the assets that have one specified. + groupBalanceReq := &taprpc.ListBalancesRequest_GroupKey{ + GroupKey: true, + } + assetGroupBalances, err := client.ListBalances( + ctxt, &taprpc.ListBalancesRequest{ + GroupBy: groupBalanceReq, + }, + ) + require.NoError(t, err) + + require.Equal( + t, len(issuableAssets), + len(assetGroupBalances.AssetGroupBalances), + ) + + for _, balance := range assetGroupBalances.AssetBalances { + for _, rpcAsset := range issuableAssets { + if balance.AssetGenesis.Name == rpcAsset.AssetGenesis.Name { + require.Equal( + t, balance.Balance, rpcAsset.Amount, + ) + require.Equal( + t, balance.AssetGenesis, + rpcAsset.AssetGenesis, + ) + } + } + } +} + +func assertGroups(t *testing.T, client taprpc.TaprootAssetsClient, + issuableAssets []*mintrpc.MintAssetRequest) { + + t.Helper() + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) + defer cancel() + + // We should be able to fetch two groups of one asset each. + assetGroups, err := client.ListGroups( + ctxt, &taprpc.ListGroupsRequest{}, + ) + require.NoError(t, err) + + groupKeys := maps.Keys(assetGroups.Groups) + require.Equal(t, 2, len(groupKeys)) + + groupedAssets := assetGroups.Groups[groupKeys[0]].Assets + require.Equal(t, 1, len(groupedAssets)) + require.Equal(t, 1, len(assetGroups.Groups[groupKeys[1]].Assets)) + + groupedAssets = append( + groupedAssets, assetGroups.Groups[groupKeys[1]].Assets[0], + ) + + // Sort the listed assets to match the order of issuableAssets. + sort.Slice(groupedAssets, func(i, j int) bool { + return groupedAssets[i].Amount > groupedAssets[j].Amount + }) + + equalityCheck := func(a *mintrpc.MintAsset, + b *taprpc.AssetHumanReadable) { + + metaHash := (&proof.MetaReveal{ + Data: a.AssetMeta.Data, + }).MetaHash() + + require.Equal(t, a.AssetType, b.Type) + require.Equal(t, a.Name, b.Tag) + + require.Equal(t, metaHash[:], b.MetaHash) + require.Equal(t, a.Amount, b.Amount) + } + + equalityCheck(issuableAssets[0].Asset, groupedAssets[0]) + equalityCheck(issuableAssets[1].Asset, groupedAssets[1]) +} diff --git a/itest/assets_test.go b/itest/assets_test.go index 495f78e18..2bd110300 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -3,26 +3,19 @@ package itest import ( "context" "crypto/tls" - "fmt" "net/http" - "sort" "time" "github.com/btcsuite/btcd/chaincfg/chainhash" "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" "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" "golang.org/x/net/http2" - "google.golang.org/protobuf/proto" ) var ( - zeroHash chainhash.Hash - simpleAssets = []*mintrpc.MintAssetRequest{ { Asset: &mintrpc.MintAsset{ @@ -81,34 +74,23 @@ var ( } ) -// CopyRequest is a helper function to copy a request so that we can modify it. -func CopyRequest(req *mintrpc.MintAssetRequest) *mintrpc.MintAssetRequest { - return proto.Clone(req).(*mintrpc.MintAssetRequest) -} - -// CopyRequests is a helper function to copy a slice of requests so that we can -// modify them. -func CopyRequests(reqs []*mintrpc.MintAssetRequest) []*mintrpc.MintAssetRequest { - copied := make([]*mintrpc.MintAssetRequest, len(reqs)) - for idx := range reqs { - copied[idx] = CopyRequest(reqs[idx]) - } - return copied -} - // testMintAssets tests that we're able to mint assets, retrieve their proofs // and that we're able to import the proofs into a new node. func testMintAssets(t *harnessTest) { - rpcSimpleAssets := mintAssetsConfirmBatch(t, t.tapd, simpleAssets) - rpcIssuableAssets := mintAssetsConfirmBatch(t, t.tapd, issuableAssets) + rpcSimpleAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, simpleAssets, + ) + rpcIssuableAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, issuableAssets, + ) // Now that all our assets have been issued, we'll use the balance // calls to ensure that we're able to retrieve the proper balance for // them all. - assertAssetBalances(t, rpcSimpleAssets, rpcIssuableAssets) + AssertAssetBalances(t.t, t.tapd, rpcSimpleAssets, rpcIssuableAssets) // Check that we can retrieve the group keys for the issuable assets. - assertGroups(t, issuableAssets) + assertGroups(t.t, t.tapd, issuableAssets) // Make sure the proof files for the freshly minted assets can be // retrieved and are fully valid. @@ -133,189 +115,6 @@ func testMintAssets(t *harnessTest) { transferAssetProofs(t, t.tapd, secondTapd, allAssets, false) } -type mintOption func(*mintOptions) - -type mintOptions struct { - mintingTimeout time.Duration -} - -func defaultMintOptions() *mintOptions { - return &mintOptions{ - mintingTimeout: defaultWaitTimeout, - } -} - -func withMintingTimeout(timeout time.Duration) mintOption { - return func(options *mintOptions) { - options.mintingTimeout = timeout - } -} - -// mintAssetUnconfirmed is a helper function that mints a batch of assets and -// waits until the minting transaction is in the mempool but does not mine a -// block. -func mintAssetUnconfirmed(t *harnessTest, tapd *tapdHarness, - assetRequests []*mintrpc.MintAssetRequest, - opts ...mintOption) (chainhash.Hash, []byte) { - - options := defaultMintOptions() - for _, opt := range opts { - opt(options) - } - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, options.mintingTimeout) - defer cancel() - - // Mint all the assets in the same batch. - for idx, assetRequest := range assetRequests { - assetResp, err := tapd.MintAsset(ctxt, assetRequest) - require.NoError(t.t, err) - require.NotEmpty(t.t, assetResp.PendingBatch) - require.Len(t.t, assetResp.PendingBatch.Assets, idx+1) - } - - // Instruct the daemon to finalize the batch. - batchResp, err := tapd.FinalizeBatch( - ctxt, &mintrpc.FinalizeBatchRequest{}, - ) - require.NoError(t.t, err) - require.NotEmpty(t.t, batchResp.Batch) - require.Len(t.t, batchResp.Batch.Assets, len(assetRequests)) - require.Equal( - t.t, mintrpc.BatchState_BATCH_STATE_BROADCAST, - batchResp.Batch.State, - ) - - WaitForBatchState( - t.t, ctxt, tapd, options.mintingTimeout, - batchResp.Batch.BatchKey, - mintrpc.BatchState_BATCH_STATE_BROADCAST, - ) - hashes, err := waitForNTxsInMempool( - t.lndHarness.Miner.Client, 1, options.mintingTimeout, - ) - 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 := groupAssetsByName(listRespUnconfirmed.Assets) - for _, assetRequest := range assetRequests { - metaHash := (&proof.MetaReveal{ - Data: assetRequest.Asset.AssetMeta.Data, - }).MetaHash() - AssertAssetState( - t.t, unconfirmedAssets, assetRequest.Asset.Name, - metaHash[:], - assetAmountCheck(assetRequest.Asset.Amount), - assetTypeCheck(assetRequest.Asset.AssetType), - assetAnchorCheck(*hashes[0], zeroHash), - assetScriptKeyIsLocalCheck(true), - ) - } - - return *hashes[0], batchResp.Batch.BatchKey -} - -// mintAssetsConfirmBatch mints all given assets in the same batch, confirms the -// batch and verifies all asset proofs of the minted assets. -func mintAssetsConfirmBatch(t *harnessTest, tapd *tapdHarness, - assetRequests []*mintrpc.MintAssetRequest, - opts ...mintOption) []*taprpc.Asset { - - mintTXID, batchKey := mintAssetUnconfirmed( - t, tapd, assetRequests, opts..., - ) - - options := defaultMintOptions() - for _, opt := range opts { - opt(options) - } - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, options.mintingTimeout) - defer cancel() - - // Mine a block to confirm the assets. - block := mineBlocks(t, t.lndHarness, 1, 1)[0] - blockHash := block.BlockHash() - WaitForBatchState( - t.t, ctxt, tapd, options.mintingTimeout, batchKey, - mintrpc.BatchState_BATCH_STATE_FINALIZED, - ) - - return assertAssetsMinted(t, tapd, assetRequests, mintTXID, blockHash) -} - -// assertAssetsMinted makes sure all assets in the minting request were in fact -// minted in the given anchor TX and block. The function returns the list of -// minted assets. -func assertAssetsMinted(t *harnessTest, tapd *tapdHarness, - assetRequests []*mintrpc.MintAssetRequest, mintTXID, - blockHash chainhash.Hash) []*taprpc.Asset { - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) - defer cancel() - - // 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, - // since they were all minted in the same batch. - var ( - firstOutpoint string - assetList []*taprpc.Asset - ) - - listRespConfirmed, err := tapd.ListAssets( - ctxt, &taprpc.ListAssetRequest{}, - ) - require.NoError(t.t, err) - confirmedAssets := groupAssetsByName(listRespConfirmed.Assets) - - for _, assetRequest := range assetRequests { - metaHash := (&proof.MetaReveal{ - Data: assetRequest.Asset.AssetMeta.Data, - }).MetaHash() - mintedAsset := AssertAssetState( - t.t, confirmedAssets, assetRequest.Asset.Name, - metaHash[:], assetAnchorCheck(mintTXID, blockHash), - assetScriptKeyIsLocalCheck(true), - func(a *taprpc.Asset) error { - anchor := a.ChainAnchor - - if anchor.AnchorOutpoint == "" { - return fmt.Errorf("missing anchor " + - "outpoint") - } - - if firstOutpoint == "" { - firstOutpoint = anchor.AnchorOutpoint - - return nil - } - - if anchor.AnchorOutpoint != firstOutpoint { - return fmt.Errorf("unexpected anchor "+ - "outpoint, got %v wanted %v", - anchor.AnchorOutpoint, - firstOutpoint) - } - - return nil - }, - ) - - assetList = append(assetList, mintedAsset) - } - - return assetList -} - // transferAssetProofs locates and exports the proof files for all given assets // from the source node and imports them into the destination node. func transferAssetProofs(t *harnessTest, src, dst *tapdHarness, @@ -350,7 +149,7 @@ func transferAssetProofs(t *harnessTest, src, dst *tapdHarness, ) require.NoError(t.t, err) - importedAssets := groupAssetsByName(listResp.Assets) + importedAssets := GroupAssetsByName(listResp.Assets) for _, existingAsset := range assets { gen := existingAsset.AssetGenesis anchorTxHash, err := chainhash.NewHashFromStr( @@ -365,136 +164,14 @@ func transferAssetProofs(t *harnessTest, src, dst *tapdHarness, AssertAssetState( t.t, importedAssets, gen.Name, gen.MetaHash, - assetAmountCheck(existingAsset.Amount), - assetTypeCheck(existingAsset.AssetType), - assetAnchorCheck(*anchorTxHash, *anchorBlockHash), - assetScriptKeyIsLocalCheck(shouldShowUpAsLocal), + AssetAmountCheck(existingAsset.Amount), + AssetTypeCheck(existingAsset.AssetType), + AssetAnchorCheck(*anchorTxHash, *anchorBlockHash), + AssetScriptKeyIsLocalCheck(shouldShowUpAsLocal), ) } } -func assertAssetBalances(t *harnessTest, - simpleAssets, issuableAssets []*taprpc.Asset) { - - t.t.Helper() - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) - defer cancel() - - // First, we'll ensure that we're able to get the balances of all the - // assets grouped by their asset IDs. - balanceReq := &taprpc.ListBalancesRequest_AssetId{ - AssetId: true, - } - assetIDBalances, err := t.tapd.ListBalances( - ctxt, &taprpc.ListBalancesRequest{ - GroupBy: balanceReq, - }, - ) - require.NoError(t.t, err) - - var allAssets []*taprpc.Asset - allAssets = append(allAssets, simpleAssets...) - allAssets = append(allAssets, issuableAssets...) - - require.Equal(t.t, len(allAssets), len(assetIDBalances.AssetBalances)) - - for _, balance := range assetIDBalances.AssetBalances { - for _, rpcAsset := range allAssets { - if balance.AssetGenesis.Name == rpcAsset.AssetGenesis.Name { - require.Equal( - t.t, balance.Balance, rpcAsset.Amount, - ) - require.Equal( - t.t, - balance.AssetGenesis, - rpcAsset.AssetGenesis, - ) - } - } - } - - // We'll also ensure that we're able to get the balance by key group - // for all the assets that have one specified. - groupBalanceReq := &taprpc.ListBalancesRequest_GroupKey{ - GroupKey: true, - } - assetGroupBalances, err := t.tapd.ListBalances( - ctxt, &taprpc.ListBalancesRequest{ - GroupBy: groupBalanceReq, - }, - ) - require.NoError(t.t, err) - - require.Equal( - t.t, len(issuableAssets), - len(assetGroupBalances.AssetGroupBalances), - ) - - for _, balance := range assetGroupBalances.AssetBalances { - for _, rpcAsset := range issuableAssets { - if balance.AssetGenesis.Name == rpcAsset.AssetGenesis.Name { - require.Equal( - t.t, balance.Balance, rpcAsset.Amount, - ) - require.Equal( - t.t, - balance.AssetGenesis, - rpcAsset.AssetGenesis, - ) - } - } - } -} - -func assertGroups(t *harnessTest, issuableAssets []*mintrpc.MintAssetRequest) { - t.t.Helper() - - ctxb := context.Background() - ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) - defer cancel() - - // We should be able to fetch two groups of one asset each. - assetGroups, err := t.tapd.ListGroups( - ctxt, &taprpc.ListGroupsRequest{}, - ) - require.NoError(t.t, err) - - groupKeys := maps.Keys(assetGroups.Groups) - require.Equal(t.t, 2, len(groupKeys)) - - groupedAssets := assetGroups.Groups[groupKeys[0]].Assets - require.Equal(t.t, 1, len(groupedAssets)) - require.Equal(t.t, 1, len(assetGroups.Groups[groupKeys[1]].Assets)) - - groupedAssets = append( - groupedAssets, assetGroups.Groups[groupKeys[1]].Assets[0], - ) - - // Sort the listed assets to match the order of issuableAssets. - sort.Slice(groupedAssets, func(i, j int) bool { - return groupedAssets[i].Amount > groupedAssets[j].Amount - }) - - equalityCheck := func(a *mintrpc.MintAsset, - b *taprpc.AssetHumanReadable) { - - metaHash := (&proof.MetaReveal{ - Data: a.AssetMeta.Data, - }).MetaHash() - - require.Equal(t.t, a.AssetType, b.Type) - require.Equal(t.t, a.Name, b.Tag) - - require.Equal(t.t, metaHash[:], b.MetaHash) - require.Equal(t.t, a.Amount, b.Amount) - } - - equalityCheck(issuableAssets[0].Asset, groupedAssets[0]) - equalityCheck(issuableAssets[1].Asset, groupedAssets[1]) -} - // testMintAssetNameCollisionError tests that no error is produced when // attempting to mint an asset whose name collides with an existing minted asset // or an asset from a cancelled minting batch. An error should be produced @@ -515,8 +192,9 @@ func testMintAssetNameCollisionError(t *harnessTest) { Amount: 5000, }, } - rpcSimpleAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{&assetMint}, + rpcSimpleAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{&assetMint}, ) // Ensure minted asset with requested name was successfully minted. @@ -612,8 +290,9 @@ func testMintAssetNameCollisionError(t *harnessTest) { // Minting the asset with the name collision should work, even though // it is also part of a cancelled batch. - rpcCollideAsset := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{&assetCollide}, + rpcCollideAsset := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{&assetCollide}, ) collideAssetName := rpcCollideAsset[0].AssetGenesis.Name diff --git a/itest/burn_test.go b/itest/burn_test.go index ade45da07..bcbf43fa1 100644 --- a/itest/burn_test.go +++ b/itest/burn_test.go @@ -16,8 +16,9 @@ import ( // testBurnAssets tests that we're able to mint assets and then burn assets // again. func testBurnAssets(t *harnessTest) { - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + minerClient := t.lndHarness.Miner.Client + rpcAssets := MintAssetsConfirmBatch( + t.t, minerClient, t.tapd, []*mintrpc.MintAssetRequest{ simpleAssets[0], simpleAssets[1], issuableAssets[1], }, ) @@ -88,14 +89,16 @@ func testBurnAssets(t *harnessTest) { }, ) require.NoError(t.t, err) - confirmAndAssetOutboundTransferWithOutputs( - t, t.tapd, sendResp, simpleAssetGen.AssetId, outputAmounts, 0, 1, - numOutputs, + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, minerClient, t.tapd, sendResp, simpleAssetGen.AssetId, + outputAmounts, 0, 1, numOutputs, ) // Let's make sure that we still have the original number of assets as // seen by our wallet balance. - AssertBalanceByID(t.t, t.tapd, simpleAssetGen.AssetId, simpleAsset.Amount) + AssertBalanceByID( + t.t, t.tapd, simpleAssetGen.AssetId, simpleAsset.Amount, + ) // Test case 1: We'll now try to the exact amount of the largest output, // which should still select exactly that one largest output, which is @@ -130,8 +133,9 @@ func testBurnAssets(t *harnessTest) { require.NoError(t.t, err) t.Logf("Got response from burning %d units: %v", burnAmt, burnRespJSON) - assertAssetOutboundTransferWithOutputs( - t, t.tapd, burnResp.BurnTransfer, simpleAssetGen.AssetId, + AssertAssetOutboundTransferWithOutputs( + t.t, minerClient, t.tapd, burnResp.BurnTransfer, + simpleAssetGen.AssetId, []uint64{outputAmounts[2] - burnAmt, burnAmt}, 1, 2, 2, true, ) @@ -143,10 +147,10 @@ func testBurnAssets(t *harnessTest) { require.NoError(t.t, err) AssertAssetStateByScriptKey( t.t, allAssets.Assets, burnedAsset.ScriptKey, - assetAmountCheck(burnedAsset.Amount), - assetTypeCheck(burnedAsset.AssetType), - assetScriptKeyIsLocalCheck(false), - assetScriptKeyIsBurnCheck(true), + AssetAmountCheck(burnedAsset.Amount), + AssetTypeCheck(burnedAsset.AssetType), + AssetScriptKeyIsLocalCheck(false), + AssetScriptKeyIsBurnCheck(true), ) // And now our asset balance should have been decreased by the burned @@ -165,10 +169,10 @@ func testBurnAssets(t *harnessTest) { }) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, simpleAsset, fullSendAddr) + AssertAddrCreated(t.t, t.tapd, simpleAsset, fullSendAddr) sendResp = sendAssetsToAddr(t, t.tapd, fullSendAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, simpleAssetGen.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, minerClient, t.tapd, sendResp, simpleAssetGen.AssetId, []uint64{0, secondSendAmt}, 2, 3, ) AssertNonInteractiveRecvComplete(t.t, t.tapd, 1) @@ -189,9 +193,9 @@ func testBurnAssets(t *harnessTest) { require.NoError(t.t, err) t.Logf("Got response from burning all units: %v", burnRespJSON) - assertAssetOutboundTransferWithOutputs( - t, t.tapd, burnResp.BurnTransfer, simpleCollectibleGen.AssetId, - []uint64{1}, 3, 4, 1, true, + AssertAssetOutboundTransferWithOutputs( + t.t, minerClient, t.tapd, burnResp.BurnTransfer, + simpleCollectibleGen.AssetId, []uint64{1}, 3, 4, 1, true, ) // Test case 4: Burn assets from multiple inputs. This will select the @@ -213,8 +217,9 @@ func testBurnAssets(t *harnessTest) { t.Logf("Got response from burning units from multiple inputs: %v", burnRespJSON) - assertAssetOutboundTransferWithOutputs( - t, t.tapd, burnResp.BurnTransfer, simpleAssetGen.AssetId, + AssertAssetOutboundTransferWithOutputs( + t.t, minerClient, t.tapd, burnResp.BurnTransfer, + simpleAssetGen.AssetId, []uint64{changeAmt, multiBurnAmt}, 4, 5, 2, true, ) diff --git a/itest/collectible_split_test.go b/itest/collectible_split_test.go index 274ce0e0d..22bde3467 100644 --- a/itest/collectible_split_test.go +++ b/itest/collectible_split_test.go @@ -15,8 +15,9 @@ import ( // with split commitments. func testCollectibleSend(t *harnessTest) { // First, we'll make a collectible with emission enabled. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ issuableAssets[1], // Our "passive" asset. { @@ -74,12 +75,13 @@ func testCollectibleSend(t *harnessTest) { ) require.NoError(t.t, err) - assertAddrCreated( + AssertAddrCreated( t.t, secondTapd, rpcAssets[0], receiverAddr, ) sendResp := sendAssetsToAddr(t, t.tapd, receiverAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, + sendResp, genInfo.AssetId, []uint64{0, fullAmount}, senderTransferIdx, senderTransferIdx+1, ) @@ -97,14 +99,15 @@ func testCollectibleSend(t *harnessTest) { ) require.NoError(t.t, err) - assertAddrCreated( + AssertAddrCreated( t.t, t.tapd, rpcAssets[0], receiverAddr, ) sendResp := sendAssetsToAddr( t, secondTapd, receiverAddr, ) - confirmAndAssertOutboundTransfer( - t, secondTapd, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, secondTapd, + sendResp, genInfo.AssetId, []uint64{0, fullAmount}, receiverTransferIdx, receiverTransferIdx+1, ) @@ -178,11 +181,11 @@ func testCollectibleSend(t *harnessTest) { }) require.NoError(t.t, err) - assertAddrCreated(t.t, secondTapd, rpcAssets[1], bobAddr) + AssertAddrCreated(t.t, secondTapd, rpcAssets[1], bobAddr) sendResp := sendAssetsToAddr(t, t.tapd, bobAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, passiveGen.AssetId, - []uint64{0, rpcAssets[1].Amount}, 2, 3, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + passiveGen.AssetId, []uint64{0, rpcAssets[1].Amount}, 2, 3, ) _ = sendProof( t, t.tapd, secondTapd, bobAddr.ScriptKey, passiveGen, diff --git a/itest/full_value_split_test.go b/itest/full_value_split_test.go index 0bf139dba..75854b0ca 100644 --- a/itest/full_value_split_test.go +++ b/itest/full_value_split_test.go @@ -13,8 +13,9 @@ import ( func testFullValueSend(t *harnessTest) { // First, we'll make an normal assets with enough units to allow us to // send it around a few times. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], issuableAssets[0], }, ) @@ -79,10 +80,11 @@ func runFullValueSendTests(ctxt context.Context, t *harnessTest, alice, ) require.NoError(t.t, err) - assertAddrCreated(t.t, bob, mintedAsset, receiverAddr) + AssertAddrCreated(t.t, bob, mintedAsset, receiverAddr) sendResp := sendAssetsToAddr(t, alice, receiverAddr) - confirmAndAssertOutboundTransfer( - t, alice, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, alice, + sendResp, genInfo.AssetId, []uint64{0, fullAmount}, senderTransferIdx, senderTransferIdx+1, ) @@ -99,12 +101,12 @@ func runFullValueSendTests(ctxt context.Context, t *harnessTest, alice, ) require.NoError(t.t, err) - assertAddrCreated(t.t, alice, mintedAsset, receiverAddr) + AssertAddrCreated(t.t, alice, mintedAsset, receiverAddr) sendResp := sendAssetsToAddr(t, bob, receiverAddr) - confirmAndAssertOutboundTransfer( - t, bob, sendResp, genInfo.AssetId, - []uint64{0, fullAmount}, receiverTransferIdx, - receiverTransferIdx+1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, bob, sendResp, + genInfo.AssetId, []uint64{0, fullAmount}, + receiverTransferIdx, receiverTransferIdx+1, ) _ = sendProof( t, bob, alice, receiverAddr.ScriptKey, genInfo, diff --git a/itest/interface.go b/itest/interface.go index 0741e0b4c..acd98f0a1 100644 --- a/itest/interface.go +++ b/itest/interface.go @@ -2,6 +2,8 @@ package itest import ( "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" ) @@ -9,4 +11,6 @@ import ( type TapdClient interface { taprpc.TaprootAssetsClient unirpc.UniverseClient + mintrpc.MintClient + assetwalletrpc.AssetWalletClient } diff --git a/itest/loadtest/Dockerfile b/itest/loadtest/Dockerfile new file mode 100644 index 000000000..a233fa491 --- /dev/null +++ b/itest/loadtest/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.21.0 as builder + +WORKDIR /app + +COPY . /app + +ENV CGO_ENABLED=0 + +RUN make build-loadtest + +# FINAL IMAGE +FROM alpine as final + +COPY --from=builder /app/loadtest /bin/ + +ENTRYPOINT ["loadtest"] diff --git a/itest/loadtest/config.go b/itest/loadtest/config.go new file mode 100644 index 000000000..20570c273 --- /dev/null +++ b/itest/loadtest/config.go @@ -0,0 +1,144 @@ +package loadtest + +import ( + "time" + + "github.com/jessevdk/go-flags" +) + +const ( + // defaultConfigPath is the default path of the configuration file. + defaultConfigPath = "loadtest.conf" + + // defaultSuiteTimeout is the default timeout for the entire test suite. + defaultSuiteTimeout = 120 * time.Minute + + // defaultTestTimeout is the default timeout for each test. + defaultTestTimeout = 10 * time.Minute +) + +// User defines the config options for a user in the network. +type User struct { + Tapd *TapConfig `group:"tapd" namespace:"tapd"` +} + +// TapConfig are the main parameters needed for identifying and creating a grpc +// client to a tapd subsystem. +type TapConfig struct { + Name string `long:"name" description:"the name of the tapd instance"` + Host string `long:"host" description:"the host to connect to"` + Port int `long:"port" description:"the port to connect to"` + + TLSPath string `long:"tlspath" description:"Path to tapd's TLS certificate, leave empty if TLS is disabled"` + MacPath string `long:"macpath" description:"Path to tapd's macaroon file"` +} + +// BitcoinConfig defines exported config options for the connection to the +// btcd/bitcoind backend. +type BitcoinConfig struct { + Host string `long:"host" description:"bitcoind/btcd instance address"` + Port int `long:"port" description:"bitcoind/btcd instance port"` + User string `long:"user" description:"bitcoind/btcd user name"` + Password string `long:"password" description:"bitcoind/btcd password"` + TLSPath string `long:"tlspath" description:"Path to btcd's TLS certificate, if TLS is enabled"` +} + +// Config holds the main configuration for the performance testing binary. +type Config struct { + // TestCases is a comma separated list of test cases that will be + // executed. + TestCases []string `long:"test-case" description:"the test case that will be executed"` + + // Alice is the configuration for the main user in the network. + Alice *User `group:"alice" namespace:"alice" description:"alice related configuration"` + + // Bob is the configuration for the secondary user in the network. + Bob *User `group:"bob" namespace:"bob" description:"bob related configuration"` + + // Bitcoin is the configuration for the bitcoin backend. + Bitcoin *BitcoinConfig `group:"bitcoin" namespace:"bitcoin" long:"bitcoin" description:"bitcoin client configuration"` + + // BatchSize is the number of assets to mint in a single batch. This is only + // relevant for some test cases. + BatchSize int `long:"batch-size" description:"the number of assets to mint in a single batch"` + + // TestSuiteTimeout is the timeout for the entire test suite. + TestSuiteTimeout time.Duration `long:"test-suite-timeout" description:"the timeout for the entire test suite"` + + // TestTimeout is the timeout for each test. + TestTimeout time.Duration `long:"test-timeout" description:"the timeout for each test"` +} + +// DefaultConfig returns the default configuration for the performance testing +// binary. +func DefaultConfig() Config { + return Config{ + TestCases: []string{"mint_batch_stress"}, + Alice: &User{ + Tapd: &TapConfig{ + Name: "alice", + }, + }, + Bob: &User{ + Tapd: &TapConfig{ + Name: "bob", + }, + }, + BatchSize: 100, + TestSuiteTimeout: defaultSuiteTimeout, + TestTimeout: defaultTestTimeout, + } +} + +// LoadConfig initializes and parses the config using a config file and command +// line options. +// +// The configuration proceeds as follows: +// 1. Start with a default config with sane settings +// 2. Pre-parse the command line to check for an alternative config file +// 3. Load configuration file overwriting defaults with any specified options +// 4. Parse CLI options and overwrite/add any specified options +func LoadConfig() (*Config, error) { + // Pre-parse the command line options to pick up an alternative config + // file. + preCfg := DefaultConfig() + if _, err := flags.Parse(&preCfg); err != nil { + return nil, err + } + + // Next, load any additional configuration options from the file. + cfg := preCfg + fileParser := flags.NewParser(&cfg, flags.Default) + + err := flags.NewIniParser(fileParser).ParseFile(defaultConfigPath) + if err != nil { + // If it's a parsing related error, then we'll return + // immediately, otherwise we can proceed as possibly the config + // file doesn't exist which is OK. + if _, ok := err.(*flags.IniError); ok { //nolint:gosimple + return nil, err + } + } + + // Finally, parse the remaining command line options again to ensure + // they take precedence. + flagParser := flags.NewParser(&cfg, flags.Default) + if _, err := flagParser.Parse(); err != nil { + return nil, err + } + + // Make sure everything we just loaded makes sense. + cleanCfg, err := ValidateConfig(cfg) + if err != nil { + return nil, err + } + + return cleanCfg, nil +} + +// ValidateConfig validates the given configuration and returns a clean version +// of it with sane defaults. +func ValidateConfig(cfg Config) (*Config, error) { + // TODO (positiveblue): add validation logic. + return &cfg, nil +} diff --git a/itest/loadtest/load_test.go b/itest/loadtest/load_test.go new file mode 100644 index 000000000..0465fc825 --- /dev/null +++ b/itest/loadtest/load_test.go @@ -0,0 +1,40 @@ +//go:build loadtest + +package loadtest + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestPerformance executes the configured performance tests. +func TestPerformance(t *testing.T) { + cfg, err := LoadConfig() + require.NoError(t, err, "unable to load main config") + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, cfg.TestSuiteTimeout) + defer cancel() + + for _, testCase := range cfg.TestCases { + execTestCase(t, ctxt, testCase, cfg) + } +} + +// execTestCase is the method in charge of executing a single test case. +func execTestCase(t *testing.T, ctx context.Context, testName string, + cfg *Config) { + + ctxt, cancel := context.WithTimeout(ctx, cfg.TestTimeout) + defer cancel() + + switch testName { + case "mint_batch_stress": + execMintBatchStressTest(t, ctxt, cfg) + + default: + require.Fail(t, "unknown test case: %v", testName) + } +} diff --git a/itest/loadtest/loadtest-sample.conf b/itest/loadtest/loadtest-sample.conf new file mode 100644 index 000000000..8b6024d55 --- /dev/null +++ b/itest/loadtest/loadtest-sample.conf @@ -0,0 +1,19 @@ +[bitcoin] +bitcoin.host="localhost" +bitcoin.port=18443 +bitcoin.user=lightning +bitcoin.password=lightning + +[alice] +alice.tapd.name=alice +alice.tapd.host="localhost" +alice.tapd.port=10029 +alice.tapd.tlspath=path-to-alice/.tapd/tls.cert +alice.tapd.macpath=path-to-alice/.tapd/data/regtest/admin.macaroon + +[bob] +bob.tapd.name=bob +bob.tapd.host="localhost" +bob.tapd.port=10032 +bob.tapd.tlspath=path-to-bob/.tapd/tls.cert +bob.tapd.macpath=path-to-bob/.tapd/data/regtest/admin.macaroon \ No newline at end of file diff --git a/itest/loadtest/mint_batch_test.go b/itest/loadtest/mint_batch_test.go new file mode 100644 index 000000000..1466f1d9b --- /dev/null +++ b/itest/loadtest/mint_batch_test.go @@ -0,0 +1,199 @@ +package loadtest + +import ( + "context" + "encoding/binary" + "encoding/hex" + "fmt" + "strconv" + "strings" + "testing" + "time" + + _ "embed" + + "github.com/btcsuite/btcd/rpcclient" + "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/itest" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/stretchr/testify/require" +) + +//go:embed testdata/8k-metadata.hex +var imageMetadataHex []byte + +// execMintBatchStressTest checks that we are able to mint a batch of assets +// and that other memebers in the federation see the universe updated +// accordingly. +func execMintBatchStressTest(t *testing.T, ctx context.Context, cfg *Config) { + // Create tapd clients. + alice, aliceCleanUp := getTapClient(t, ctx, cfg.Alice.Tapd) + defer aliceCleanUp() + + _, err := alice.GetInfo(ctx, &taprpc.GetInfoRequest{}) + require.NoError(t, err) + + bob, bobCleanUp := getTapClient(t, ctx, cfg.Bob.Tapd) + defer bobCleanUp() + + _, err = bob.GetInfo(ctx, &taprpc.GetInfoRequest{}) + require.NoError(t, err) + + // Create bitcoin client. + bitcoinClient := getBitcoinConn(t, cfg.Bitcoin) + + imageMetadataBytes, err := hex.DecodeString( + strings.Trim(string(imageMetadataHex), "\n"), + ) + require.NoError(t, err) + + aliceHost := fmt.Sprintf("%s:%d", cfg.Alice.Tapd.Host, + cfg.Alice.Tapd.Port) + + minterTimeout := 10 * time.Minute + mintBatchStressTest( + t, ctx, bitcoinClient, alice, bob, aliceHost, cfg.BatchSize, + imageMetadataBytes, minterTimeout, + ) +} + +func mintBatchStressTest(t *testing.T, ctx context.Context, + bitcoinClient *rpcclient.Client, alice, bob itest.TapdClient, + aliceHost string, batchSize int, imageMetadataBytes []byte, + minterTimeout time.Duration) { + + var ( + batchReqs = make([]*mintrpc.MintAssetRequest, batchSize) + baseName = "jpeg" + metaPrefixSize = binary.MaxVarintLen16 + metadataPrefix = make([]byte, metaPrefixSize) + ) + + // Each asset in the batch will share a name and metdata preimage, that + // will be updated based on the asset's index in the batch. + collectibleRequestTemplate := mintrpc.MintAssetRequest{ + Asset: &mintrpc.MintAsset{ + AssetType: taprpc.AssetType_COLLECTIBLE, + Name: baseName, + AssetMeta: &taprpc.AssetMeta{ + Data: imageMetadataBytes, + Type: 0, + }, + Amount: 1, + }, + EnableEmission: false, + } + + // Update the asset name and metadata to match an index. + incrementMintAsset := func(asset *mintrpc.MintAsset, ind int) { + asset.Name = asset.Name + strconv.Itoa(ind) + binary.PutUvarint(metadataPrefix, uint64(ind)) + copy(asset.AssetMeta.Data[0:metaPrefixSize], metadataPrefix) + } + + // Use the first asset of the batch as the asset group anchor. + collectibleAnchorReq := itest.CopyRequest(&collectibleRequestTemplate) + incrementMintAsset(collectibleAnchorReq.Asset, 0) + collectibleAnchorReq.EnableEmission = true + batchReqs[0] = collectibleAnchorReq + + // Generate the rest of the batch, with each asset referencing the group + // anchor we created above. + for i := 1; i < batchSize; i++ { + groupedAsset := itest.CopyRequest(&collectibleRequestTemplate) + incrementMintAsset(groupedAsset.Asset, i) + groupedAsset.Asset.GroupAnchor = collectibleAnchorReq.Asset.Name + batchReqs[i] = groupedAsset + } + + // Submit the batch for minting. Use an extended timeout for the TX + // appearing in the mempool, so we can observe the minter hitting its + // own shorter default timeout. + itest.LogfTimestamped(t, "beginning minting of batch of %d assets", + batchSize) + + mintBatch := itest.MintAssetsConfirmBatch( + t, bitcoinClient, alice, batchReqs, + itest.WithMintingTimeout(minterTimeout), + ) + + itest.LogfTimestamped(t, "finished batch mint of %d assets", batchSize) + + // We can re-derive the group key to verify that the correct asset was + // used as the group anchor. + collectibleAnchor := itest.VerifyGroupAnchor( + t, mintBatch, collectibleAnchorReq.Asset.Name, + ) + collectGroupKey := collectibleAnchor.AssetGroup.TweakedGroupKey + collectGroupKeyStr := hex.EncodeToString(collectGroupKey[:]) + + // We should have one group, with the specified number of assets and an + // equivalent balance, since the group is made of collectibles. + groupCount := 1 + groupBalance := batchSize + + itest.AssertNumGroups(t, alice, groupCount) + itest.AssertGroupSizes( + t, alice, []string{collectGroupKeyStr}, + []int{batchSize}, + ) + itest.AssertBalanceByGroup( + t, alice, collectGroupKeyStr, uint64(groupBalance), + ) + + // The universe tree should reflect the same properties about the batch; + // there should be one root with a group key and balance matching what + // we asserted previously. + uniRoots, err := alice.AssetRoots( + ctx, &unirpc.AssetRootRequest{}, + ) + require.NoError(t, err) + require.Len(t, uniRoots.UniverseRoots, groupCount) + + err = itest.AssertUniverseRoot( + t, alice, groupBalance, nil, collectGroupKey, + ) + require.NoError(t, err) + + // The universe tree should also have a leaf for each asset minted. + // TODO(jhb): Resolve issue of 33-byte group key handling. + collectUniID := unirpc.ID{ + Id: &unirpc.ID_GroupKey{ + GroupKey: collectGroupKey[1:], + }, + } + uniLeaves, err := alice.AssetLeaves(ctx, &collectUniID) + require.NoError(t, err) + require.Len(t, uniLeaves.Leaves, batchSize) + + // The universe tree should also have a key for each asset, with all + // outpoints matching the chain anchor of the group anchor. + mintOutpoint := collectibleAnchor.ChainAnchor.AnchorOutpoint + uniKeys, err := alice.AssetLeafKeys(ctx, &collectUniID) + require.NoError(t, err) + require.Len(t, uniKeys.AssetKeys, batchSize) + + correctOp := fn.All(uniKeys.AssetKeys, func(key *unirpc.AssetKey) bool { + return key.GetOpStr() == mintOutpoint + }) + require.True(t, correctOp) + + _, err = bob.AddFederationServer( + ctx, &unirpc.AddFederationServerRequest{ + Servers: []*unirpc.UniverseFederationServer{ + { + Host: aliceHost, + }, + }, + }, + ) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return itest.AssertUniverseStateEqual( + t, alice, bob, + ) + }, minterTimeout, time.Second) +} diff --git a/itest/loadtest/testdata/8k-metadata.hex b/itest/loadtest/testdata/8k-metadata.hex new file mode 100644 index 000000000..0a5fd4db5 --- /dev/null +++ b/itest/loadtest/testdata/8k-metadata.hex @@ -0,0 +1 @@ +89504e470d0a1a0a0000000d4948445200000150000001500802000000668ab18e0000000249444154789c62a4912b000008d449444154edddb1ad64551045d171098228088104480021441ab8584898844178b810c1575fe374d7b9afd6d3f2e7dfaadaedceb7dffef81b58e2dbf85f007c8ce06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b081e16113c2c22785844f0b088e06111c1c322828745040f8b087ecc7ffffeb5d6f8f0d712fc98f1ea04bf90e0c78c5727f885043f66bc3ac12f24f831e3d5097e21c18f19af4ef00b097ecc7875825f48f063c6ab13fc42821f335e9de01712fc98f1ea04bf90e0c78c5727f885043f66bc3ac12f24f831e3d5097e21c18f19af4ef00b09fec878216b8daffe61047f64fceed71a5ffdc308fec8f8ddaf35befa8711fc91f1bb5f6b7cf50f23f823e377bfd6f8ea1f46f047c6ef7eadf1d53f8ce08f8cdffd5ae3ab7f18c11f19bffbb5c657ff30823f327ef76b8daffe61047f64fceed71a5ffdc308fec8f8ddaf35befa8711fc91f1bb5f6b7cf50f23f823e377bfd6f8ea1f46f047c6ef7eadf1d53f8ce08f8cdf3d2f8d1fc915047f64fc9a7969fc48ae20f823e3d7cc4be3477205c11f19bf665e1a3f922b08fec8f835f3d2f8915c41f047c6af9997c68fe40a823f327ecdbc347e245710fc91f16be6a5f123b982e08f8c5f332f8d1fc915047f64fc9a7969fc48ae20f823e3d7cc4be3477205c11f19bf665e1a3f922b08fec8f835f3d2f8915c41f047c6af9997c68fe40aab831fbfd187f9e7cf5f0fcdfe9de3873748f0c408be9fe089117c3fc11323f87e822746f0fd044f8ce0fb099e18c1f7133c3182ef27786204df4ff0c408be9fe089117c3fc11323f87e822746f0fd044f8ce0fb097ee9d1bfe345ef20f82cc10b5ef08b085ef0825f44f08217fc228217bce01711bce005bf88e0052ff845042f78c12f2278c10b7e11c10b5ef08b085ef0825f44f08217fc228217bce01711bce005bf88e097e631fb770a7e8ae0055f4df05982177c35c167095ef0d5049f2578c157137c96e0055f4df05982177c35c167095ef0d5049f2578c157137c96e0055f4df05982177c35c167095ef0d5049f2578c157137c96e0055f4df0590f0cfe1d27f2bc906e21f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3043f96f14fbfff10371be73b5e24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12bce0ab5f24f82cc10bbefa4582cf12fc58f0df8ebf9ffffcf1d06cf0ef7891e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf082af7e91e0b3042ff8ea17093e4bf017fc346c26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86ab3db1c3fe638c10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b3042ff86a82cf12bce0ab093e4bf082af26f82cc10bbe9ae0b304bf34f8f3fff851f04f2278c10b5ef03713bce005ff15c10b5ef082bf99e0052ff8af085ef08217fccd042f78c17f45f08217bce06f2678c10bfe2b8217bce0057f33c10b5ef05f11bce0052ff89b095ef082ff8ae0052f78c1f7995dfce69f86cdc13fefa741f082af36be26c10b5ef08217bce0052ff87a82177cb5f135095ef08217bce0052f78c1d713bce0ab8daf49f08217bce0052f78c10bbe9ee0055f6d7c4d8217bce0052f78c10b5ef0f5042ff86ae36b12fc738c5f49fca7e197efbf1b349bf1f8395d41f01710bce053047f01c10b3e45f01710bce053047f01c10b3e45f01710bce053047f01c10b3e45f01710bce053047f01c10b3e45f01710bce053047f01c10b3e45f01710bce053047f01c10b3e45f01710bce0535607ff96815ef29f34ce3a9fd2f8421fc640d30315bce08b19687aa082177c31034d0f54f0822f66a0e9810a5ef0c50c343d50c10bbe9881a6072a78c11733d0f440052ff862069a1ea8e0055fcc40d30315bce08b19687aa082177c31034d0f54f0822f66a0e9810a5ef0c50c343d50c10bbe9881a607fa86e06ff91191713fa34f0f54f0822f66f4e9810a5ef0c58c3e3d50c10bbe98d1a7072a78c11733faf440052ff862469f1ea8e0055fcce8d30315bce08b197d7aa082177c31a34f0f54f0822f66f4e9810a5ef0c58c3e3d50c10bbe98d1a7072a78c11733faf440052ff862469f1ee8689ce7fffa3bbef1e1f39225a5077afc099ecfb3a4f4408f3fc1f37996941ee8f127783ecf92d2033dfe04cfe759527aa0c79fe0f93c4b4a0ff4f8133c9f6749e9811e7f82e7f32c293dd0e34ff07c9e25a5077afc099ecfb3a4f4408f3fc1f37996941ee8f127783ecf92d2033dfe04cfe759527aa0c79fe0f93c4b4a0ff4f87b47f0eff869181f2941d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029669de9810a9e62d6991ea8e029f63f3df5c34a25705adb0000000049454e44ae426082 diff --git a/itest/loadtest/utils.go b/itest/loadtest/utils.go new file mode 100644 index 000000000..5fe40d096 --- /dev/null +++ b/itest/loadtest/utils.go @@ -0,0 +1,126 @@ +package loadtest + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "testing" + + "github.com/btcsuite/btcd/rpcclient" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon.v2" +) + +var ( + // maxMsgRecvSize is the largest message our client will receive. We + // set this to 200MiB atm. + maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize) +) + +type rpcClient struct { + taprpc.TaprootAssetsClient + universerpc.UniverseClient + mintrpc.MintClient + assetwalletrpc.AssetWalletClient +} + +func getTapClient(t *testing.T, ctx context.Context, + cfg *TapConfig) (*rpcClient, func()) { + + creds := credentials.NewTLS(&tls.Config{}) + if cfg.TLSPath != "" { + // Load the certificate file now, if specified. + tlsCert, err := os.ReadFile(cfg.TLSPath) + require.NoError(t, err) + + cp := x509.NewCertPool() + ok := cp.AppendCertsFromPEM(tlsCert) + require.True(t, ok) + + creds = credentials.NewClientTLSFromCert(cp, "") + } + + // Create a dial options array. + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(creds), + grpc.WithDefaultCallOptions(maxMsgRecvSize), + } + + if cfg.MacPath != "" { + var macBytes []byte + macBytes, err := os.ReadFile(cfg.MacPath) + require.NoError(t, err) + + mac := &macaroon.Macaroon{} + err = mac.UnmarshalBinary(macBytes) + require.NoError(t, err) + + macCred, err := macaroons.NewMacaroonCredential(mac) + require.NoError(t, err) + + opts = append(opts, grpc.WithPerRPCCredentials(macCred)) + } + + svrAddr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + conn, err := grpc.Dial(svrAddr, opts...) + require.NoError(t, err) + + assetsClient := taprpc.NewTaprootAssetsClient(conn) + universeClient := universerpc.NewUniverseClient(conn) + mintMintClient := mintrpc.NewMintClient(conn) + assetWalletClient := assetwalletrpc.NewAssetWalletClient(conn) + + client := &rpcClient{ + TaprootAssetsClient: assetsClient, + UniverseClient: universeClient, + MintClient: mintMintClient, + AssetWalletClient: assetWalletClient, + } + + cleanUp := func() { + conn.Close() + } + + return client, cleanUp +} + +func getBitcoinConn(t *testing.T, cfg *BitcoinConfig) *rpcclient.Client { + var ( + rpcCert []byte + err error + ) + + disableTLS := cfg.TLSPath == "" + + // In case we use TLS and a certificate argument is provided, we need to + // read that file and provide it to the RPC connection as byte slice. + if !disableTLS { + rpcCert, err = os.ReadFile(cfg.TLSPath) + require.NoError(t, err) + } + + // Connect to the backend with the certs we just loaded. + connCfg := &rpcclient.ConnConfig{ + Host: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), + User: cfg.User, + Pass: cfg.Password, + HTTPPostMode: true, + DisableTLS: disableTLS, + Certificates: rpcCert, + } + + client, err := rpcclient.New(connCfg, nil) + require.NoError(t, err) + + return client +} diff --git a/itest/mint_batch_stress_test.go b/itest/mint_batch_stress_test.go index 9ff63a7a1..2ee4d55b2 100644 --- a/itest/mint_batch_stress_test.go +++ b/itest/mint_batch_stress_test.go @@ -64,8 +64,9 @@ func testMintBatchNStressTest(t *harnessTest, batchSize int, }() mintBatches := func(reqs []*mintrpc.MintAssetRequest) []*taprpc.Asset { - return mintAssetsConfirmBatch( - t, t.tapd, reqs, withMintingTimeout(timeout), + return MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, reqs, + WithMintingTimeout(timeout), ) } diff --git a/itest/multi_asset_group_test.go b/itest/multi_asset_group_test.go index 2d7eae59f..601e8e14d 100644 --- a/itest/multi_asset_group_test.go +++ b/itest/multi_asset_group_test.go @@ -4,9 +4,7 @@ import ( "context" "encoding/hex" "strconv" - "testing" - "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" @@ -39,7 +37,9 @@ func testMintMultiAssetGroups(t *harnessTest) { // The minted batch should contain 7 assets total, and the daemon should // now be aware of 3 asset groups. Each group should have a different // number of assets, and a different total balance. - mintedBatch := mintAssetsConfirmBatch(t, t.tapd, complexBatch) + mintedBatch := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, complexBatch, + ) // Once the batch is minted, we can verify that all asset groups were // created correctly. We begin by verifying the number of asset groups. @@ -122,8 +122,9 @@ func testMintMultiAssetGroups(t *harnessTest) { require.NoError(t.t, err) normalGroupSend := sendAssetsToAddr(t, t.tapd, bobNormalAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, normalGroupSend, normalMember.AssetGenesis.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, normalGroupSend, + normalMember.AssetGenesis.AssetId, []uint64{0, normalMember.Amount}, 0, 1, ) _ = sendProof( @@ -162,8 +163,9 @@ func testMintMultiAssetGroups(t *harnessTest) { require.NoError(t.t, err) collectGroupSend := sendAssetsToAddr(t, t.tapd, bobCollectAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, collectGroupSend, collectMember.AssetGenesis.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, collectGroupSend, + collectMember.AssetGenesis.AssetId, []uint64{0, collectMember.Amount}, 1, 2, ) sendProof( @@ -177,28 +179,6 @@ func testMintMultiAssetGroups(t *harnessTest) { ) } -// VerifyGroupAnchor verifies 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. @@ -280,7 +260,9 @@ func testMintMultiAssetGroupErrors(t *harnessTest) { multiAssetGroup := []*mintrpc.MintAssetRequest{validAnchor, groupedAsset} // The assets should be minted into the same group. - rpcGroupedAssets := mintAssetsConfirmBatch(t, t.tapd, multiAssetGroup) + rpcGroupedAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, multiAssetGroup, + ) AssertNumGroups(t.t, t.tapd, 1) groupKey := rpcGroupedAssets[0].AssetGroup.TweakedGroupKey groupKeyHex := hex.EncodeToString(groupKey) @@ -288,17 +270,3 @@ func testMintMultiAssetGroupErrors(t *harnessTest) { validAnchor.Asset.Amount AssertBalanceByGroup(t.t, t.tapd, groupKeyHex, expectedGroupBalance) } - -func parseGenInfo(t *testing.T, genInfo *taprpc.GenesisInfo) *asset.Genesis { - genPoint, err := parseOutPoint(genInfo.GenesisPoint) - require.NoError(t, err) - - parsedGenesis := asset.Genesis{ - FirstPrevOut: *genPoint, - Tag: genInfo.Name, - OutputIndex: genInfo.OutputIndex, - } - copy(parsedGenesis.MetaHash[:], genInfo.MetaHash) - - return &parsedGenesis -} diff --git a/itest/psbt_test.go b/itest/psbt_test.go index 13079a7ce..6cc7313b5 100644 --- a/itest/psbt_test.go +++ b/itest/psbt_test.go @@ -28,8 +28,9 @@ import ( func testPsbtScriptHashLockSend(t *harnessTest) { // First, we'll make a normal asset with enough units to allow us to // send it around a few times. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) mintedAsset := rpcAssets[0] @@ -83,7 +84,7 @@ func testPsbtScriptHashLockSend(t *harnessTest) { Amt: numUnits / 2, }) require.NoError(t.t, err) - assertAddrCreated(t.t, alice, rpcAssets[0], aliceAddr) + AssertAddrCreated(t.t, alice, rpcAssets[0], aliceAddr) fundResp := fundAddressSendPacket(t, bob, aliceAddr) t.Logf("Funded PSBT: %v", @@ -127,9 +128,9 @@ func testPsbtScriptHashLockSend(t *harnessTest) { ) require.NoError(t.t, err) - confirmAndAssertOutboundTransfer( - t, bob, sendResp, genInfo.AssetId, - []uint64{numUnits / 2, numUnits / 2}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, bob, sendResp, + genInfo.AssetId, []uint64{numUnits / 2, numUnits / 2}, 0, 1, ) _ = sendProof(t, bob, alice, aliceAddr.ScriptKey, genInfo) AssertNonInteractiveRecvComplete(t.t, alice, 1) @@ -149,8 +150,9 @@ func testPsbtScriptHashLockSend(t *harnessTest) { func testPsbtScriptCheckSigSend(t *harnessTest) { // First, we'll make a normal asset with enough units to allow us to // send it around a few times. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{issuableAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{issuableAssets[0]}, ) mintedAsset := rpcAssets[0] @@ -205,7 +207,7 @@ func testPsbtScriptCheckSigSend(t *harnessTest) { Amt: numUnits / 2, }) require.NoError(t.t, err) - assertAddrCreated(t.t, alice, rpcAssets[0], aliceAddr) + AssertAddrCreated(t.t, alice, rpcAssets[0], aliceAddr) fundResp := fundAddressSendPacket(t, bob, aliceAddr) t.Logf("Funded PSBT: %v", @@ -251,9 +253,9 @@ func testPsbtScriptCheckSigSend(t *harnessTest) { ) require.NoError(t.t, err) - confirmAndAssertOutboundTransfer( - t, bob, sendResp, genInfo.AssetId, - []uint64{numUnits / 2, numUnits / 2}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, bob, sendResp, + genInfo.AssetId, []uint64{numUnits / 2, numUnits / 2}, 0, 1, ) _ = sendProof(t, bob, alice, aliceAddr.ScriptKey, genInfo) AssertNonInteractiveRecvComplete(t.t, alice, 1) @@ -275,8 +277,9 @@ func testPsbtNormalInteractiveFullValueSend(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. We're also minting a passive asset that // should remain where it is. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], // Our "passive" asset. { @@ -325,8 +328,9 @@ func testPsbtGroupedInteractiveFullValueSend(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. We're also minting a passive asset that // should remain where it is. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ issuableAssets[0], // Our "passive" asset. { @@ -427,9 +431,10 @@ func runPsbtInteractiveFullValueSendTest(ctxt context.Context, t *harnessTest, numOutputs = 2 amounts = []uint64{fullAmt, 0} } - confirmAndAssetOutboundTransferWithOutputs( - t, sender, sendResp, genInfo.AssetId, amounts, - i/2, (i/2)+1, numOutputs, + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, sender, + sendResp, genInfo.AssetId, amounts, i/2, (i/2)+1, + numOutputs, ) _ = sendProof( t, sender, receiver, @@ -457,10 +462,10 @@ func runPsbtInteractiveFullValueSendTest(ctxt context.Context, t *harnessTest, ) require.NoError(t.t, err) require.Len(t.t, receiverAssets.Assets, numReceiverAssets) - receivedAssets := groupAssetsByName(receiverAssets.Assets) + receivedAssets := GroupAssetsByName(receiverAssets.Assets) AssertAssetState( t.t, receivedAssets, genInfo.Name, genInfo.MetaHash, - assetAmountCheck(fullAmt), + AssetAmountCheck(fullAmt), ) } @@ -479,8 +484,9 @@ func testPsbtNormalInteractiveSplitSend(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. We're also minting a passive asset that // should remain where it is. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], // Our "passive" asset. { @@ -529,8 +535,9 @@ func testPsbtGroupedInteractiveSplitSend(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. We're also minting a passive asset that // should remain where it is. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ issuableAssets[0], // Our "passive" asset. { @@ -641,8 +648,9 @@ func runPsbtInteractiveSplitSendTest(ctxt context.Context, t *harnessTest, require.NoError(t.t, err) numOutputs := 2 - confirmAndAssetOutboundTransferWithOutputs( - t, sender, sendResp, genInfo.AssetId, + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, sender, + sendResp, genInfo.AssetId, []uint64{sendAmt, changeAmt}, i/2, (i/2)+1, numOutputs, ) @@ -695,8 +703,9 @@ func runPsbtInteractiveSplitSendTest(ctxt context.Context, t *harnessTest, func testPsbtInteractiveTapscriptSibling(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -764,9 +773,9 @@ func testPsbtInteractiveTapscriptSibling(t *harnessTest) { ) require.NoError(t.t, err) - confirmAndAssetOutboundTransferWithOutputs( - t, alice, sendResp, genInfo.AssetId, - []uint64{sendAmt, changeAmt}, 0, 1, 2, + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, alice, sendResp, + genInfo.AssetId, []uint64{sendAmt, changeAmt}, 0, 1, 2, ) _ = sendProof( t, alice, bob, @@ -806,8 +815,9 @@ func testPsbtMultiSend(t *harnessTest) { // First, we'll make a normal asset with a bunch of units that we are // going to send backand forth. We're also minting a passive asset that // should remain where it is. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{ + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{ simpleAssets[0], // Our "passive" asset. { @@ -911,9 +921,9 @@ func testPsbtMultiSend(t *harnessTest) { // addresses (sharing an anchor output) and 1 for the change. So there // are 4 BTC anchor outputs but 5 asset transfer outputs. numOutputs := 5 - confirmAndAssetOutboundTransferWithOutputs( - t, sender, sendResp, genInfo.AssetId, outputAmounts, 0, 1, - numOutputs, + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, sender, sendResp, + genInfo.AssetId, outputAmounts, 0, 1, numOutputs, ) _ = sendProof( t, sender, receiver, @@ -1009,16 +1019,16 @@ func sendToTapscriptAddr(ctx context.Context, t *harnessTest, alice, InternalKey: lndKeyDescToTap(bobInternalKey), }) require.NoError(t.t, err) - assertAddrCreated(t.t, bob, mintedAsset, bobAddr) + AssertAddrCreated(t.t, bob, mintedAsset, bobAddr) // Send the asset to Bob using the script key with an actual script // tree. sendResp := sendAssetsToAddr(t, alice, bobAddr) changeUnits := mintedAsset.Amount - numUnits - confirmAndAssertOutboundTransfer( - t, alice, sendResp, genInfo.AssetId, - []uint64{changeUnits, numUnits}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, alice, sendResp, + genInfo.AssetId, []uint64{changeUnits, numUnits}, 0, 1, ) _ = sendProof(t, alice, bob, bobAddr.ScriptKey, genInfo) AssertNonInteractiveRecvComplete(t.t, bob, 1) @@ -1038,11 +1048,12 @@ func sendAssetAndAssert(ctx context.Context, t *harnessTest, alice, }) require.NoError(t.t, err) - assertAddrCreated(t.t, bob, mintedAsset, bobAddr) + AssertAddrCreated(t.t, bob, mintedAsset, bobAddr) sendResp := sendAssetsToAddr(t, alice, bobAddr) - confirmAndAssertOutboundTransfer( - t, alice, sendResp, genInfo.AssetId, - []uint64{change, numUnits}, outTransferIdx, numOutTransfers, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, alice, sendResp, + genInfo.AssetId, []uint64{change, numUnits}, outTransferIdx, + numOutTransfers, ) _ = sendProof(t, alice, bob, bobAddr.ScriptKey, genInfo) diff --git a/itest/re-issuance_test.go b/itest/re-issuance_test.go index ffc3778f6..4649b6f90 100644 --- a/itest/re-issuance_test.go +++ b/itest/re-issuance_test.go @@ -14,13 +14,17 @@ import ( // testReIssuance tests that we can properly reissue an asset into group, and // that the daemon handles a group with multiple assets correctly. func testReIssuance(t *harnessTest) { + miner := t.lndHarness.Miner.Client + // First, we'll mint a collectible and a normal asset, both with // emission enabled. - normalGroupGen := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{issuableAssets[0]}, + normalGroupGen := MintAssetsConfirmBatch( + t.t, miner, t.tapd, + []*mintrpc.MintAssetRequest{issuableAssets[0]}, ) - collectGroupGen := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{issuableAssets[1]}, + collectGroupGen := MintAssetsConfirmBatch( + t.t, miner, t.tapd, + []*mintrpc.MintAssetRequest{issuableAssets[1]}, ) require.Equal(t.t, 1, len(normalGroupGen)) require.Equal(t.t, 1, len(collectGroupGen)) @@ -70,9 +74,9 @@ func testReIssuance(t *harnessTest) { require.NoError(t.t, err) firstCollectSend := sendAssetsToAddr(t, t.tapd, collectGroupAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, firstCollectSend, collectGenInfo.AssetId, - []uint64{0, 1}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, firstCollectSend, + collectGenInfo.AssetId, []uint64{0, 1}, 0, 1, ) sendProof( t, t.tapd, secondTapd, collectGroupAddr.ScriptKey, @@ -98,8 +102,9 @@ func testReIssuance(t *harnessTest) { require.NoError(t.t, err) firstNormalSend := sendAssetsToAddr(t, t.tapd, normalGroupAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, firstNormalSend, normalGenInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, firstNormalSend, + normalGenInfo.AssetId, []uint64{normalGroupMintHalf, normalGroupMintHalf}, 1, 2, ) sendProof( @@ -115,11 +120,13 @@ func testReIssuance(t *harnessTest) { reissuedAssets[0].Asset.GroupKey = normalGroupKey reissuedAssets[1].Asset.GroupKey = collectGroupKey - normalReissueGen := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{reissuedAssets[0]}, + normalReissueGen := MintAssetsConfirmBatch( + t.t, miner, t.tapd, + []*mintrpc.MintAssetRequest{reissuedAssets[0]}, ) - collectReissueGen := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{reissuedAssets[1]}, + collectReissueGen := MintAssetsConfirmBatch( + t.t, miner, t.tapd, + []*mintrpc.MintAssetRequest{reissuedAssets[1]}, ) require.Equal(t.t, 1, len(normalReissueGen)) require.Equal(t.t, 1, len(collectReissueGen)) @@ -173,8 +180,8 @@ func testReIssuance(t *harnessTest) { require.NoError(t.t, err) secondCollectSend := sendAssetsToAddr(t, t.tapd, collectReissueAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, secondCollectSend, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, secondCollectSend, collectReissueInfo.AssetId, []uint64{0, 1}, 2, 3, ) sendProof( @@ -207,9 +214,9 @@ func testReIssuance(t *harnessTest) { require.NoError(t.t, err) thirdCollectSend := sendAssetsToAddr(t, secondTapd, collectGenAddr) - confirmAndAssertOutboundTransfer( - t, secondTapd, thirdCollectSend, - collectGenInfo.AssetId, []uint64{0, 1}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, secondTapd.ht.lndHarness.Miner.Client, secondTapd, + thirdCollectSend, collectGenInfo.AssetId, []uint64{0, 1}, 0, 1, ) sendProof( t, secondTapd, t.tapd, collectReissueAddr.ScriptKey, @@ -237,8 +244,9 @@ func testReIssuanceAmountOverflow(t *harnessTest) { assetIssueReq.EnableEmission = true assetIssueReq.Asset.Amount = math.MaxUint64 - assets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{assetIssueReq}, + assets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{assetIssueReq}, ) require.Equal(t.t, 1, len(assets)) @@ -268,8 +276,9 @@ func testReIssuanceAmountOverflow(t *harnessTest) { // that incorrectly try to specify a group for reissuance. func testMintWithGroupKeyErrors(t *harnessTest) { // First, mint a collectible with emission enabled to create one group. - collectGroupGen := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{issuableAssets[1]}, + collectGroupGen := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{issuableAssets[1]}, ) require.Equal(t.t, 1, len(collectGroupGen)) @@ -338,9 +347,9 @@ func testMintWithGroupKeyErrors(t *harnessTest) { require.NoError(t.t, err) collectSend := sendAssetsToAddr(t, t.tapd, collectGroupAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, collectSend, collectGenInfo.AssetId, - []uint64{0, 1}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, collectSend, + collectGenInfo.AssetId, []uint64{0, 1}, 0, 1, ) sendProof( t, t.tapd, secondTapd, collectGroupAddr.ScriptKey, diff --git a/itest/re-org_test.go b/itest/re-org_test.go index 61264a076..b77762f5a 100644 --- a/itest/re-org_test.go +++ b/itest/re-org_test.go @@ -19,7 +19,9 @@ func testReOrgMint(t *harnessTest) { mintRequests := []*mintrpc.MintAssetRequest{ issuableAssets[0], issuableAssets[1], } - mintTXID, batchKey := mintAssetUnconfirmed(t, t.tapd, mintRequests) + mintTXID, batchKey := MintAssetUnconfirmed( + t.t, t.lndHarness.Miner.Client, t.tapd, mintRequests, + ) ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) @@ -31,7 +33,7 @@ func testReOrgMint(t *harnessTest) { miner := t.lndHarness.Miner // And now we mine a block to confirm the assets. - initialBlock := mineBlocks(t, t.lndHarness, 1, 1)[0] + initialBlock := MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 1)[0] initialBlockHash := initialBlock.BlockHash() WaitForBatchState( t.t, ctxt, t.tapd, defaultWaitTimeout, batchKey, @@ -42,8 +44,8 @@ func testReOrgMint(t *harnessTest) { miner.AssertTxInBlock(initialBlock, &mintTXID) t.Logf("Mint TX %v mined in block %v", mintTXID, initialBlockHash) - assetList := assertAssetsMinted( - t, t.tapd, mintRequests, mintTXID, initialBlockHash, + assetList := AssertAssetsMinted( + t.t, t.tapd, mintRequests, mintTXID, initialBlockHash, ) // Now that we have the asset created, we'll make a new node that'll @@ -73,7 +75,7 @@ func testReOrgMint(t *harnessTest) { // was re-organized out. for idx := range assetList { a := assetList[idx] - assertAssetProofsInvalid(t.t, t.tapd, a) + AssertAssetProofsInvalid(t.t, t.tapd, a) } // Cleanup by mining the minting tx again. @@ -124,7 +126,9 @@ func testReOrgSend(t *harnessTest) { mintRequests := []*mintrpc.MintAssetRequest{ issuableAssets[0], issuableAssets[1], } - assetList := mintAssetsConfirmBatch(t, t.tapd, mintRequests) + assetList := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, mintRequests, + ) ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) @@ -159,10 +163,11 @@ func testReOrgSend(t *harnessTest) { Amt: sendAmount, }) require.NoError(t.t, err) - assertAddrCreated(t.t, secondTapd, sendAsset, bobAddr) + AssertAddrCreated(t.t, secondTapd, sendAsset, bobAddr) sendResp := sendAssetsToAddr(t, t.tapd, bobAddr) - initialBlock := confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, sendAssetGen.AssetId, + initialBlock := ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + sendAssetGen.AssetId, []uint64{sendAsset.Amount - sendAmount, sendAmount}, 0, 1, ) _ = sendProof( @@ -196,11 +201,11 @@ func testReOrgSend(t *harnessTest) { for idx := range aliceAssets.Assets { a := aliceAssets.Assets[idx] - assertAssetProofsInvalid(t.t, t.tapd, a) + AssertAssetProofsInvalid(t.t, t.tapd, a) } for idx := range bobAssets.Assets { a := bobAssets.Assets[idx] - assertAssetProofsInvalid(t.t, secondTapd, a) + AssertAssetProofsInvalid(t.t, secondTapd, a) } // Cleanup by mining the minting tx again. @@ -251,7 +256,9 @@ func testReOrgMintAndSend(t *harnessTest) { mintRequests := []*mintrpc.MintAssetRequest{ issuableAssets[0], issuableAssets[1], } - assetList := mintAssetsConfirmBatch(t, t.tapd, mintRequests) + assetList := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, mintRequests, + ) // Now that we have the asset created, we'll make a new node that'll // serve as the node which'll receive the assets. The existing tapd @@ -277,10 +284,11 @@ func testReOrgMintAndSend(t *harnessTest) { Amt: sendAmount, }) require.NoError(t.t, err) - assertAddrCreated(t.t, secondTapd, sendAsset, bobAddr) + AssertAddrCreated(t.t, secondTapd, sendAsset, bobAddr) sendResp := sendAssetsToAddr(t, t.tapd, bobAddr) - initialBlock := confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, sendAssetGen.AssetId, + initialBlock := ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + sendAssetGen.AssetId, []uint64{sendAsset.Amount - sendAmount, sendAmount}, 0, 1, ) _ = sendProof( @@ -314,11 +322,11 @@ func testReOrgMintAndSend(t *harnessTest) { for idx := range aliceAssets.Assets { a := aliceAssets.Assets[idx] - assertAssetProofsInvalid(t.t, t.tapd, a) + AssertAssetProofsInvalid(t.t, t.tapd, a) } for idx := range bobAssets.Assets { a := bobAssets.Assets[idx] - assertAssetProofsInvalid(t.t, secondTapd, a) + AssertAssetProofsInvalid(t.t, secondTapd, a) } // We now also stop Bob to make sure he can still detect the re-org and diff --git a/itest/round_trip_send_test.go b/itest/round_trip_send_test.go index eb4686c9d..784ca1571 100644 --- a/itest/round_trip_send_test.go +++ b/itest/round_trip_send_test.go @@ -4,14 +4,10 @@ import ( "bytes" "context" "encoding/hex" - "fmt" - "strconv" - "strings" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/taproot-assets/commitment" @@ -33,8 +29,9 @@ import ( func testRoundTripSend(t *harnessTest) { // First, we'll make a normal assets with enough units to allow us to // send it around a few times. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -74,15 +71,15 @@ func testRoundTripSend(t *harnessTest) { }) require.NoError(t.t, err) - assertAddrCreated(t.t, secondTapd, rpcAssets[0], bobAddr) + AssertAddrCreated(t.t, secondTapd, rpcAssets[0], bobAddr) sendResp := sendAssetsToAddr(t, t.tapd, bobAddr) sendRespJSON, err := formatProtoJSON(sendResp) require.NoError(t.t, err) t.Logf("Got response from sending assets: %v", sendRespJSON) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, - []uint64{bobAmt, bobAmt}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{bobAmt, bobAmt}, 0, 1, ) _ = sendProof(t, t.tapd, secondTapd, bobAddr.ScriptKey, genInfo) @@ -94,15 +91,15 @@ func testRoundTripSend(t *harnessTest) { }) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, rpcAssets[0], aliceAddr) + AssertAddrCreated(t.t, t.tapd, rpcAssets[0], aliceAddr) sendResp = sendAssetsToAddr(t, secondTapd, aliceAddr) sendRespJSON, err = formatProtoJSON(sendResp) require.NoError(t.t, err) t.Logf("Got response from sending assets: %v", sendRespJSON) - confirmAndAssertOutboundTransfer( - t, secondTapd, sendResp, genInfo.AssetId, - []uint64{aliceAmt, aliceAmt}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, secondTapd, + sendResp, genInfo.AssetId, []uint64{aliceAmt, aliceAmt}, 0, 1, ) _ = sendProof(t, secondTapd, t.tapd, aliceAddr.ScriptKey, genInfo) @@ -138,7 +135,7 @@ func testRoundTripSend(t *harnessTest) { // recipient's output is the second one. bobToAliceOutput := transferResp.Transfers[0].Outputs[1] bobToAliceAnchor := bobToAliceOutput.Anchor - outpoint, err := parseOutPoint(bobToAliceAnchor.Outpoint) + outpoint, err := ParseOutPoint(bobToAliceAnchor.Outpoint) require.NoError(t.t, err) internalKey, err := btcec.ParsePubKey(bobToAliceAnchor.InternalKey) @@ -237,26 +234,3 @@ func newAddrWithScript(ht *lntest.HarnessTest, node *node.HarnessNode, return p2wkhAddr, p2wkhPkScript } - -func parseOutPoint(s string) (*wire.OutPoint, error) { - split := strings.Split(s, ":") - if len(split) != 2 { - return nil, fmt.Errorf("expecting outpoint to be in format of: " + - "txid:index") - } - - index, err := strconv.ParseInt(split[1], 10, 32) - if err != nil { - return nil, fmt.Errorf("unable to decode output index: %v", err) - } - - txid, err := chainhash.NewHashFromStr(split[0]) - if err != nil { - return nil, fmt.Errorf("unable to parse hex string: %v", err) - } - - return &wire.OutPoint{ - Hash: *txid, - Index: uint32(index), - }, nil -} diff --git a/itest/send_test.go b/itest/send_test.go index 3513486d1..30ba26709 100644 --- a/itest/send_test.go +++ b/itest/send_test.go @@ -75,8 +75,9 @@ func testBasicSendUnidirectional(t *harnessTest) { // First, we'll make a normal assets with enough units to allow us to // send it around a few times. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -111,12 +112,13 @@ func testBasicSendUnidirectional(t *harnessTest) { // units. currentUnits -= numUnits - assertAddrCreated(t.t, secondTapd, rpcAssets[0], bobAddr) + AssertAddrCreated(t.t, secondTapd, rpcAssets[0], bobAddr) sendResp := sendAssetsToAddr(t, t.tapd, bobAddr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{currentUnits, numUnits}, i, i+1, ) AssertNonInteractiveRecvComplete(t.t, secondTapd, i+1) @@ -151,8 +153,9 @@ func testResumePendingPackageSend(t *harnessTest) { ) // Mint (and mine) an asset for sending. - rpcAssets := mintAssetsConfirmBatch( - t, sendTapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, sendTapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -167,7 +170,7 @@ func testResumePendingPackageSend(t *harnessTest) { Amt: 10, }) require.NoError(t.t, err) - assertAddrCreated(t.t, recvTapd, rpcAssets[0], recvAddr) + AssertAddrCreated(t.t, recvTapd, rpcAssets[0], recvAddr) // We will now start two asset send events in sequence. We will stop and // restart the sending node during each send. During one sending event @@ -243,7 +246,9 @@ func testBasicSendPassiveAsset(t *harnessTest) { }, }, } - rpcAssets := mintAssetsConfirmBatch(t, t.tapd, assets) + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, assets, + ) firstAsset := rpcAssets[0] genInfo := firstAsset.AssetGenesis secondAsset := rpcAssets[1] @@ -279,7 +284,7 @@ func testBasicSendPassiveAsset(t *harnessTest) { Amt: numUnitsSend, }) require.NoError(t.t, err) - assertAddrCreated(t.t, recvTapd, firstAsset, recvAddr) + AssertAddrCreated(t.t, recvTapd, firstAsset, recvAddr) // Send the assets to the receiving node. sendResp := sendAssetsToAddr(t, t.tapd, recvAddr) @@ -296,23 +301,25 @@ func testBasicSendPassiveAsset(t *harnessTest) { // Assert that the outbound transfer was confirmed. expectedAmtAfterSend := assets[0].Asset.Amount - numUnitsSend - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{expectedAmtAfterSend, numUnitsSend}, 0, 1, ) AssertNonInteractiveRecvComplete(t.t, recvTapd, 1) // Assert that the sending node returns the correct asset list via RPC. - assertListAssets(t.t, ctxb, t.tapd, []MatchRpcAsset{ - func(asset *taprpc.Asset) bool { - return asset.Amount == 300 && - asset.AssetGenesis.Name == "first-itestbuxx" - }, - func(asset *taprpc.Asset) bool { - return asset.Amount == 2000 && - asset.AssetGenesis.Name == "second-itestbuxx" - }, - }) + AssertListAssets( + t.t, ctxb, t.tapd, []MatchRpcAsset{ + func(asset *taprpc.Asset) bool { + return asset.Amount == 300 && + asset.AssetGenesis.Name == "first-itestbuxx" + }, + func(asset *taprpc.Asset) bool { + return asset.Amount == 2000 && + asset.AssetGenesis.Name == "second-itestbuxx" + }, + }) t.Logf("First send complete, now attempting to send passive asset") @@ -322,15 +329,17 @@ func testBasicSendPassiveAsset(t *harnessTest) { Amt: numUnitsSend, }) require.NoError(t.t, err) - assertAddrCreated(t.t, recvTapd, secondAsset, recvAddr) + AssertAddrCreated(t.t, recvTapd, secondAsset, recvAddr) // Send the assets to the receiving node. sendResp = sendAssetsToAddr(t, t.tapd, recvAddr) // Assert that the outbound transfer was confirmed. expectedAmtAfterSend = assets[1].Asset.Amount - numUnitsSend - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo2.AssetId, + + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo2.AssetId, []uint64{expectedAmtAfterSend, numUnitsSend}, 1, 2, ) AssertNonInteractiveRecvComplete(t.t, recvTapd, 2) @@ -342,15 +351,16 @@ func testBasicSendPassiveAsset(t *harnessTest) { Amt: numUnitsSend / 2, }) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, firstAsset, newAddr) + AssertAddrCreated(t.t, t.tapd, firstAsset, newAddr) // Send the assets back to the first node. sendResp = sendAssetsToAddr(t, recvTapd, newAddr) // Assert that the outbound transfer was confirmed. expectedAmtAfterSend = numUnitsSend - numUnitsSend/2 - confirmAndAssertOutboundTransfer( - t, recvTapd, sendResp, genInfo.AssetId, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, recvTapd, sendResp, + genInfo.AssetId, []uint64{expectedAmtAfterSend, numUnitsSend / 2}, 0, 1, ) AssertNonInteractiveRecvComplete(t.t, t.tapd, 1) @@ -451,8 +461,9 @@ func testReattemptFailedAssetSend(t *harnessTest) { }() // Mint an asset for sending. - rpcAssets := mintAssetsConfirmBatch( - t, sendTapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, sendTapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -467,7 +478,7 @@ func testReattemptFailedAssetSend(t *harnessTest) { Amt: 10, }) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, rpcAssets[0], recvAddr) + AssertAddrCreated(t.t, t.tapd, rpcAssets[0], recvAddr) // Simulate a failed attempt at sending the asset proof by stopping // the receiver node. @@ -475,7 +486,7 @@ func testReattemptFailedAssetSend(t *harnessTest) { // Send asset and then mine to confirm the associated on-chain tx. sendAssetsToAddr(t, sendTapd, recvAddr) - _ = mineBlocks(t, t.lndHarness, 1, 1) + _ = MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 1) wg.Wait() } @@ -552,8 +563,9 @@ func testOfflineReceiverEventuallyReceives(t *harnessTest) { }() // Mint an asset for sending. - rpcAssets := mintAssetsConfirmBatch( - t, sendTapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, sendTapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -568,7 +580,7 @@ func testOfflineReceiverEventuallyReceives(t *harnessTest) { Amt: 10, }) require.NoError(t.t, err) - assertAddrCreated(t.t, recvTapd, rpcAssets[0], recvAddr) + AssertAddrCreated(t.t, recvTapd, rpcAssets[0], recvAddr) // Stop receiving tapd node to simulate offline receiver. t.Logf("Stopping receiving taproot assets node") @@ -576,7 +588,7 @@ func testOfflineReceiverEventuallyReceives(t *harnessTest) { // Send asset and then mine to confirm the associated on-chain tx. sendAssetsToAddr(t, sendTapd, recvAddr) - _ = mineBlocks(t, t.lndHarness, 1, 1) + _ = MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 1) // Pause before restarting receiving tapd node so that sender node has // an opportunity to attempt to send the proof multiple times. @@ -651,8 +663,9 @@ func testMultiInputSendNonInteractiveSingleID(t *harnessTest) { ctxb := context.Background() // Mint a single asset. - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) rpcAsset := rpcAssets[0] @@ -678,14 +691,14 @@ func testMultiInputSendNonInteractiveSingleID(t *harnessTest) { }, ) require.NoError(t.t, err) - assertAddrCreated(t.t, bobTapd, rpcAsset, addr) + AssertAddrCreated(t.t, bobTapd, rpcAsset, addr) // Send the assets to the secondary node. sendResp := sendAssetsToAddr(t, t.tapd, addr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, []uint64{4000, 1000}, - 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{4000, 1000}, 0, 1, ) _ = sendProof(t, t.tapd, bobTapd, addr.ScriptKey, genInfo) @@ -699,13 +712,14 @@ func testMultiInputSendNonInteractiveSingleID(t *harnessTest) { }, ) require.NoError(t.t, err) - assertAddrCreated(t.t, bobTapd, rpcAsset, addr) + AssertAddrCreated(t.t, bobTapd, rpcAsset, addr) // Send the assets to the secondary node. sendResp = sendAssetsToAddr(t, t.tapd, addr) - confirmAndAssertOutboundTransfer( - t, t.tapd, sendResp, genInfo.AssetId, []uint64{0, 4000}, 1, 2, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{0, 4000}, 1, 2, ) _ = sendProof(t, t.tapd, bobTapd, addr.ScriptKey, genInfo) @@ -722,13 +736,14 @@ func testMultiInputSendNonInteractiveSingleID(t *harnessTest) { }, ) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, rpcAsset, addr) + AssertAddrCreated(t.t, t.tapd, rpcAsset, addr) // Send the assets to the minting node. sendResp = sendAssetsToAddr(t, bobTapd, addr) - confirmAndAssertOutboundTransfer( - t, bobTapd, sendResp, genInfo.AssetId, []uint64{0, 5000}, 0, 1, + ConfirmAndAssertOutboundTransfer( + t.t, t.lndHarness.Miner.Client, bobTapd, sendResp, + genInfo.AssetId, []uint64{0, 5000}, 0, 1, ) _ = sendProof(t, bobTapd, t.tapd, addr.ScriptKey, genInfo) @@ -742,8 +757,9 @@ func testSendMultipleCoins(t *harnessTest) { // First, we'll make a normal assets with enough units to allow us to // send it to different UTXOs - rpcAssets := mintAssetsConfirmBatch( - t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]}, + rpcAssets := MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, t.tapd, + []*mintrpc.MintAssetRequest{simpleAssets[0]}, ) genInfo := rpcAssets[0].AssetGenesis @@ -775,15 +791,16 @@ func testSendMultipleCoins(t *harnessTest) { }) require.NoError(t.t, err) - assertAddrCreated(t.t, t.tapd, rpcAssets[0], newAddr) + AssertAddrCreated(t.t, t.tapd, rpcAssets[0], newAddr) addrs[i] = newAddr } // We created 5 addresses in our first node now, so we can initiate the // transfer to send the coins back to our wallet in 5 pieces now. sendResp := sendAssetsToAddr(t, t.tapd, addrs...) - confirmAndAssetOutboundTransferWithOutputs( - t, t.tapd, sendResp, genInfo.AssetId, []uint64{ + ConfirmAndAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, t.tapd, sendResp, + genInfo.AssetId, []uint64{ 0, unitsPerPart, unitsPerPart, unitsPerPart, unitsPerPart, unitsPerPart, }, 0, 1, numParts+1, @@ -804,9 +821,11 @@ func testSendMultipleCoins(t *harnessTest) { require.NoError(t.t, err) sendResp := sendAssetsToAddr(t, t.tapd, bobAddrs[i]) - assertAssetOutboundTransferWithOutputs( - t, t.tapd, sendResp.Transfer, genInfo.AssetId, - []uint64{0, unitsPerPart}, i+1, i+2, 2, false, + AssertAssetOutboundTransferWithOutputs( + t.t, t.lndHarness.Miner.Client, t.tapd, + sendResp.Transfer, genInfo.AssetId, + []uint64{0, unitsPerPart}, i+1, i+2, + 2, false, ) } @@ -829,7 +848,7 @@ func testSendMultipleCoins(t *harnessTest) { // Now we confirm the 5 transfers and make sure they complete as // expected. - _ = mineBlocks(t, t.lndHarness, 1, 5) + _ = MineBlocks(t.t, t.lndHarness.Miner.Client, 1, 5) for _, addr := range bobAddrs { _ = sendProof(t, t.tapd, secondTapd, addr.ScriptKey, genInfo) } diff --git a/itest/test_harness.go b/itest/test_harness.go index 18605cd6d..94cb2c722 100644 --- a/itest/test_harness.go +++ b/itest/test_harness.go @@ -1,7 +1,6 @@ package itest import ( - "bytes" "context" "flag" "fmt" @@ -13,7 +12,6 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightninglabs/aperture" "github.com/lightninglabs/lndclient" @@ -448,66 +446,6 @@ func waitForNTxsInMempool(miner *rpcclient.Client, n int, } } -// assertTxInBlock checks that a given transaction can be found in the block's -// transaction list. -func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, - txid *chainhash.Hash) *wire.MsgTx { - - for _, tx := range block.Transactions { - sha := tx.TxHash() - if bytes.Equal(txid[:], sha[:]) { - return tx - } - } - - t.Fatalf("tx was not included in block") - return nil -} - -// mineBlocks mine 'num' of blocks and check that blocks are present in -// node blockchain. numTxs should be set to the number of transactions -// (excluding the coinbase) we expect to be included in the first mined block. -func mineBlocks(t *harnessTest, net *lntest.HarnessTest, - num uint32, numTxs int) []*wire.MsgBlock { - - // If we expect transactions to be included in the blocks we'll mine, - // we wait here until they are seen in the miner's mempool. - var txids []*chainhash.Hash - var err error - if numTxs > 0 { - txids, err = waitForNTxsInMempool( - net.Miner.Client, numTxs, minerMempoolTimeout, - ) - if err != nil { - t.Fatalf("unable to find txns in mempool: %v", err) - } - } - - blocks := make([]*wire.MsgBlock, num) - - blockHashes, err := net.Miner.Client.Generate(num) - if err != nil { - t.Fatalf("unable to generate blocks: %v", err) - } - - for i, blockHash := range blockHashes { - block, err := net.Miner.Client.GetBlock(blockHash) - if err != nil { - t.Fatalf("unable to get block: %v", err) - } - - blocks[i] = block - } - - // Finally, assert that all the transactions were included in the first - // block. - for _, txid := range txids { - assertTxInBlock(t, blocks[0], txid) - } - - return blocks -} - // shutdownAndAssert shuts down the given node and asserts that no errors // occur. func shutdownAndAssert(t *harnessTest, node *node.HarnessNode, diff --git a/itest/universe_test.go b/itest/universe_test.go index a923912e5..2c3408824 100644 --- a/itest/universe_test.go +++ b/itest/universe_test.go @@ -25,10 +25,15 @@ import ( // testUniverseSync tests that we're able to properly sync the universe state // between two nodes. func testUniverseSync(t *harnessTest) { + miner := t.lndHarness.Miner.Client // First, we'll create out usual set of simple and also issuable // assets. - rpcSimpleAssets := mintAssetsConfirmBatch(t, t.tapd, simpleAssets) - rpcIssuableAssets := mintAssetsConfirmBatch(t, t.tapd, issuableAssets) + rpcSimpleAssets := MintAssetsConfirmBatch( + t.t, miner, t.tapd, simpleAssets, + ) + rpcIssuableAssets := MintAssetsConfirmBatch( + t.t, miner, t.tapd, issuableAssets, + ) // With those assets created, we'll now create a new node that we'll // use to exercise the Universe sync. @@ -256,9 +261,14 @@ func unmarshalMerkleSumNode(root *unirpc.MerkleSumNode) mssmt.Node { // testUniverseREST tests that we're able to properly query the universe state // via the REST interface. func testUniverseREST(t *harnessTest) { + miner := t.lndHarness.Miner.Client // Mint a few assets that we then want to inspect in the universe. - rpcSimpleAssets := mintAssetsConfirmBatch(t, t.tapd, simpleAssets) - rpcIssuableAssets := mintAssetsConfirmBatch(t, t.tapd, issuableAssets) + rpcSimpleAssets := MintAssetsConfirmBatch( + t.t, miner, t.tapd, simpleAssets, + ) + rpcIssuableAssets := MintAssetsConfirmBatch( + t.t, miner, t.tapd, issuableAssets, + ) urlPrefix := fmt.Sprintf("https://%s/v1/taproot-assets/universe", t.tapd.clientCfg.RpcConf.RawRESTListeners[0]) @@ -361,8 +371,10 @@ func testUniverseFederation(t *harnessTest) { ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) defer cancel() + miner := t.lndHarness.Miner.Client + // Now that Bob is active, we'll make a set of assets with the main node. - firstAsset := mintAssetsConfirmBatch(t, t.tapd, simpleAssets[:1]) + firstAsset := MintAssetsConfirmBatch(t.t, miner, t.tapd, simpleAssets[:1]) require.Len(t.t, firstAsset, 1) // Make sure we can't add ourselves to the universe. @@ -424,9 +436,11 @@ func testUniverseFederation(t *harnessTest) { // We'll now make two new assets with Bob, and ensure that the state is // properly pushed to the main node which is a part of the federation. - newAssets := mintAssetsConfirmBatch(t, bob, []*mintrpc.MintAssetRequest{ - simpleAssets[1], issuableAssets[0], - }) + newAssets := MintAssetsConfirmBatch( + t.t, miner, bob, []*mintrpc.MintAssetRequest{ + simpleAssets[1], issuableAssets[0], + }, + ) // Bob should have two new assets in its local Universe tree. for _, newAsset := range newAssets { diff --git a/itest/utils.go b/itest/utils.go new file mode 100644 index 000000000..16a13a856 --- /dev/null +++ b/itest/utils.go @@ -0,0 +1,244 @@ +package itest + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +var ( + zeroHash chainhash.Hash +) + +// CopyRequest is a helper function to copy a request so that we can modify it. +func CopyRequest(req *mintrpc.MintAssetRequest) *mintrpc.MintAssetRequest { + return proto.Clone(req).(*mintrpc.MintAssetRequest) +} + +// CopyRequests is a helper function to copy a slice of requests so that we can +// modify them. +func CopyRequests( + reqs []*mintrpc.MintAssetRequest) []*mintrpc.MintAssetRequest { + + copied := make([]*mintrpc.MintAssetRequest, len(reqs)) + for idx := range reqs { + copied[idx] = CopyRequest(reqs[idx]) + } + return copied +} + +// ParseOutPoint +func ParseOutPoint(s string) (*wire.OutPoint, error) { + split := strings.Split(s, ":") + if len(split) != 2 { + return nil, fmt.Errorf("expecting outpoint to be in format " + + "of: txid:index") + } + + index, err := strconv.ParseInt(split[1], 10, 32) + if err != nil { + return nil, fmt.Errorf("unable to decode output index: %v", err) + } + + txid, err := chainhash.NewHashFromStr(split[0]) + if err != nil { + return nil, fmt.Errorf("unable to parse hex string: %v", err) + } + + return &wire.OutPoint{ + Hash: *txid, + Index: uint32(index), + }, nil +} + +// ParseGenInfo converts a taprpc.GenesisInfo into its asset.Genesis +// counterpart. +func ParseGenInfo(t *testing.T, genInfo *taprpc.GenesisInfo) *asset.Genesis { + genPoint, err := ParseOutPoint(genInfo.GenesisPoint) + require.NoError(t, err) + + parsedGenesis := asset.Genesis{ + FirstPrevOut: *genPoint, + Tag: genInfo.Name, + OutputIndex: genInfo.OutputIndex, + } + copy(parsedGenesis.MetaHash[:], genInfo.MetaHash) + + return &parsedGenesis +} + +// MineBlocks mine 'num' of blocks and check that blocks are present in +// node blockchain. numTxs should be set to the number of transactions +// (excluding the coinbase) we expect to be included in the first mined block. +func MineBlocks(t *testing.T, client *rpcclient.Client, + num uint32, numTxs int) []*wire.MsgBlock { + + // If we expect transactions to be included in the blocks we'll mine, + // we wait here until they are seen in the miner's mempool. + var txids []*chainhash.Hash + var err error + if numTxs > 0 { + txids, err = waitForNTxsInMempool( + client, numTxs, minerMempoolTimeout, + ) + if err != nil { + t.Fatalf("unable to find txns in mempool: %v", err) + } + } + + blocks := make([]*wire.MsgBlock, num) + + blockHashes, err := client.Generate(num) + if err != nil { + t.Fatalf("unable to generate blocks: %v", err) + } + + for i, blockHash := range blockHashes { + block, err := client.GetBlock(blockHash) + if err != nil { + t.Fatalf("unable to get block: %v", err) + } + + blocks[i] = block + } + + // Finally, assert that all the transactions were included in the first + // block. + for _, txid := range txids { + AssertTxInBlock(t, blocks[0], txid) + } + + return blocks +} + +type MintOption func(*MintOptions) + +type MintOptions struct { + mintingTimeout time.Duration +} + +func DefaultMintOptions() *MintOptions { + return &MintOptions{ + mintingTimeout: defaultWaitTimeout, + } +} + +func WithMintingTimeout(timeout time.Duration) MintOption { + return func(options *MintOptions) { + options.mintingTimeout = timeout + } +} + +// MintAssetUnconfirmed is a helper function that mints a batch of assets and +// waits until the minting transaction is in the mempool but does not mine a +// block. +func MintAssetUnconfirmed(t *testing.T, minerClient *rpcclient.Client, + tapClient TapdClient, assetRequests []*mintrpc.MintAssetRequest, + opts ...MintOption) (chainhash.Hash, []byte) { + + options := DefaultMintOptions() + for _, opt := range opts { + opt(options) + } + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, options.mintingTimeout) + defer cancel() + + // Mint all the assets in the same batch. + for idx, assetRequest := range assetRequests { + assetResp, err := tapClient.MintAsset(ctxt, assetRequest) + require.NoError(t, err) + require.NotEmpty(t, assetResp.PendingBatch) + require.Len(t, assetResp.PendingBatch.Assets, idx+1) + } + + // Instruct the daemon to finalize the batch. + batchResp, err := tapClient.FinalizeBatch( + ctxt, &mintrpc.FinalizeBatchRequest{}, + ) + require.NoError(t, err) + require.NotEmpty(t, batchResp.Batch) + require.Len(t, batchResp.Batch.Assets, len(assetRequests)) + require.Equal( + t, mintrpc.BatchState_BATCH_STATE_BROADCAST, + batchResp.Batch.State, + ) + + WaitForBatchState( + t, ctxt, tapClient, options.mintingTimeout, + batchResp.Batch.BatchKey, + mintrpc.BatchState_BATCH_STATE_BROADCAST, + ) + hashes, err := waitForNTxsInMempool( + minerClient, 1, options.mintingTimeout, + ) + require.NoError(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 := tapClient.ListAssets( + ctxt, &taprpc.ListAssetRequest{}, + ) + require.NoError(t, err) + + unconfirmedAssets := GroupAssetsByName(listRespUnconfirmed.Assets) + for _, assetRequest := range assetRequests { + metaHash := (&proof.MetaReveal{ + Data: assetRequest.Asset.AssetMeta.Data, + }).MetaHash() + AssertAssetState( + t, unconfirmedAssets, assetRequest.Asset.Name, + metaHash[:], + AssetAmountCheck(assetRequest.Asset.Amount), + AssetTypeCheck(assetRequest.Asset.AssetType), + AssetAnchorCheck(*hashes[0], zeroHash), + AssetScriptKeyIsLocalCheck(true), + ) + } + + return *hashes[0], batchResp.Batch.BatchKey +} + +// MintAssetsConfirmBatch mints all given assets in the same batch, confirms the +// batch and verifies all asset proofs of the minted assets. +func MintAssetsConfirmBatch(t *testing.T, minerClient *rpcclient.Client, + tapClient TapdClient, assetRequests []*mintrpc.MintAssetRequest, + opts ...MintOption) []*taprpc.Asset { + + mintTXID, batchKey := MintAssetUnconfirmed( + t, minerClient, tapClient, assetRequests, opts..., + ) + + options := DefaultMintOptions() + for _, opt := range opts { + opt(options) + } + + ctxb := context.Background() + ctxt, cancel := context.WithTimeout(ctxb, options.mintingTimeout) + defer cancel() + + // Mine a block to confirm the assets. + block := MineBlocks(t, minerClient, 1, 1)[0] + blockHash := block.BlockHash() + WaitForBatchState( + t, ctxt, tapClient, options.mintingTimeout, batchKey, + mintrpc.BatchState_BATCH_STATE_FINALIZED, + ) + + return AssertAssetsMinted(t, tapClient, assetRequests, mintTXID, blockHash) +} diff --git a/make/testing_flags.mk b/make/testing_flags.mk index 1032c10a5..166ee5395 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -111,3 +111,6 @@ endif # Construct the integration test command with the added build flags. ITEST_TAGS := $(DEV_TAGS) $(RPC_TAGS) integration itest $(backend) + +# Construct the load test command with the added build flags. +LOADTEST_TAGS := $(DEV_TAGS) $(RPC_TAGS) loadtest \ No newline at end of file