Skip to content

Commit b444e01

Browse files
committed
loadtest: add mintTestV2
1 parent a768492 commit b444e01

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

itest/loadtest/load_test.go

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

itest/loadtest/mint_batch_test.go

Lines changed: 194 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,194 @@ 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+
minter := alice
199+
200+
// First we make sure group initialization is completed. We check if
201+
// there's any more groups left
202+
existingGroups := getTotalAssetGroups(totalAssets)
203+
groupKeys := make(map[string][]byte, 0)
204+
205+
for _, v := range existingGroups {
206+
tweakedKey, err := hex.DecodeString(v)
207+
require.NoError(t, err)
208+
209+
groupKeys[v] = tweakedKey
210+
}
211+
212+
var remainingGroups int
213+
if cfg.TotalNumGroups > len(existingGroups) {
214+
remainingGroups = cfg.TotalNumGroups - len(existingGroups)
215+
}
216+
217+
t.Logf("Existing groups=%v, minting %v new groups",
218+
len(existingGroups), remainingGroups)
219+
for _ = range remainingGroups {
220+
mintNewGroup(t, ctx, bitcoinClient, minter, cfg)
221+
}
222+
223+
// If there's not any existing groups we skip the rest of the steps, we
224+
// will mint into those groups in another run.
225+
if len(existingGroups) == 0 {
226+
return
227+
}
228+
229+
groupIndex := rand.Intn(len(existingGroups))
230+
groupKey := groupKeys[existingGroups[groupIndex]]
231+
232+
mintIntoGroup(t, ctx, bitcoinClient, minter, groupKey, cfg)
233+
}
234+
235+
// mintNewGroup mints an asset that creates a new group.
236+
func mintNewGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
237+
minter *rpcClient, cfg *Config) []*taprpc.Asset {
238+
239+
mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
240+
if mintAmt < uint64(cfg.MintSupplyMin) {
241+
mintAmt = uint64(cfg.MintSupplyMin)
242+
}
243+
244+
assetRequests := []*mintrpc.MintAssetRequest{
245+
{
246+
Asset: &mintrpc.MintAsset{
247+
AssetType: taprpc.AssetType_NORMAL,
248+
Name: fmt.Sprintf("tapcoin-%d", time.Now().UnixNano()),
249+
AssetMeta: &taprpc.AssetMeta{
250+
Data: []byte("{}"),
251+
Type: taprpc.AssetMetaType_META_TYPE_JSON,
252+
},
253+
Amount: mintAmt,
254+
NewGroupedAsset: true,
255+
DecimalDisplay: 4,
256+
},
257+
},
258+
}
259+
260+
return finishMint(t, ctx, miner, minter, assetRequests)
261+
}
262+
263+
// mintIntoGroup mints as many assets as the batch size and puts them in the
264+
// existing group that is provided by the corresponding argument.
265+
func mintIntoGroup(t *testing.T, ctx context.Context, miner *rpcclient.Client,
266+
minter *rpcClient, tweakedKey []byte, cfg *Config) []*taprpc.Asset {
267+
268+
mintAmt := rand.Uint64() % uint64(cfg.MintSupplyMax)
269+
if mintAmt < uint64(cfg.MintSupplyMin) {
270+
mintAmt = uint64(cfg.MintSupplyMin)
271+
}
272+
273+
var assetRequests []*mintrpc.MintAssetRequest
274+
275+
t.Logf("Minting %v assets into group %s", cfg.BatchSize, tweakedKey)
276+
277+
for range cfg.BatchSize {
278+
ts := time.Now().UnixNano()
279+
280+
req := &mintrpc.MintAssetRequest{
281+
Asset: &mintrpc.MintAsset{
282+
AssetType: taprpc.AssetType_NORMAL,
283+
Name: fmt.Sprintf("tapcoin-%d", ts),
284+
AssetMeta: &taprpc.AssetMeta{
285+
Data: []byte("{}"),
286+
Type: taprpc.AssetMetaType_META_TYPE_JSON,
287+
},
288+
Amount: mintAmt,
289+
GroupedAsset: true,
290+
GroupKey: tweakedKey,
291+
DecimalDisplay: 4,
292+
},
293+
}
294+
295+
assetRequests = append(assetRequests, req)
296+
}
297+
298+
return finishMint(t, ctx, miner, minter, assetRequests)
299+
}
300+
301+
// finishMint accepts a list of asset requests and performs the necessary RPC
302+
// calls to create and finalize a minting batch.
303+
func finishMint(t *testing.T, ctx context.Context, miner *rpcclient.Client,
304+
minter *rpcClient,
305+
assetRequests []*mintrpc.MintAssetRequest) []*taprpc.Asset {
306+
307+
ctxc, streamCancel := context.WithCancel(ctx)
308+
stream, err := minter.SubscribeMintEvents(
309+
ctxc, &mintrpc.SubscribeMintEventsRequest{},
310+
)
311+
require.NoError(t, err)
312+
sub := &itest.EventSubscription[*mintrpc.MintEvent]{
313+
ClientEventStream: stream,
314+
Cancel: streamCancel,
315+
}
316+
317+
itest.BuildMintingBatch(t, minter, assetRequests)
318+
319+
ctxb := context.Background()
320+
ctxt, cancel := context.WithTimeout(ctxb, wait.DefaultTimeout)
321+
defer cancel()
322+
323+
finalizeReq := &mintrpc.FinalizeBatchRequest{}
324+
// Instruct the daemon to finalize the batch.
325+
batchResp, err := minter.FinalizeBatch(ctxt, finalizeReq)
326+
require.NoError(t, err)
327+
require.NotEmpty(t, batchResp.Batch)
328+
require.Len(t, batchResp.Batch.Assets, len(assetRequests))
329+
require.Equal(
330+
t, mintrpc.BatchState_BATCH_STATE_BROADCAST,
331+
batchResp.Batch.State,
332+
)
333+
334+
itest.WaitForBatchState(
335+
t, ctxt, minter, wait.DefaultTimeout,
336+
batchResp.Batch.BatchKey,
337+
mintrpc.BatchState_BATCH_STATE_BROADCAST,
338+
)
339+
hashes, err := itest.WaitForNTxsInMempool(
340+
miner, 1, wait.DefaultTimeout,
341+
)
342+
require.NoError(t, err)
343+
require.GreaterOrEqual(t, len(hashes), 1)
344+
345+
return itest.ConfirmBatch(
346+
t, miner, minter, assetRequests, sub, *hashes[0],
347+
batchResp.Batch.BatchKey,
348+
)
349+
}
350+
351+
// getTotalAssetGroups returns the total number of asset groups found in the
352+
// passed array of assets.
353+
func getTotalAssetGroups(assets []*taprpc.Asset) []string {
354+
groups := fn.NewSet[string]()
355+
356+
for _, v := range assets {
357+
groupKeyStr := fmt.Sprintf("%x", v.AssetGroup.TweakedGroupKey)
358+
groups.Add(groupKeyStr)
359+
}
360+
361+
return groups.ToSlice()
362+
}

0 commit comments

Comments
 (0)