@@ -33,6 +33,7 @@ import (
3333	"github.com/lightninglabs/taproot-assets/tapscript" 
3434	"github.com/lightningnetwork/lnd/fn" 
3535	"github.com/lightningnetwork/lnd/lnrpc" 
36+ 	"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" 
3637	"github.com/lightningnetwork/lnd/lnrpc/routerrpc" 
3738	"github.com/lightningnetwork/lnd/lntest/rpc" 
3839	"github.com/lightningnetwork/lnd/lntest/wait" 
@@ -61,8 +62,8 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
6162	daveTap , erinTap , fabiaTap , yaraTap , universeTap  * tapClient ,
6263	mintedAsset  * taprpc.Asset , assetSendAmount , charlieFundingAmount ,
6364	daveFundingAmount ,
64- 	erinFundingAmount  uint64 , pushSat  int64 ) (* tchrpc. FundChannelResponse ,
65- 	* tchrpc. FundChannelResponse , * tchrpc. FundChannelResponse ) {
65+ 	erinFundingAmount  uint64 , pushSat  int64 ) (* lnrpc. ChannelPoint ,
66+ 	* lnrpc. ChannelPoint , * lnrpc. ChannelPoint ) {
6667
6768	ctxb  :=  context .Background ()
6869	assetID  :=  mintedAsset .AssetGenesis .AssetId 
@@ -256,7 +257,26 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
256257		t .t , erinTap .node , fabiaTap .node , erinFundingAmount , assetID ,
257258	)
258259
259- 	return  fundRespCD , fundRespDY , fundRespEF 
260+ 	chanPointCD  :=  & lnrpc.ChannelPoint {
261+ 		OutputIndex : uint32 (fundRespCD .OutputIndex ),
262+ 		FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
263+ 			FundingTxidStr : fundRespCD .Txid ,
264+ 		},
265+ 	}
266+ 	chanPointDY  :=  & lnrpc.ChannelPoint {
267+ 		OutputIndex : uint32 (fundRespDY .OutputIndex ),
268+ 		FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
269+ 			FundingTxidStr : fundRespDY .Txid ,
270+ 		},
271+ 	}
272+ 	chanPointEF  :=  & lnrpc.ChannelPoint {
273+ 		OutputIndex : uint32 (fundRespEF .OutputIndex ),
274+ 		FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
275+ 			FundingTxidStr : fundRespEF .Txid ,
276+ 		},
277+ 	}
278+ 
279+ 	return  chanPointCD , chanPointDY , chanPointEF 
260280}
261281
262282func  assertNumAssetUTXOs (t  * testing.T , tapdClient  * tapClient ,
@@ -586,6 +606,65 @@ func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
586606		balance .RemoteBalance .Sat 
587607}
588608
609+ func  fetchChannel (t  * testing.T , node  * HarnessNode ,
610+ 	chanPoint  * lnrpc.ChannelPoint ) * lnrpc.Channel  {
611+ 
612+ 	ctxb  :=  context .Background ()
613+ 	ctxt , cancel  :=  context .WithTimeout (ctxb , defaultTimeout )
614+ 	defer  cancel ()
615+ 
616+ 	channelResp , err  :=  node .ListChannels (ctxt , & lnrpc.ListChannelsRequest {
617+ 		ActiveOnly : true ,
618+ 	})
619+ 	require .NoError (t , err )
620+ 
621+ 	chanFundingHash , err  :=  lnrpc .GetChanPointFundingTxid (chanPoint )
622+ 	require .NoError (t , err )
623+ 
624+ 	chanPointStr  :=  fmt .Sprintf ("%v:%v" , chanFundingHash ,
625+ 		chanPoint .OutputIndex )
626+ 
627+ 	var  targetChan  * lnrpc.Channel 
628+ 	for  _ , channel  :=  range  channelResp .Channels  {
629+ 		if  channel .ChannelPoint  ==  chanPointStr  {
630+ 			targetChan  =  channel 
631+ 
632+ 			break 
633+ 		}
634+ 	}
635+ 	require .NotNil (t , targetChan )
636+ 
637+ 	return  targetChan 
638+ }
639+ 
640+ func  assertChannelSatBalance (t  * testing.T , node  * HarnessNode ,
641+ 	chanPoint  * lnrpc.ChannelPoint , local , remote  int64 ) {
642+ 
643+ 	targetChan  :=  fetchChannel (t , node , chanPoint )
644+ 
645+ 	require .InDelta (t , local , targetChan .LocalBalance , 1 )
646+ 	require .InDelta (t , remote , targetChan .RemoteBalance , 1 )
647+ }
648+ 
649+ func  assertChannelAssetBalance (t  * testing.T , node  * HarnessNode ,
650+ 	chanPoint  * lnrpc.ChannelPoint , local , remote  uint64 ) {
651+ 
652+ 	targetChan  :=  fetchChannel (t , node , chanPoint )
653+ 
654+ 	var  assetBalance  rfqmsg.JsonAssetChannel 
655+ 	err  :=  json .Unmarshal (targetChan .CustomChannelData , & assetBalance )
656+ 	require .NoError (t , err )
657+ 
658+ 	require .Len (t , assetBalance .Assets , 1 )
659+ 
660+ 	require .InDelta (t , local , assetBalance .Assets [0 ].LocalBalance , 1 )
661+ 	require .InDelta (t , remote , assetBalance .Assets [0 ].RemoteBalance , 1 )
662+ }
663+ 
664+ func  routingFee (amt  lnwire.MilliSatoshi ) lnwire.MilliSatoshi  {
665+ 	return  amt  +  (amt  /  1000_000 ) +  1000 
666+ }
667+ 
589668func  sendAssetKeySendPayment (t  * testing.T , src , dst  * HarnessNode , amt  uint64 ,
590669	assetID  []byte , btcAmt  fn.Option [int64 ],
591670	expectedStatus  lnrpc.Payment_PaymentStatus ,
@@ -702,9 +781,11 @@ func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode,
702781	})
703782	require .NoError (t , err )
704783
705- 	return  payInvoiceWithAssets (
784+ 	numUnits ,  _   :=  payInvoiceWithAssets (
706785		t , src , rfqPeer , invoiceResp , assetID , smallShards ,
707786	)
787+ 
788+ 	return  numUnits 
708789}
709790
710791func  payInvoiceWithSatoshi (t  * testing.T , payer  * HarnessNode ,
@@ -730,7 +811,7 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
730811
731812func  payInvoiceWithAssets (t  * testing.T , payer , rfqPeer  * HarnessNode ,
732813	invoice  * lnrpc.AddInvoiceResponse , assetID  []byte ,
733- 	smallShards  bool ) uint64  {
814+ 	smallShards  bool ) ( uint64 , rfqmath. BigIntFixedPoint )  {
734815
735816	ctxb  :=  context .Background ()
736817	ctxt , cancel  :=  context .WithTimeout (ctxb , defaultTimeout )
@@ -774,19 +855,21 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
774855	rate , err  :=  rfqrpc .UnmarshalFixedPoint (rpcRate )
775856	require .NoError (t , err )
776857
858+ 	t .Logf ("Got quote for %v asset units per BTC" , rate )
859+ 
777860	amountMsat  :=  lnwire .MilliSatoshi (decodedInvoice .NumMsat )
778861	milliSatsFP  :=  rfqmath .MilliSatoshiToUnits (amountMsat , * rate )
779862	numUnits  :=  milliSatsFP .ScaleTo (0 ).ToUint64 ()
780- 	msatPerUnit  :=  uint64 (decodedInvoice .NumMsat ) /  numUnits 
781- 	t .Logf ("Got quote for %v asset units at %v  msat/unit from peer %s " + 
863+ 	msatPerUnit  :=  float64 (decodedInvoice .NumMsat ) /  float64 ( numUnits ) 
864+ 	t .Logf ("Got quote for %v asset units at %3f  msat/unit from peer %s " + 
782865		"with SCID %d" , numUnits , msatPerUnit , peerPubKey ,
783866		acceptedQuote .Scid )
784867
785868	result , err  :=  getAssetPaymentResult (stream )
786869	require .NoError (t , err )
787870	require .Equal (t , lnrpc .Payment_SUCCEEDED , result .Status )
788871
789- 	return  numUnits 
872+ 	return  numUnits ,  * rate 
790873}
791874
792875func  createAssetInvoice (t  * testing.T , dstRfqPeer , dst  * HarnessNode ,
@@ -825,19 +908,103 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
825908	rate , err  :=  rfqrpc .UnmarshalFixedPoint (rpcRate )
826909	require .NoError (t , err )
827910
911+ 	t .Logf ("Got quote for %v asset units per BTC" , rate )
912+ 
828913	assetUnits  :=  rfqmath .NewBigIntFixedPoint (assetAmount , 0 )
829914	numMSats  :=  rfqmath .UnitsToMilliSatoshi (assetUnits , * rate )
830- 	mSatPerUnit  :=  uint64 (decodedInvoice .NumMsat ) /  assetAmount 
915+ 	mSatPerUnit  :=  float64 (decodedInvoice .NumMsat ) /  float64 ( assetAmount ) 
831916
832917	require .EqualValues (t , numMSats , decodedInvoice .NumMsat )
833918
834- 	t .Logf ("Got quote for %d sats  at %v  msat/unit from peer %x with SCID  " + 
835- 		"%d" , decodedInvoice .NumMsat , mSatPerUnit ,  dstRfqPeer . PubKey [:] ,
836- 		resp .AcceptedBuyQuote .Scid )
919+ 	t .Logf ("Got quote for %d mSats  at %3f  msat/unit from peer %x with " + 
920+ 		"SCID  %d" , decodedInvoice .NumMsat , mSatPerUnit ,
921+ 		dstRfqPeer . PubKey [:],  resp .AcceptedBuyQuote .Scid )
837922
838923	return  resp .InvoiceResult 
839924}
840925
926+ // assertInvoiceHtlcAssets makes sure the invoice with the given hash shows the 
927+ // individual HTLCs that arrived for it and that they show the correct asset 
928+ // amounts when decoded. 
929+ func  assertInvoiceHtlcAssets (t  * testing.T , node  * HarnessNode ,
930+ 	addedInvoice  * lnrpc.AddInvoiceResponse , assetAmount  uint64 ) {
931+ 
932+ 	ctxb  :=  context .Background ()
933+ 	ctxt , cancel  :=  context .WithTimeout (ctxb , defaultTimeout )
934+ 	defer  cancel ()
935+ 
936+ 	invoice , err  :=  node .InvoicesClient .LookupInvoiceV2 (
937+ 		ctxt , & invoicesrpc.LookupInvoiceMsg {
938+ 			InvoiceRef : & invoicesrpc.LookupInvoiceMsg_PaymentAddr {
939+ 				PaymentAddr : addedInvoice .PaymentAddr ,
940+ 			},
941+ 		},
942+ 	)
943+ 	require .NoError (t , err )
944+ 	require .NotEmpty (t , invoice .Htlcs )
945+ 
946+ 	t .Logf ("Asset invoice: %v" , toProtoJSON (t , invoice ))
947+ 
948+ 	var  totalAssetAmount  uint64 
949+ 	for  _ , htlc  :=  range  invoice .Htlcs  {
950+ 		require .NotEmpty (t , htlc .CustomChannelData )
951+ 
952+ 		jsonHtlc  :=  & rfqmsg.JsonHtlc {}
953+ 		err  :=  json .Unmarshal (htlc .CustomChannelData , jsonHtlc )
954+ 		require .NoError (t , err )
955+ 
956+ 		for  _ , balance  :=  range  jsonHtlc .Balances  {
957+ 			totalAssetAmount  +=  balance .Amount 
958+ 		}
959+ 	}
960+ 
961+ 	// Due to rounding we allow up to 1 unit of error. 
962+ 	require .InDelta (t , assetAmount , totalAssetAmount , 1 )
963+ }
964+ 
965+ // assertPaymentHtlcAssets makes sure the payment with the given hash shows the 
966+ // individual HTLCs that arrived for it and that they show the correct asset 
967+ // amounts when decoded. 
968+ func  assertPaymentHtlcAssets (t  * testing.T , node  * HarnessNode , payHash  []byte ,
969+ 	assetAmount  uint64 ) {
970+ 
971+ 	ctxb  :=  context .Background ()
972+ 	ctxt , cancel  :=  context .WithTimeout (ctxb , defaultTimeout )
973+ 	defer  cancel ()
974+ 
975+ 	stream , err  :=  node .RouterClient .TrackPaymentV2 (
976+ 		ctxt , & routerrpc.TrackPaymentRequest {
977+ 			PaymentHash :       payHash ,
978+ 			NoInflightUpdates : true ,
979+ 		},
980+ 	)
981+ 	require .NoError (t , err )
982+ 
983+ 	payment , err  :=  stream .Recv ()
984+ 	require .NoError (t , err )
985+ 	require .NotNil (t , payment )
986+ 	require .NotEmpty (t , payment .Htlcs )
987+ 
988+ 	t .Logf ("Asset payment: %v" , toProtoJSON (t , payment ))
989+ 
990+ 	var  totalAssetAmount  uint64 
991+ 	for  _ , htlc  :=  range  payment .Htlcs  {
992+ 		require .NotNil (t , htlc .Route )
993+ 		require .NotEmpty (t , htlc .Route .CustomChannelData )
994+ 
995+ 		jsonHtlc  :=  & rfqmsg.JsonHtlc {}
996+ 		err  :=  json .Unmarshal (htlc .Route .CustomChannelData , jsonHtlc )
997+ 		require .NoError (t , err )
998+ 
999+ 		for  _ , balance  :=  range  jsonHtlc .Balances  {
1000+ 			totalAssetAmount  +=  balance .Amount 
1001+ 		}
1002+ 	}
1003+ 
1004+ 	// Due to rounding we allow up to 1 unit of error. 
1005+ 	require .InDelta (t , assetAmount , totalAssetAmount , 1 )
1006+ }
1007+ 
8411008func  waitForSendEvent (t  * testing.T ,
8421009	sendEvents  taprpc.TaprootAssets_SubscribeSendEventsClient ,
8431010	expectedState  tapfreighter.SendState ) {
@@ -862,6 +1029,14 @@ type coOpCloseBalanceCheck func(t *testing.T, local, remote *HarnessNode,
8621029	closeTx  * wire.MsgTx , closeUpdate  * lnrpc.ChannelCloseUpdate ,
8631030	assetID , groupKey  []byte , universeTap  * tapClient )
8641031
1032+ // noOpCoOpCloseBalanceCheck is a no-op implementation of the co-op close 
1033+ // balance check that can be used in tests. 
1034+ func  noOpCoOpCloseBalanceCheck (_  * testing.T , _ , _  * HarnessNode , _  * wire.MsgTx ,
1035+ 	_  * lnrpc.ChannelCloseUpdate , _ , _  []byte , _  * tapClient ) {
1036+ 
1037+ 	// This is a no-op function. 
1038+ }
1039+ 
8651040// closeAssetChannelAndAssert closes the channel between the local and remote 
8661041// node and asserts the final balances of the closing transaction. 
8671042func  closeAssetChannelAndAssert (t  * harnessTest , net  * NetworkHarness ,
0 commit comments