@@ -61,8 +61,8 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
6161 daveTap , erinTap , fabiaTap , yaraTap , universeTap * tapClient ,
6262 mintedAsset * taprpc.Asset , assetSendAmount , charlieFundingAmount ,
6363 daveFundingAmount ,
64- erinFundingAmount uint64 , pushSat int64 ) (* tchrpc. FundChannelResponse ,
65- * tchrpc. FundChannelResponse , * tchrpc. FundChannelResponse ) {
64+ erinFundingAmount uint64 , pushSat int64 ) (* lnrpc. ChannelPoint ,
65+ * lnrpc. ChannelPoint , * lnrpc. ChannelPoint ) {
6666
6767 ctxb := context .Background ()
6868 assetID := mintedAsset .AssetGenesis .AssetId
@@ -256,7 +256,26 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
256256 t .t , erinTap .node , fabiaTap .node , erinFundingAmount , assetID ,
257257 )
258258
259- return fundRespCD , fundRespDY , fundRespEF
259+ chanPointCD := & lnrpc.ChannelPoint {
260+ OutputIndex : uint32 (fundRespCD .OutputIndex ),
261+ FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
262+ FundingTxidStr : fundRespCD .Txid ,
263+ },
264+ }
265+ chanPointDY := & lnrpc.ChannelPoint {
266+ OutputIndex : uint32 (fundRespDY .OutputIndex ),
267+ FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
268+ FundingTxidStr : fundRespDY .Txid ,
269+ },
270+ }
271+ chanPointEF := & lnrpc.ChannelPoint {
272+ OutputIndex : uint32 (fundRespEF .OutputIndex ),
273+ FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
274+ FundingTxidStr : fundRespEF .Txid ,
275+ },
276+ }
277+
278+ return chanPointCD , chanPointDY , chanPointEF
260279}
261280
262281func assertNumAssetUTXOs (t * testing.T , tapdClient * tapClient ,
@@ -586,6 +605,65 @@ func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
586605 balance .RemoteBalance .Sat
587606}
588607
608+ func fetchChannel (t * testing.T , node * HarnessNode ,
609+ chanPoint * lnrpc.ChannelPoint ) * lnrpc.Channel {
610+
611+ ctxb := context .Background ()
612+ ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
613+ defer cancel ()
614+
615+ channelResp , err := node .ListChannels (ctxt , & lnrpc.ListChannelsRequest {
616+ ActiveOnly : true ,
617+ })
618+ require .NoError (t , err )
619+
620+ chanFundingHash , err := lnrpc .GetChanPointFundingTxid (chanPoint )
621+ require .NoError (t , err )
622+
623+ chanPointStr := fmt .Sprintf ("%v:%v" , chanFundingHash ,
624+ chanPoint .OutputIndex )
625+
626+ var targetChan * lnrpc.Channel
627+ for _ , channel := range channelResp .Channels {
628+ if channel .ChannelPoint == chanPointStr {
629+ targetChan = channel
630+
631+ break
632+ }
633+ }
634+ require .NotNil (t , targetChan )
635+
636+ return targetChan
637+ }
638+
639+ func assertChannelSatBalance (t * testing.T , node * HarnessNode ,
640+ chanPoint * lnrpc.ChannelPoint , local , remote int64 ) {
641+
642+ targetChan := fetchChannel (t , node , chanPoint )
643+
644+ require .InDelta (t , local , targetChan .LocalBalance , 1 )
645+ require .InDelta (t , remote , targetChan .RemoteBalance , 1 )
646+ }
647+
648+ func assertChannelAssetBalance (t * testing.T , node * HarnessNode ,
649+ chanPoint * lnrpc.ChannelPoint , local , remote uint64 ) {
650+
651+ targetChan := fetchChannel (t , node , chanPoint )
652+
653+ var assetBalance rfqmsg.JsonAssetChannel
654+ err := json .Unmarshal (targetChan .CustomChannelData , & assetBalance )
655+ require .NoError (t , err )
656+
657+ require .Len (t , assetBalance .Assets , 1 )
658+
659+ require .InDelta (t , local , assetBalance .Assets [0 ].LocalBalance , 1 )
660+ require .InDelta (t , remote , assetBalance .Assets [0 ].RemoteBalance , 1 )
661+ }
662+
663+ func routingFee (amt lnwire.MilliSatoshi ) lnwire.MilliSatoshi {
664+ return amt + (amt / 1000_000 ) + 1000
665+ }
666+
589667func sendAssetKeySendPayment (t * testing.T , src , dst * HarnessNode , amt uint64 ,
590668 assetID []byte , btcAmt fn.Option [int64 ],
591669 expectedStatus lnrpc.Payment_PaymentStatus ,
@@ -702,9 +780,11 @@ func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode,
702780 })
703781 require .NoError (t , err )
704782
705- return payInvoiceWithAssets (
783+ numUnits , _ := payInvoiceWithAssets (
706784 t , src , rfqPeer , invoiceResp , assetID , smallShards ,
707785 )
786+
787+ return numUnits
708788}
709789
710790func payInvoiceWithSatoshi (t * testing.T , payer * HarnessNode ,
@@ -730,7 +810,7 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode,
730810
731811func payInvoiceWithAssets (t * testing.T , payer , rfqPeer * HarnessNode ,
732812 invoice * lnrpc.AddInvoiceResponse , assetID []byte ,
733- smallShards bool ) uint64 {
813+ smallShards bool ) ( uint64 , rfqmath. BigIntFixedPoint ) {
734814
735815 ctxb := context .Background ()
736816 ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
@@ -774,19 +854,21 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
774854 rate , err := rfqrpc .UnmarshalFixedPoint (rpcRate )
775855 require .NoError (t , err )
776856
857+ t .Logf ("Got quote for %v asset units per BTC" , rate )
858+
777859 amountMsat := lnwire .MilliSatoshi (decodedInvoice .NumMsat )
778860 milliSatsFP := rfqmath .MilliSatoshiToUnits (amountMsat , * rate )
779861 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 " +
862+ msatPerUnit := float64 (decodedInvoice .NumMsat ) / float64 ( numUnits )
863+ t .Logf ("Got quote for %v asset units at %3f msat/unit from peer %s " +
782864 "with SCID %d" , numUnits , msatPerUnit , peerPubKey ,
783865 acceptedQuote .Scid )
784866
785867 result , err := getAssetPaymentResult (stream )
786868 require .NoError (t , err )
787869 require .Equal (t , lnrpc .Payment_SUCCEEDED , result .Status )
788870
789- return numUnits
871+ return numUnits , * rate
790872}
791873
792874func createAssetInvoice (t * testing.T , dstRfqPeer , dst * HarnessNode ,
@@ -825,19 +907,70 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode,
825907 rate , err := rfqrpc .UnmarshalFixedPoint (rpcRate )
826908 require .NoError (t , err )
827909
910+ t .Logf ("Got quote for %v asset units per BTC" , rate )
911+
828912 assetUnits := rfqmath .NewBigIntFixedPoint (assetAmount , 0 )
829913 numMSats := rfqmath .UnitsToMilliSatoshi (assetUnits , * rate )
830- mSatPerUnit := uint64 (decodedInvoice .NumMsat ) / assetAmount
914+ mSatPerUnit := float64 (decodedInvoice .NumMsat ) / float64 ( assetAmount )
831915
832916 require .EqualValues (t , numMSats , decodedInvoice .NumMsat )
833917
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 )
918+ t .Logf ("Got quote for %d mSats at %3f msat/unit from peer %x with " +
919+ "SCID %d" , decodedInvoice .NumMsat , mSatPerUnit ,
920+ dstRfqPeer . PubKey [:], resp .AcceptedBuyQuote .Scid )
837921
838922 return resp .InvoiceResult
839923}
840924
925+ // assertPaymentHtlcAssets makes sure the payment with the given hash shows the
926+ // individual HTLCs that arrived for it and that they show the correct asset
927+ // amounts for the given ID when decoded.
928+ func assertPaymentHtlcAssets (t * testing.T , node * HarnessNode , payHash []byte ,
929+ assetID []byte , assetAmount uint64 ) {
930+
931+ ctxb := context .Background ()
932+ ctxt , cancel := context .WithTimeout (ctxb , defaultTimeout )
933+ defer cancel ()
934+
935+ stream , err := node .RouterClient .TrackPaymentV2 (
936+ ctxt , & routerrpc.TrackPaymentRequest {
937+ PaymentHash : payHash ,
938+ NoInflightUpdates : true ,
939+ },
940+ )
941+ require .NoError (t , err )
942+
943+ payment , err := stream .Recv ()
944+ require .NoError (t , err )
945+ require .NotNil (t , payment )
946+ require .NotEmpty (t , payment .Htlcs )
947+
948+ t .Logf ("Asset payment: %v" , toProtoJSON (t , payment ))
949+
950+ targetID := hex .EncodeToString (assetID )
951+
952+ var totalAssetAmount uint64
953+ for _ , htlc := range payment .Htlcs {
954+ require .NotNil (t , htlc .Route )
955+ require .NotEmpty (t , htlc .Route .CustomChannelData )
956+
957+ jsonHtlc := & rfqmsg.JsonHtlc {}
958+ err := json .Unmarshal (htlc .Route .CustomChannelData , jsonHtlc )
959+ require .NoError (t , err )
960+
961+ for _ , balance := range jsonHtlc .Balances {
962+ if balance .AssetID != targetID {
963+ continue
964+ }
965+
966+ totalAssetAmount += balance .Amount
967+ }
968+ }
969+
970+ // Due to rounding we allow up to 1 unit of error.
971+ require .InDelta (t , assetAmount , totalAssetAmount , 1 )
972+ }
973+
841974func waitForSendEvent (t * testing.T ,
842975 sendEvents taprpc.TaprootAssets_SubscribeSendEventsClient ,
843976 expectedState tapfreighter.SendState ) {
@@ -862,6 +995,14 @@ type coOpCloseBalanceCheck func(t *testing.T, local, remote *HarnessNode,
862995 closeTx * wire.MsgTx , closeUpdate * lnrpc.ChannelCloseUpdate ,
863996 assetID , groupKey []byte , universeTap * tapClient )
864997
998+ // noOpCoOpCloseBalanceCheck is a no-op implementation of the co-op close
999+ // balance check that can be used in tests.
1000+ func noOpCoOpCloseBalanceCheck (_ * testing.T , _ , _ * HarnessNode , _ * wire.MsgTx ,
1001+ _ * lnrpc.ChannelCloseUpdate , _ , _ []byte , _ * tapClient ) {
1002+
1003+ // This is a no-op function.
1004+ }
1005+
8651006// closeAssetChannelAndAssert closes the channel between the local and remote
8661007// node and asserts the final balances of the closing transaction.
8671008func closeAssetChannelAndAssert (t * harnessTest , net * NetworkHarness ,
0 commit comments