@@ -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,198 @@ 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+ // nolint:lll
245+ assetRequests := []* mintrpc.MintAssetRequest {
246+ {
247+ Asset : & mintrpc.MintAsset {
248+ AssetType : taprpc .AssetType_NORMAL ,
249+ Name : fmt .Sprintf (
250+ "tapcoin-%d" , time .Now ().UnixNano (),
251+ ),
252+ AssetMeta : & taprpc.AssetMeta {
253+ Data : []byte ("{}" ),
254+ Type : taprpc .AssetMetaType_META_TYPE_JSON ,
255+ },
256+ Amount : mintAmt ,
257+ NewGroupedAsset : true ,
258+ DecimalDisplay : 4 ,
259+ },
260+ },
261+ }
262+
263+ return finishMint (t , ctx , miner , minter , assetRequests )
264+ }
265+
266+ // mintIntoGroup mints as many assets as the batch size and puts them in the
267+ // existing group that is provided by the corresponding argument.
268+ func mintIntoGroup (t * testing.T , ctx context.Context , miner * rpcclient.Client ,
269+ minter * rpcClient , tweakedKey []byte , cfg * Config ) []* taprpc.Asset {
270+
271+ mintAmt := rand .Uint64 () % uint64 (cfg .MintSupplyMax )
272+ if mintAmt < uint64 (cfg .MintSupplyMin ) {
273+ mintAmt = uint64 (cfg .MintSupplyMin )
274+ }
275+
276+ var assetRequests []* mintrpc.MintAssetRequest
277+
278+ t .Logf ("Minting %v assets into group %s" , cfg .BatchSize , tweakedKey )
279+
280+ for range cfg .BatchSize {
281+ ts := time .Now ().UnixNano ()
282+
283+ // nolint:lll
284+ req := & mintrpc.MintAssetRequest {
285+ Asset : & mintrpc.MintAsset {
286+ AssetType : taprpc .AssetType_NORMAL ,
287+ Name : fmt .Sprintf ("tapcoin-%d" , ts ),
288+ AssetMeta : & taprpc.AssetMeta {
289+ Data : []byte ("{}" ),
290+ Type : taprpc .AssetMetaType_META_TYPE_JSON ,
291+ },
292+ Amount : mintAmt ,
293+ GroupedAsset : true ,
294+ GroupKey : tweakedKey ,
295+ DecimalDisplay : 4 ,
296+ },
297+ }
298+
299+ assetRequests = append (assetRequests , req )
300+ }
301+
302+ return finishMint (t , ctx , miner , minter , assetRequests )
303+ }
304+
305+ // finishMint accepts a list of asset requests and performs the necessary RPC
306+ // calls to create and finalize a minting batch.
307+ func finishMint (t * testing.T , ctx context.Context , miner * rpcclient.Client ,
308+ minter * rpcClient ,
309+ assetRequests []* mintrpc.MintAssetRequest ) []* taprpc.Asset {
310+
311+ ctxc , streamCancel := context .WithCancel (ctx )
312+ stream , err := minter .SubscribeMintEvents (
313+ ctxc , & mintrpc.SubscribeMintEventsRequest {},
314+ )
315+ require .NoError (t , err )
316+ sub := & itest.EventSubscription [* mintrpc.MintEvent ]{
317+ ClientEventStream : stream ,
318+ Cancel : streamCancel ,
319+ }
320+
321+ itest .BuildMintingBatch (t , minter , assetRequests )
322+
323+ ctxb := context .Background ()
324+ ctxt , cancel := context .WithTimeout (ctxb , wait .DefaultTimeout )
325+ defer cancel ()
326+
327+ finalizeReq := & mintrpc.FinalizeBatchRequest {}
328+ // Instruct the daemon to finalize the batch.
329+ batchResp , err := minter .FinalizeBatch (ctxt , finalizeReq )
330+ require .NoError (t , err )
331+ require .NotEmpty (t , batchResp .Batch )
332+ require .Len (t , batchResp .Batch .Assets , len (assetRequests ))
333+ require .Equal (
334+ t , mintrpc .BatchState_BATCH_STATE_BROADCAST ,
335+ batchResp .Batch .State ,
336+ )
337+
338+ itest .WaitForBatchState (
339+ t , ctxt , minter , wait .DefaultTimeout ,
340+ batchResp .Batch .BatchKey ,
341+ mintrpc .BatchState_BATCH_STATE_BROADCAST ,
342+ )
343+ hashes , err := itest .WaitForNTxsInMempool (
344+ miner , 1 , wait .DefaultTimeout ,
345+ )
346+ require .NoError (t , err )
347+ require .GreaterOrEqual (t , len (hashes ), 1 )
348+
349+ return itest .ConfirmBatch (
350+ t , miner , minter , assetRequests , sub , * hashes [0 ],
351+ batchResp .Batch .BatchKey ,
352+ )
353+ }
354+
355+ // getTotalAssetGroups returns the total number of asset groups found in the
356+ // passed array of assets.
357+ func getTotalAssetGroups (assets []* taprpc.Asset ) []string {
358+ groups := fn .NewSet [string ]()
359+
360+ for _ , v := range assets {
361+ groupKeyStr := fmt .Sprintf ("%x" , v .AssetGroup .TweakedGroupKey )
362+ groups .Add (groupKeyStr )
363+ }
364+
365+ return groups .ToSlice ()
366+ }
0 commit comments