Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mintV2 loadtest #1285

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion itest/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func testMintBatchResume(t *harnessTest) {
require.NoError(t.t, t.tapd.stop(false))
require.NoError(t.t, t.tapd.start(false))

hashes, err := waitForNTxsInMempool(
hashes, err := WaitForNTxsInMempool(
t.lndHarness.Miner().Client, 1, defaultWaitTimeout,
)
require.NoError(t.t, err)
Expand Down
10 changes: 10 additions & 0 deletions itest/loadtest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ type Config struct {
// only relevant for the mint test.
BatchSize int `long:"mint-test-batch-size" description:"the number of assets to mint in a single batch; only relevant for the mint test"`

// TotalNumGroups is the total number of groups that the minted assets
// belong to.
TotalNumGroups int `long:"mint-test-total-groups" description:"the total number of groups the minted assets belong to"`

// MintSupplyMin is the minimum supply to mint per asset.
MintSupplyMin int `long:"mint-test-supply-min" description:"the max supply to mint per asset"`

// MintSupplyMax is the max suipply to mint per asset.
MintSupplyMax int `long:"mint-test-supply-max" description:"the min supply to mint per asset"`

// NumSends is the number of asset sends to perform. This is only
// relevant for the send test.
NumSends int `long:"send-test-num-sends" description:"the number of send operations to perform; only relevant for the send test"`
Expand Down
4 changes: 4 additions & 0 deletions itest/loadtest/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ var loadTestCases = []testCase{
name: "mint",
fn: mintTest,
},
{
name: "mintV2",
fn: mintTestV2,
},
{
name: "send",
fn: sendTest,
Expand Down
11 changes: 10 additions & 1 deletion itest/loadtest/loadtest-sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
network=regtest

# The name of the test case to run. Example "send" or "mint"
test-case="mint"
test-case="mintV2"

# Batch size for mint test
mint-test-batch-size=5

# Total number of groups that assets belong to.
mint-test-total-groups=20

# Max supply per asset mint.
mint-test-supply-max=500000

# Min supply per asset mint.
mint-test-supply-min=100000

# Number of send operations to perform for send test
send-test-num-sends=5

Expand Down
198 changes: 198 additions & 0 deletions itest/loadtest/mint_batch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
"math/rand"
"strings"
"testing"
"time"

"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/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -166,3 +169,198 @@ func mintTest(t *testing.T, ctx context.Context, cfg *Config) {

itest.SyncUniverses(ctx, t, bob, alice, aliceHost, cfg.TestTimeout)
}

// mintTestV2 checks that we can mint a batch of assets. It is a more
// performant version of the existing mintTest, as it uses less assertions and
// RPC calls.
func mintTestV2(t *testing.T, ctx context.Context, cfg *Config) {
// Start by initializing all our client connections.
alice, bob, bitcoinClient := initClients(t, ctx, cfg)

// We query the assets of each node once on this step. Every function
// that needs to take a node's assets into account will be passed these
// values instead of calling the RPC again. This is done to minimize
// collateral RPC impact of the loadtest.
resAlice, err := alice.ListAssets(ctx, &taprpc.ListAssetRequest{})
require.NoError(t, err)

resBob, err := bob.ListAssets(ctx, &taprpc.ListAssetRequest{})
require.NoError(t, err)

assetsAlice := resAlice.Assets
assetsBob := resBob.Assets

totalAssets := make([]*taprpc.Asset, len(assetsAlice)+len(assetsBob))
copy(totalAssets, assetsAlice)
copy(totalAssets[len(assetsAlice):], assetsBob)

// Alice serves as the minter.
minter := alice

// First we make sure group initialization is completed. We check if
// there's any more groups left
existingGroups := getTotalAssetGroups(totalAssets)
groupKeys := make(map[string][]byte, 0)

for _, v := range existingGroups {
tweakedKey, err := hex.DecodeString(v)
require.NoError(t, err)

groupKeys[v] = tweakedKey
}

var remainingGroups int
if cfg.TotalNumGroups > len(existingGroups) {
remainingGroups = cfg.TotalNumGroups - len(existingGroups)
}

t.Logf("Existing groups=%v, minting %v new groups",
len(existingGroups), remainingGroups)
for range remainingGroups {
mintNewGroup(t, ctx, bitcoinClient, minter, cfg)
}

// If there's not any existing groups we skip the rest of the steps, we
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grammar nit: "If there aren't any existing groups".

// will mint into those groups in another run.
if len(existingGroups) == 0 {
return
}

groupIndex := rand.Intn(len(existingGroups))
groupKey := groupKeys[existingGroups[groupIndex]]

mintIntoGroup(t, ctx, bitcoinClient, minter, groupKey, cfg)
}

// mintNewGroup mints an asset that creates a new group.
func mintNewGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
minter *rpcClient, cfg *Config) []*taprpc.Asset {

mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
if mintAmt < uint64(cfg.MintSupplyMin) {
mintAmt = uint64(cfg.MintSupplyMin)
}

// nolint:lll
assetRequests := []*mintrpc.MintAssetRequest{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can remove a level of indentation by formatting as:

	assetRequests := []*mintrpc.MintAssetRequest{{
		Asset: &mintrpc.MintAsset{
			AssetType: taprpc.AssetType_NORMAL,
			Name: fmt.Sprintf(
				"tapcoin-%d", time.Now().UnixNano(),
			),
			AssetMeta: &taprpc.AssetMeta{
				Data: []byte("{}"),
				Type: taprpc.AssetMetaType_META_TYPE_JSON,
			},
			Amount:          mintAmt,
			NewGroupedAsset: true,
			DecimalDisplay:  4,
		},
	}}

That way we don't need the lll nolint directive. Same below.

{
Asset: &mintrpc.MintAsset{
AssetType: taprpc.AssetType_NORMAL,
Name: fmt.Sprintf(
"tapcoin-%d", time.Now().UnixNano(),
),
AssetMeta: &taprpc.AssetMeta{
Data: []byte("{}"),
Type: taprpc.AssetMetaType_META_TYPE_JSON,
},
Amount: mintAmt,
NewGroupedAsset: true,
DecimalDisplay: 4,
},
},
}

return finishMint(t, ctx, miner, minter, assetRequests)
}

// mintIntoGroup mints as many assets as the batch size and puts them in the
// existing group that is provided by the corresponding argument.
func mintIntoGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
minter *rpcClient, tweakedKey []byte, cfg *Config) []*taprpc.Asset {

mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
if mintAmt < uint64(cfg.MintSupplyMin) {
mintAmt = uint64(cfg.MintSupplyMin)
}

var assetRequests []*mintrpc.MintAssetRequest

t.Logf("Minting %v assets into group %s", cfg.BatchSize, tweakedKey)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: %x.


for range cfg.BatchSize {
ts := time.Now().UnixNano()

// nolint:lll
req := &mintrpc.MintAssetRequest{
Asset: &mintrpc.MintAsset{
AssetType: taprpc.AssetType_NORMAL,
Name: fmt.Sprintf("tapcoin-%d", ts),
AssetMeta: &taprpc.AssetMeta{
Data: []byte("{}"),
Type: taprpc.AssetMetaType_META_TYPE_JSON,
},
Amount: mintAmt,
GroupedAsset: true,
GroupKey: tweakedKey,
DecimalDisplay: 4,
},
}

assetRequests = append(assetRequests, req)
}

return finishMint(t, ctx, miner, minter, assetRequests)
}

// finishMint accepts a list of asset requests and performs the necessary RPC
// calls to create and finalize a minting batch.
func finishMint(t *testing.T, ctx context.Context, miner *rpcclient.Client,
minter *rpcClient,
assetRequests []*mintrpc.MintAssetRequest) []*taprpc.Asset {

ctxc, streamCancel := context.WithCancel(ctx)
stream, err := minter.SubscribeMintEvents(
ctxc, &mintrpc.SubscribeMintEventsRequest{},
)
require.NoError(t, err)
sub := &itest.EventSubscription[*mintrpc.MintEvent]{
ClientEventStream: stream,
Cancel: streamCancel,
}

itest.BuildMintingBatch(t, minter, assetRequests)

ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, wait.DefaultTimeout)
defer cancel()

finalizeReq := &mintrpc.FinalizeBatchRequest{}
// Instruct the daemon to finalize the batch.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline before comment block.

batchResp, err := minter.FinalizeBatch(ctxt, finalizeReq)
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,
)

itest.WaitForBatchState(
t, ctxt, minter, wait.DefaultTimeout,
batchResp.Batch.BatchKey,
mintrpc.BatchState_BATCH_STATE_BROADCAST,
)
hashes, err := itest.WaitForNTxsInMempool(
miner, 1, wait.DefaultTimeout,
)
require.NoError(t, err)
require.GreaterOrEqual(t, len(hashes), 1)

return itest.ConfirmBatch(
t, miner, minter, assetRequests, sub, *hashes[0],
batchResp.Batch.BatchKey,
)
}

// getTotalAssetGroups returns the total number of asset groups found in the
// passed array of assets.
func getTotalAssetGroups(assets []*taprpc.Asset) []string {
groups := fn.NewSet[string]()

for _, v := range assets {
groupKeyStr := fmt.Sprintf("%x", v.AssetGroup.TweakedGroupKey)
groups.Add(groupKeyStr)
}

return groups.ToSlice()
}
4 changes: 2 additions & 2 deletions itest/test_harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,10 +472,10 @@ func isMempoolEmpty(miner *rpcclient.Client, timeout time.Duration) (bool,
}
}

// waitForNTxsInMempool polls until finding the desired number of transactions
// WaitForNTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. An error is returned if this number is not
// met after the given timeout.
func waitForNTxsInMempool(miner *rpcclient.Client, n int,
func WaitForNTxsInMempool(miner *rpcclient.Client, n int,
timeout time.Duration) ([]*chainhash.Hash, error) {

breakTimeout := time.After(timeout)
Expand Down
4 changes: 2 additions & 2 deletions itest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func MineBlocks(t *testing.T, client *rpcclient.Client,
var txids []*chainhash.Hash
var err error
if numTxs > 0 {
txids, err = waitForNTxsInMempool(
txids, err = WaitForNTxsInMempool(
client, numTxs, minerMempoolTimeout,
)
if err != nil {
Expand Down Expand Up @@ -386,7 +386,7 @@ func FinalizeBatchUnconfirmed(t *testing.T, minerClient *rpcclient.Client,
batchResp.Batch.BatchKey,
mintrpc.BatchState_BATCH_STATE_BROADCAST,
)
hashes, err := waitForNTxsInMempool(
hashes, err := WaitForNTxsInMempool(
minerClient, 1, options.mintingTimeout,
)
require.NoError(t, err)
Expand Down
Loading