Skip to content

Commit 3a0556c

Browse files
authored
Merge pull request #1285 from GeorgeTsagk/loadtest-mint-v2
Add `mintV2` loadtest
2 parents d81faf9 + ee8fc89 commit 3a0556c

File tree

7 files changed

+229
-6
lines changed

7 files changed

+229
-6
lines changed

itest/assets_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func testMintBatchResume(t *harnessTest) {
183183
require.NoError(t.t, t.tapd.stop(false))
184184
require.NoError(t.t, t.tapd.start(false))
185185

186-
hashes, err := waitForNTxsInMempool(
186+
hashes, err := WaitForNTxsInMempool(
187187
t.lndHarness.Miner().Client, 1, defaultWaitTimeout,
188188
)
189189
require.NoError(t.t, err)

itest/loadtest/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ type Config struct {
9393
// only relevant for the mint test.
9494
BatchSize int `long:"mint-test-batch-size" description:"the number of assets to mint in a single batch; only relevant for the mint test"`
9595

96+
// TotalNumGroups is the total number of groups that the minted assets
97+
// belong to.
98+
TotalNumGroups int `long:"mint-test-total-groups" description:"the total number of groups the minted assets belong to"`
99+
100+
// MintSupplyMin is the minimum supply to mint per asset.
101+
MintSupplyMin int `long:"mint-test-supply-min" description:"the max supply to mint per asset"`
102+
103+
// MintSupplyMax is the max suipply to mint per asset.
104+
MintSupplyMax int `long:"mint-test-supply-max" description:"the min supply to mint per asset"`
105+
96106
// NumSends is the number of asset sends to perform. This is only
97107
// relevant for the send test.
98108
NumSends int `long:"send-test-num-sends" description:"the number of send operations to perform; only relevant for the send test"`

itest/loadtest/load_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ var loadTestCases = []testCase{
3939
name: "mint",
4040
fn: mintTest,
4141
},
42+
{
43+
name: "mintV2",
44+
fn: mintTestV2,
45+
},
4246
{
4347
name: "send",
4448
fn: sendTest,

itest/loadtest/loadtest-sample.conf

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
network=regtest
33

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

77
# Batch size for mint test
88
mint-test-batch-size=5
99

10+
# Total number of groups that assets belong to.
11+
mint-test-total-groups=20
12+
13+
# Max supply per asset mint.
14+
mint-test-supply-max=500000
15+
16+
# Min supply per asset mint.
17+
mint-test-supply-min=100000
18+
1019
# Number of send operations to perform for send test
1120
send-test-num-sends=5
1221

itest/loadtest/mint_batch_test.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import (
99
"math/rand"
1010
"strings"
1111
"testing"
12+
"time"
1213

14+
"github.com/btcsuite/btcd/rpcclient"
1315
"github.com/lightninglabs/taproot-assets/fn"
1416
"github.com/lightninglabs/taproot-assets/itest"
1517
"github.com/lightninglabs/taproot-assets/taprpc"
1618
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
1719
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
20+
"github.com/lightningnetwork/lnd/lntest/wait"
1821
"github.com/stretchr/testify/require"
1922
)
2023

@@ -166,3 +169,200 @@ func mintTest(t *testing.T, ctx context.Context, cfg *Config) {
166169

167170
itest.SyncUniverses(ctx, t, bob, alice, aliceHost, cfg.TestTimeout)
168171
}
172+
173+
// mintTestV2 checks that we can mint a batch of assets. It is a more
174+
// performant version of the existing mintTest, as it uses less assertions and
175+
// RPC calls.
176+
func mintTestV2(t *testing.T, ctx context.Context, cfg *Config) {
177+
// Start by initializing all our client connections.
178+
alice, bob, bitcoinClient := initClients(t, ctx, cfg)
179+
180+
// We query the assets of each node once on this step. Every function
181+
// that needs to take a node's assets into account will be passed these
182+
// values instead of calling the RPC again. This is done to minimize
183+
// collateral RPC impact of the loadtest.
184+
resAlice, err := alice.ListAssets(ctx, &taprpc.ListAssetRequest{})
185+
require.NoError(t, err)
186+
187+
resBob, err := bob.ListAssets(ctx, &taprpc.ListAssetRequest{})
188+
require.NoError(t, err)
189+
190+
assetsAlice := resAlice.Assets
191+
assetsBob := resBob.Assets
192+
193+
totalAssets := make([]*taprpc.Asset, len(assetsAlice)+len(assetsBob))
194+
copy(totalAssets, assetsAlice)
195+
copy(totalAssets[len(assetsAlice):], assetsBob)
196+
197+
// Alice serves as the minter.
198+
//
199+
// TODO(george): Currently we use only 1 fixed minter, but this could
200+
// change in the future to emulate a more realistic environment where
201+
// multiple nodes continuously mint assets into their own groups.
202+
minter := alice
203+
204+
// First we make sure group initialization is completed. We check if
205+
// there's any more groups left
206+
existingGroups := getTotalAssetGroups(totalAssets)
207+
groupKeys := make(map[string][]byte, 0)
208+
209+
for _, v := range existingGroups {
210+
tweakedKey, err := hex.DecodeString(v)
211+
require.NoError(t, err)
212+
213+
groupKeys[v] = tweakedKey
214+
}
215+
216+
var remainingGroups int
217+
if cfg.TotalNumGroups > len(existingGroups) {
218+
remainingGroups = cfg.TotalNumGroups - len(existingGroups)
219+
}
220+
221+
t.Logf("Existing groups=%v, minting %v new groups",
222+
len(existingGroups), remainingGroups)
223+
for range remainingGroups {
224+
mintNewGroup(t, ctx, bitcoinClient, minter, cfg)
225+
}
226+
227+
// If there aren't any existing groups we skip the rest of the steps, we
228+
// will mint into those groups in another run.
229+
if len(existingGroups) == 0 {
230+
return
231+
}
232+
233+
groupIndex := rand.Intn(len(existingGroups))
234+
groupKey := groupKeys[existingGroups[groupIndex]]
235+
236+
mintIntoGroup(t, ctx, bitcoinClient, minter, groupKey, cfg)
237+
}
238+
239+
// mintNewGroup mints an asset that creates a new group.
240+
func mintNewGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
241+
minter *rpcClient, cfg *Config) []*taprpc.Asset {
242+
243+
mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
244+
if mintAmt < uint64(cfg.MintSupplyMin) {
245+
mintAmt = uint64(cfg.MintSupplyMin)
246+
}
247+
248+
assetRequests := []*mintrpc.MintAssetRequest{{
249+
Asset: &mintrpc.MintAsset{
250+
AssetType: taprpc.AssetType_NORMAL,
251+
Name: fmt.Sprintf(
252+
"tapcoin-%d", time.Now().UnixNano(),
253+
),
254+
AssetMeta: &taprpc.AssetMeta{
255+
Data: []byte("{}"),
256+
Type: taprpc.AssetMetaType_META_TYPE_JSON,
257+
},
258+
Amount: mintAmt,
259+
NewGroupedAsset: true,
260+
DecimalDisplay: 4,
261+
},
262+
}}
263+
264+
return finishMint(t, ctx, miner, minter, assetRequests)
265+
}
266+
267+
// mintIntoGroup mints as many assets as the batch size and puts them in the
268+
// existing group that is provided by the corresponding argument.
269+
func mintIntoGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
270+
minter *rpcClient, tweakedKey []byte, cfg *Config) []*taprpc.Asset {
271+
272+
mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
273+
if mintAmt < uint64(cfg.MintSupplyMin) {
274+
mintAmt = uint64(cfg.MintSupplyMin)
275+
}
276+
277+
var assetRequests []*mintrpc.MintAssetRequest
278+
279+
t.Logf("Minting %v assets into group %x", cfg.BatchSize, tweakedKey)
280+
281+
for range cfg.BatchSize {
282+
ts := time.Now().UnixNano()
283+
284+
// nolint:lll
285+
req := &mintrpc.MintAssetRequest{
286+
Asset: &mintrpc.MintAsset{
287+
AssetType: taprpc.AssetType_NORMAL,
288+
Name: fmt.Sprintf("tapcoin-%d", ts),
289+
AssetMeta: &taprpc.AssetMeta{
290+
Data: []byte("{}"),
291+
Type: taprpc.AssetMetaType_META_TYPE_JSON,
292+
},
293+
Amount: mintAmt,
294+
GroupedAsset: true,
295+
GroupKey: tweakedKey,
296+
DecimalDisplay: 4,
297+
},
298+
}
299+
300+
assetRequests = append(assetRequests, req)
301+
}
302+
303+
return finishMint(t, ctx, miner, minter, assetRequests)
304+
}
305+
306+
// finishMint accepts a list of asset requests and performs the necessary RPC
307+
// calls to create and finalize a minting batch.
308+
func finishMint(t *testing.T, ctx context.Context, miner *rpcclient.Client,
309+
minter *rpcClient,
310+
assetRequests []*mintrpc.MintAssetRequest) []*taprpc.Asset {
311+
312+
ctxc, streamCancel := context.WithCancel(ctx)
313+
stream, err := minter.SubscribeMintEvents(
314+
ctxc, &mintrpc.SubscribeMintEventsRequest{},
315+
)
316+
require.NoError(t, err)
317+
sub := &itest.EventSubscription[*mintrpc.MintEvent]{
318+
ClientEventStream: stream,
319+
Cancel: streamCancel,
320+
}
321+
322+
itest.BuildMintingBatch(t, minter, assetRequests)
323+
324+
ctxb := context.Background()
325+
ctxt, cancel := context.WithTimeout(ctxb, wait.DefaultTimeout)
326+
defer cancel()
327+
328+
finalizeReq := &mintrpc.FinalizeBatchRequest{}
329+
330+
// Instruct the daemon to finalize the batch.
331+
batchResp, err := minter.FinalizeBatch(ctxt, finalizeReq)
332+
require.NoError(t, err)
333+
require.NotEmpty(t, batchResp.Batch)
334+
require.Len(t, batchResp.Batch.Assets, len(assetRequests))
335+
require.Equal(
336+
t, mintrpc.BatchState_BATCH_STATE_BROADCAST,
337+
batchResp.Batch.State,
338+
)
339+
340+
itest.WaitForBatchState(
341+
t, ctxt, minter, wait.DefaultTimeout,
342+
batchResp.Batch.BatchKey,
343+
mintrpc.BatchState_BATCH_STATE_BROADCAST,
344+
)
345+
hashes, err := itest.WaitForNTxsInMempool(
346+
miner, 1, wait.DefaultTimeout,
347+
)
348+
require.NoError(t, err)
349+
require.GreaterOrEqual(t, len(hashes), 1)
350+
351+
return itest.ConfirmBatch(
352+
t, miner, minter, assetRequests, sub, *hashes[0],
353+
batchResp.Batch.BatchKey,
354+
)
355+
}
356+
357+
// getTotalAssetGroups returns the total number of asset groups found in the
358+
// passed array of assets.
359+
func getTotalAssetGroups(assets []*taprpc.Asset) []string {
360+
groups := fn.NewSet[string]()
361+
362+
for _, v := range assets {
363+
groupKeyStr := fmt.Sprintf("%x", v.AssetGroup.TweakedGroupKey)
364+
groups.Add(groupKeyStr)
365+
}
366+
367+
return groups.ToSlice()
368+
}

itest/test_harness.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,10 @@ func isMempoolEmpty(miner *rpcclient.Client, timeout time.Duration) (bool,
476476
}
477477
}
478478

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

485485
breakTimeout := time.After(timeout)

itest/utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func MineBlocks(t *testing.T, client *rpcclient.Client,
142142
var txids []*chainhash.Hash
143143
var err error
144144
if numTxs > 0 {
145-
txids, err = waitForNTxsInMempool(
145+
txids, err = WaitForNTxsInMempool(
146146
client, numTxs, minerMempoolTimeout,
147147
)
148148
if err != nil {
@@ -386,7 +386,7 @@ func FinalizeBatchUnconfirmed(t *testing.T, minerClient *rpcclient.Client,
386386
batchResp.Batch.BatchKey,
387387
mintrpc.BatchState_BATCH_STATE_BROADCAST,
388388
)
389-
hashes, err := waitForNTxsInMempool(
389+
hashes, err := WaitForNTxsInMempool(
390390
minerClient, 1, options.mintingTimeout,
391391
)
392392
require.NoError(t, err)

0 commit comments

Comments
 (0)