@@ -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