@@ -17,6 +17,8 @@ import (
1717	"github.com/btcsuite/btcd/chaincfg/chainhash" 
1818	"github.com/btcsuite/btcd/wire" 
1919	"github.com/davecgh/go-spew/spew" 
20+ 	taprootassets "github.com/lightninglabs/taproot-assets" 
21+ 	"github.com/lightninglabs/taproot-assets/asset" 
2022	tapfn "github.com/lightninglabs/taproot-assets/fn" 
2123	"github.com/lightninglabs/taproot-assets/itest" 
2224	"github.com/lightninglabs/taproot-assets/proof" 
@@ -291,6 +293,277 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
291293	return  chanPointCD , chanPointDY , chanPointEF 
292294}
293295
296+ // createTestAssetNetworkGroupKey sets up a test network with Charlie, Dave, 
297+ // Erin and Fabia and creates asset channels between Charlie->Dave and 
298+ // Erin-Fabia in a way that there are two equally sized asset pieces for each 
299+ // minted asset (currently limited to exactly two assets). The channels are then 
300+ // confirmed and balances asserted. 
301+ func  createTestAssetNetworkGroupKey (ctx  context.Context , t  * harnessTest ,
302+ 	net  * NetworkHarness , charlieTap , daveTap , erinTap , fabiaTap ,
303+ 	universeTap  * tapClient , mintedAssets  []* taprpc.Asset ,
304+ 	charlieFundingAmount , erinFundingAmount  uint64 ,
305+ 	pushSat  int64 ) (* lnrpc.ChannelPoint , * lnrpc.ChannelPoint ) {
306+ 
307+ 	var  groupKey  []byte 
308+ 	for  _ , mintedAsset  :=  range  mintedAssets  {
309+ 		require .NotNil (t .t , mintedAsset .AssetGroup )
310+ 
311+ 		if  groupKey  ==  nil  {
312+ 			groupKey  =  mintedAsset .AssetGroup .TweakedGroupKey 
313+ 
314+ 			continue 
315+ 		}
316+ 
317+ 		require .Equal (
318+ 			t .t , groupKey , mintedAsset .AssetGroup .TweakedGroupKey ,
319+ 		)
320+ 	}
321+ 
322+ 	// We first do a transfer to Charlie by itself, so we get the correct 
323+ 	// asset pieces that we want for the channel funding. 
324+ 	sendAssetsAndAssert (
325+ 		ctx , t , charlieTap , charlieTap , universeTap , mintedAssets [0 ],
326+ 		charlieFundingAmount / 2 , 0 , 1 , 0 ,
327+ 	)
328+ 	sendAssetsAndAssert (
329+ 		ctx , t , charlieTap , charlieTap , universeTap , mintedAssets [1 ],
330+ 		charlieFundingAmount / 2 , 1 , 2 , 0 ,
331+ 	)
332+ 
333+ 	// We need to send some assets to Erin, so he can fund an asset channel 
334+ 	// with Fabia. 
335+ 	sendAssetsAndAssert (
336+ 		ctx , t , erinTap , charlieTap , universeTap , mintedAssets [0 ],
337+ 		erinFundingAmount / 2 , 2 , 1 , charlieFundingAmount / 2 ,
338+ 	)
339+ 	sendAssetsAndAssert (
340+ 		ctx , t , erinTap , charlieTap , universeTap , mintedAssets [1 ],
341+ 		erinFundingAmount / 2 , 3 , 2 , charlieFundingAmount / 2 ,
342+ 	)
343+ 
344+ 	// Then we burn everything but a single asset piece. 
345+ 	assetID1  :=  mintedAssets [0 ].AssetGenesis .AssetId 
346+ 	assetID2  :=  mintedAssets [1 ].AssetGenesis .AssetId 
347+ 	burnAmount1  :=  mintedAssets [0 ].Amount  -  charlieFundingAmount / 2  - 
348+ 		erinFundingAmount / 2  -  1 
349+ 	_ , err  :=  charlieTap .BurnAsset (ctx , & taprpc.BurnAssetRequest {
350+ 		Asset : & taprpc.BurnAssetRequest_AssetId {
351+ 			AssetId : assetID1 ,
352+ 		},
353+ 		AmountToBurn :     burnAmount1 ,
354+ 		ConfirmationText : taprootassets .AssetBurnConfirmationText ,
355+ 	})
356+ 	require .NoError (t .t , err )
357+ 
358+ 	mineBlocks (t , net , 1 , 1 )
359+ 
360+ 	burnAmount2  :=  mintedAssets [1 ].Amount  -  charlieFundingAmount / 2  - 
361+ 		erinFundingAmount / 2  -  1 
362+ 	_ , err  =  charlieTap .BurnAsset (ctx , & taprpc.BurnAssetRequest {
363+ 		Asset : & taprpc.BurnAssetRequest_AssetId {
364+ 			AssetId : assetID2 ,
365+ 		},
366+ 		AmountToBurn :     burnAmount2 ,
367+ 		ConfirmationText : taprootassets .AssetBurnConfirmationText ,
368+ 	})
369+ 	require .NoError (t .t , err )
370+ 
371+ 	mineBlocks (t , net , 1 , 1 )
372+ 
373+ 	t .Logf ("Opening asset channels..." )
374+ 
375+ 	// The first channel we create has a push amount, so Charlie can receive 
376+ 	// payments immediately and not run into the channel reserve issue. 
377+ 	fundRespCD , err  :=  charlieTap .FundChannel (
378+ 		ctx , & tchrpc.FundChannelRequest {
379+ 			AssetAmount :        charlieFundingAmount ,
380+ 			GroupKey :           groupKey ,
381+ 			PeerPubkey :         daveTap .node .PubKey [:],
382+ 			FeeRateSatPerVbyte : 5 ,
383+ 			PushSat :            pushSat ,
384+ 		},
385+ 	)
386+ 	require .NoError (t .t , err )
387+ 	t .Logf ("Funded channel between Charlie and Dave: %v" , fundRespCD )
388+ 
389+ 	fundRespEF , err  :=  erinTap .FundChannel (
390+ 		ctx , & tchrpc.FundChannelRequest {
391+ 			AssetAmount :        erinFundingAmount ,
392+ 			GroupKey :           groupKey ,
393+ 			PeerPubkey :         fabiaTap .node .PubKey [:],
394+ 			FeeRateSatPerVbyte : 5 ,
395+ 			PushSat :            pushSat ,
396+ 		},
397+ 	)
398+ 	require .NoError (t .t , err )
399+ 	t .Logf ("Funded channel between Erin and Fabia: %v" , fundRespEF )
400+ 
401+ 	// Make sure the pending channel shows up in the list and has the 
402+ 	// custom records set as JSON. 
403+ 	assertPendingChannels (
404+ 		t .t , charlieTap .node , mintedAssets [0 ], 1 ,
405+ 		charlieFundingAmount / 2 , 0 ,
406+ 	)
407+ 	assertPendingChannels (
408+ 		t .t , charlieTap .node , mintedAssets [1 ], 1 ,
409+ 		charlieFundingAmount / 2 , 0 ,
410+ 	)
411+ 	assertPendingChannels (
412+ 		t .t , erinTap .node , mintedAssets [0 ], 1 , erinFundingAmount / 2 , 0 ,
413+ 	)
414+ 	assertPendingChannels (
415+ 		t .t , erinTap .node , mintedAssets [1 ], 1 , erinFundingAmount / 2 , 0 ,
416+ 	)
417+ 
418+ 	// Now that we've looked at the pending channels, let's actually confirm 
419+ 	// all three of them. 
420+ 	mineBlocks (t , net , 6 , 2 )
421+ 
422+ 	var  id1 , id2  asset.ID 
423+ 	copy (id1 [:], assetID1 )
424+ 	copy (id2 [:], assetID2 )
425+ 
426+ 	fundingTree1 , err  :=  tapscript .NewChannelFundingScriptTreeUniqueID (
427+ 		id1 ,
428+ 	)
429+ 	require .NoError (t .t , err )
430+ 	fundingScriptKey1  :=  fundingTree1 .TaprootKey 
431+ 	fundingScriptTreeBytes1  :=  fundingScriptKey1 .SerializeCompressed ()
432+ 
433+ 	fundingTree2 , err  :=  tapscript .NewChannelFundingScriptTreeUniqueID (
434+ 		id2 ,
435+ 	)
436+ 	require .NoError (t .t , err )
437+ 	fundingScriptKey2  :=  fundingTree2 .TaprootKey 
438+ 	fundingScriptTreeBytes2  :=  fundingScriptKey2 .SerializeCompressed ()
439+ 
440+ 	// TODO(guggero): Those asset balances should be 1, 1, 0, 0 
441+ 	// respectively, but because we now have unique script keys, we need 
442+ 	// https://github.com/lightninglabs/taproot-assets/pull/1198 first. 
443+ 	assertAssetBalance (t .t , charlieTap , assetID1 , 25001 )
444+ 	assertAssetBalance (t .t , charlieTap , assetID2 , 25001 )
445+ 	assertAssetBalance (t .t , erinTap , assetID1 , 25000 )
446+ 	assertAssetBalance (t .t , erinTap , assetID2 , 25000 )
447+ 
448+ 	// There should be two asset pieces for Charlie for both asset IDs, one 
449+ 	// in the channel and one with a single unit from the burn. 
450+ 	assertNumAssetOutputs (t .t , charlieTap , assetID1 , 2 )
451+ 	assertNumAssetOutputs (t .t , charlieTap , assetID2 , 2 )
452+ 	assertAssetExists (
453+ 		t .t , charlieTap , assetID1 , charlieFundingAmount / 2 ,
454+ 		fundingScriptKey1 , false , true , true ,
455+ 	)
456+ 	assertAssetExists (
457+ 		t .t , charlieTap , assetID1 , 1 , nil , true , false , false ,
458+ 	)
459+ 	assertAssetExists (
460+ 		t .t , charlieTap , assetID2 , charlieFundingAmount / 2 ,
461+ 		fundingScriptKey2 , false , true , true ,
462+ 	)
463+ 	assertAssetExists (
464+ 		t .t , charlieTap , assetID2 , 1 , nil , true , false , false ,
465+ 	)
466+ 
467+ 	// Erin should just have one output for each asset ID, the one in the 
468+ 	// channel. 
469+ 	assertNumAssetOutputs (t .t , erinTap , assetID1 , 1 )
470+ 	assertNumAssetOutputs (t .t , erinTap , assetID2 , 1 )
471+ 	assertAssetExists (
472+ 		t .t , erinTap , assetID1 , erinFundingAmount / 2 , fundingScriptKey1 ,
473+ 		false , true , true ,
474+ 	)
475+ 	assertAssetExists (
476+ 		t .t , erinTap , assetID2 , erinFundingAmount / 2 , fundingScriptKey2 ,
477+ 		false , true , true ,
478+ 	)
479+ 
480+ 	// Assert that the proofs for both channels has been uploaded to the 
481+ 	// designated Universe server. 
482+ 	assertUniverseProofExists (
483+ 		t .t , universeTap , assetID1 , groupKey , fundingScriptTreeBytes1 ,
484+ 		fmt .Sprintf ("%v:%v" , fundRespCD .Txid , fundRespCD .OutputIndex ),
485+ 	)
486+ 	assertUniverseProofExists (
487+ 		t .t , universeTap , assetID2 , groupKey , fundingScriptTreeBytes2 ,
488+ 		fmt .Sprintf ("%v:%v" , fundRespCD .Txid , fundRespCD .OutputIndex ),
489+ 	)
490+ 	assertUniverseProofExists (
491+ 		t .t , universeTap , assetID1 , groupKey , fundingScriptTreeBytes1 ,
492+ 		fmt .Sprintf ("%v:%v" , fundRespEF .Txid , fundRespEF .OutputIndex ),
493+ 	)
494+ 	assertUniverseProofExists (
495+ 		t .t , universeTap , assetID2 , groupKey , fundingScriptTreeBytes2 ,
496+ 		fmt .Sprintf ("%v:%v" , fundRespEF .Txid , fundRespEF .OutputIndex ),
497+ 	)
498+ 
499+ 	// Make sure the channel shows the correct asset information. 
500+ 	assertAssetChan (
501+ 		t .t , charlieTap .node , daveTap .node , charlieFundingAmount ,
502+ 		mintedAssets ,
503+ 	)
504+ 	assertAssetChan (
505+ 		t .t , erinTap .node , fabiaTap .node , erinFundingAmount ,
506+ 		mintedAssets ,
507+ 	)
508+ 
509+ 	chanPointCD  :=  & lnrpc.ChannelPoint {
510+ 		OutputIndex : uint32 (fundRespCD .OutputIndex ),
511+ 		FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
512+ 			FundingTxidStr : fundRespCD .Txid ,
513+ 		},
514+ 	}
515+ 	chanPointEF  :=  & lnrpc.ChannelPoint {
516+ 		OutputIndex : uint32 (fundRespEF .OutputIndex ),
517+ 		FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
518+ 			FundingTxidStr : fundRespEF .Txid ,
519+ 		},
520+ 	}
521+ 
522+ 	return  chanPointCD , chanPointEF 
523+ }
524+ 
525+ // sendAssetsAndAssert sends the given amount of assets to the recipient and 
526+ // asserts that the transfer was successful. It also checks that the asset 
527+ // balance of the sender and recipient is as expected. 
528+ func  sendAssetsAndAssert (ctx  context.Context , t  * harnessTest ,
529+ 	recipient , sender , universe  * tapClient , mintedAsset  * taprpc.Asset ,
530+ 	assetSendAmount  uint64 , idx , numTransfers  int ,
531+ 	previousSentAmount  uint64 ) {
532+ 
533+ 	assetID  :=  mintedAsset .AssetGenesis .AssetId 
534+ 	recipientAddr , err  :=  recipient .NewAddr (ctx , & taprpc.NewAddrRequest {
535+ 		Amt :     assetSendAmount ,
536+ 		AssetId : assetID ,
537+ 		ProofCourierAddr : fmt .Sprintf (
538+ 			"%s://%s" , proof .UniverseRpcCourierType ,
539+ 			universe .node .Cfg .LitAddr (),
540+ 		),
541+ 	})
542+ 	require .NoError (t .t , err )
543+ 
544+ 	t .Logf ("Sending %v asset units to %s..." , assetSendAmount ,
545+ 		recipient .node .Cfg .Name )
546+ 
547+ 	// We assume that we sent the same size in a previous send. 
548+ 	totalSent  :=  assetSendAmount  +  previousSentAmount 
549+ 
550+ 	// Send the assets to recipient. 
551+ 	itest .AssertAddrCreated (
552+ 		t .t , recipient , mintedAsset , recipientAddr ,
553+ 	)
554+ 	sendResp , err  :=  sender .SendAsset (ctx , & taprpc.SendAssetRequest {
555+ 		TapAddrs : []string {recipientAddr .Encoded },
556+ 	})
557+ 	require .NoError (t .t , err )
558+ 	itest .ConfirmAndAssertOutboundTransfer (
559+ 		t .t , t .lndHarness .Miner .Client , sender , sendResp ,
560+ 		assetID ,
561+ 		[]uint64 {mintedAsset .Amount  -  totalSent , assetSendAmount },
562+ 		idx , idx + 1 ,
563+ 	)
564+ 	itest .AssertNonInteractiveRecvComplete (t .t , recipient , numTransfers )
565+ }
566+ 
294567func  assertNumAssetUTXOs (t  * testing.T , tapdClient  * tapClient ,
295568	numUTXOs  int ) * taprpc.ListUtxosResponse  {
296569
@@ -2228,6 +2501,24 @@ func logBalance(t *testing.T, nodes []*HarnessNode, assetID []byte,
22282501	}
22292502}
22302503
2504+ func  logBalanceGroup (t  * testing.T , nodes  []* HarnessNode , assetIDs  [][]byte ,
2505+ 	occasion  string ) {
2506+ 
2507+ 	t .Helper ()
2508+ 
2509+ 	time .Sleep (time .Millisecond  *  250 )
2510+ 
2511+ 	for  _ , node  :=  range  nodes  {
2512+ 		local , remote , localSat , remoteSat  :=  getAssetChannelBalance (
2513+ 			t , node , assetIDs , false ,
2514+ 		)
2515+ 
2516+ 		t .Logf ("%-7s balance: local=%-9d remote=%-9d, localSat=%-9d, " + 
2517+ 			"remoteSat=%-9d (%v)" , node .Cfg .Name , local , remote ,
2518+ 			localSat , remoteSat , occasion )
2519+ 	}
2520+ }
2521+ 
22312522// readMacaroon tries to read the macaroon file at the specified path and create 
22322523// gRPC dial options from it. 
22332524func  readMacaroon (macPath  string ) (grpc.DialOption , error ) {
0 commit comments