@@ -20,6 +20,7 @@ import (
2020	"github.com/lightninglabs/taproot-assets/tapchannel" 
2121	"github.com/lightninglabs/taproot-assets/taprpc" 
2222	"github.com/lightninglabs/taproot-assets/taprpc/mintrpc" 
23+ 	oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" 
2324	"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" 
2425	tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" 
2526	"github.com/lightninglabs/taproot-assets/taprpc/universerpc" 
@@ -1744,7 +1745,7 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness,
17441745
17451746// testCustomChannelsLiquidityEdgeCases is a test that runs through some 
17461747// taproot asset channel liquidity related edge cases. 
1747- func  testCustomChannelsLiquidityEdgeCases (_  context.Context ,
1748+ func  testCustomChannelsLiquidityEdgeCases (ctxb  context.Context ,
17481749	net  * NetworkHarness , t  * harnessTest ) {
17491750
17501751	lndArgs  :=  slices .Clone (lndArgsTemplate )
@@ -2032,11 +2033,8 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20322033	// satoshi, where we will check whether Dave's strict forwarding works 
20332034	// as expected. Charlie is only used as a dummy RFQ peer in this case, 
20342035	// Yara totally ignored the RFQ hint and pays agnostically with sats. 
2035- 	invoiceResp  =  createAssetInvoice (
2036- 		t .t , charlie , dave , 1 , assetID ,
2037- 	)
2036+ 	invoiceResp  =  createAssetInvoice (t .t , charlie , dave , 1 , assetID )
20382037
2039- 	ctxb  :=  context .Background ()
20402038	stream , err  :=  dave .InvoicesClient .SubscribeSingleInvoice (
20412039		ctxb , & invoicesrpc.SubscribeSingleInvoiceRequest {
20422040			RHash : invoiceResp .RHash ,
@@ -3461,3 +3459,225 @@ func runCustomChannelsHtlcForceClose(ctxb context.Context, t *harnessTest,
34613459		t .t , zaneTap , assetID , zaneExpectedBalance ,
34623460	)
34633461}
3462+ 
3463+ // testCustomChannelsForwardBandwidth is a test that runs through some Taproot 
3464+ // Assets Channel liquidity edge cases, specifically related to forwarding HTLCs 
3465+ // into channels with no available asset bandwidth. 
3466+ func  testCustomChannelsForwardBandwidth (ctxb  context.Context ,
3467+ 	net  * NetworkHarness , t  * harnessTest ) {
3468+ 
3469+ 	lndArgs  :=  slices .Clone (lndArgsTemplate )
3470+ 	litdArgs  :=  slices .Clone (litdArgsTemplate )
3471+ 
3472+ 	// Explicitly set the proof courier as Zane (now has no other role 
3473+ 	// other than proof shuffling), otherwise a hashmail courier will be 
3474+ 	// used. For the funding transaction, we're just posting it and don't 
3475+ 	// expect a true receiver. 
3476+ 	zane , err  :=  net .NewNode (
3477+ 		t .t , "Zane" , lndArgs , false , true , litdArgs ... ,
3478+ 	)
3479+ 	require .NoError (t .t , err )
3480+ 
3481+ 	litdArgs  =  append (litdArgs , fmt .Sprintf (
3482+ 		"--taproot-assets.proofcourieraddr=%s://%s" ,
3483+ 		proof .UniverseRpcCourierType , zane .Cfg .LitAddr (),
3484+ 	))
3485+ 
3486+ 	// The topology we are going for looks like the following: 
3487+ 	// 
3488+ 	// Charlie  --[assets]-->  Dave  --[sats]-->  Erin  --[assets]-->  Fabia 
3489+ 	//                          | 
3490+ 	//                          | 
3491+ 	//                       [assets] 
3492+ 	//                          | 
3493+ 	//                          v 
3494+ 	//                        Yara 
3495+ 	// 
3496+ 	// With [assets] being a custom channel and [sats] being a normal, BTC 
3497+ 	// only channel. 
3498+ 	// All 5 nodes need to be full litd nodes running in integrated mode 
3499+ 	// with tapd included. We also need specific flags to be enabled, so we 
3500+ 	// create 5 completely new nodes, ignoring the two default nodes that 
3501+ 	// are created by the harness. 
3502+ 	charlie , err  :=  net .NewNode (
3503+ 		t .t , "Charlie" , lndArgs , false , true , litdArgs ... ,
3504+ 	)
3505+ 	require .NoError (t .t , err )
3506+ 
3507+ 	dave , err  :=  net .NewNode (t .t , "Dave" , lndArgs , false , true , litdArgs ... )
3508+ 	require .NoError (t .t , err )
3509+ 	erin , err  :=  net .NewNode (t .t , "Erin" , lndArgs , false , true , litdArgs ... )
3510+ 	require .NoError (t .t , err )
3511+ 	fabia , err  :=  net .NewNode (
3512+ 		t .t , "Fabia" , lndArgs , false , true , litdArgs ... ,
3513+ 	)
3514+ 	require .NoError (t .t , err )
3515+ 	yara , err  :=  net .NewNode (
3516+ 		t .t , "Yara" , lndArgs , false , true , litdArgs ... ,
3517+ 	)
3518+ 	require .NoError (t .t , err )
3519+ 
3520+ 	nodes  :=  []* HarnessNode {charlie , dave , erin , fabia , yara }
3521+ 	connectAllNodes (t .t , net , nodes )
3522+ 	fundAllNodes (t .t , net , nodes )
3523+ 
3524+ 	// Create the normal channel between Dave and Erin. 
3525+ 	t .Logf ("Opening normal channel between Dave and Erin..." )
3526+ 	channelOp  :=  openChannelAndAssert (
3527+ 		t , net , dave , erin , lntest.OpenChannelParams {
3528+ 			Amt :         10_000_000 ,
3529+ 			SatPerVByte : 5 ,
3530+ 		},
3531+ 	)
3532+ 	defer  closeChannelAndAssert (t , net , dave , channelOp , false )
3533+ 
3534+ 	// This is the only public channel, we need everyone to be aware of it. 
3535+ 	assertChannelKnown (t .t , charlie , channelOp )
3536+ 	assertChannelKnown (t .t , fabia , channelOp )
3537+ 
3538+ 	universeTap  :=  newTapClient (t .t , zane )
3539+ 	charlieTap  :=  newTapClient (t .t , charlie )
3540+ 	daveTap  :=  newTapClient (t .t , dave )
3541+ 	erinTap  :=  newTapClient (t .t , erin )
3542+ 	fabiaTap  :=  newTapClient (t .t , fabia )
3543+ 	yaraTap  :=  newTapClient (t .t , yara )
3544+ 
3545+ 	// Mint an asset on Charlie and sync all nodes to Charlie as the 
3546+ 	// universe. 
3547+ 	mintedAssets  :=  itest .MintAssetsConfirmBatch (
3548+ 		t .t , t .lndHarness .Miner .Client , charlieTap ,
3549+ 		[]* mintrpc.MintAssetRequest {
3550+ 			{
3551+ 				Asset : itestAsset ,
3552+ 			},
3553+ 		},
3554+ 	)
3555+ 	cents  :=  mintedAssets [0 ]
3556+ 	assetID  :=  cents .AssetGenesis .AssetId 
3557+ 
3558+ 	t .Logf ("Minted %d lightning cents, syncing universes..." , cents .Amount )
3559+ 	syncUniverses (t .t , charlieTap , dave , erin , fabia , yara )
3560+ 	t .Logf ("Universes synced between all nodes, distributing assets..." )
3561+ 
3562+ 	const  (
3563+ 		daveFundingAmount  =  uint64 (400_000 )
3564+ 		erinFundingAmount  =  uint64 (200_000 )
3565+ 	)
3566+ 	charlieFundingAmount  :=  cents .Amount  -  uint64 (2 * 400_000 )
3567+ 
3568+ 	_ , _ , chanPointEF  :=  createTestAssetNetwork (
3569+ 		t , net , charlieTap , daveTap , erinTap , fabiaTap , yaraTap ,
3570+ 		universeTap , cents , 400_000 , charlieFundingAmount ,
3571+ 		daveFundingAmount , erinFundingAmount , 0 ,
3572+ 	)
3573+ 
3574+ 	// Before we start sending out payments, let's make sure each node can 
3575+ 	// see the other one in the graph and has all required features. 
3576+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (charlie , dave ))
3577+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (dave , charlie ))
3578+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (dave , yara ))
3579+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (yara , dave ))
3580+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (erin , fabia ))
3581+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (fabia , erin ))
3582+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (charlie , erin ))
3583+ 
3584+ 	logBalance (t .t , nodes , assetID , "initial" )
3585+ 
3586+ 	// We now deplete the channel between Erin and Fabia by moving all 
3587+ 	// assets to Fabia. 
3588+ 	sendAssetKeySendPayment (
3589+ 		t .t , erin , fabia , erinFundingAmount , assetID ,
3590+ 		fn .None [int64 ](), lnrpc .Payment_SUCCEEDED ,
3591+ 		fn .None [lnrpc.PaymentFailureReason ](),
3592+ 	)
3593+ 	logBalance (t .t , nodes , assetID , "after moving assets to Fabia" )
3594+ 
3595+ 	// Test case 1: We cannot keysend more assets from Erin to Fabia. 
3596+ 	sendAssetKeySendPayment (
3597+ 		t .t , erin , fabia , 1 , assetID , fn .None [int64 ](),
3598+ 		lnrpc .Payment_FAILED , fn .Some (errNoBalance ),
3599+ 	)
3600+ 
3601+ 	// Test case 2: We cannot pay an invoice from Charlie to Fabia. 
3602+ 	invoiceResp  :=  createAssetInvoice (t .t , erin , fabia , 123 , assetID )
3603+ 	payInvoiceWithSatoshi (
3604+ 		t .t , charlie , invoiceResp , lnrpc .Payment_FAILED ,
3605+ 		fn .Some (errNoRoute ),
3606+ 	)
3607+ 
3608+ 	// Test case 3: We now create an asset buy order for a normal amount of 
3609+ 	// assets. We then "fake" an invoice referencing that buy order that 
3610+ 	// is for an amount that is too small to be paid with a single asset 
3611+ 	// unit. This should be handled gracefully and not lead to a crash. 
3612+ 	// Ideally such an invoice shouldn't be created in the first place, but 
3613+ 	// we want to make sure that the system doesn't crash in this case. 
3614+ 	numUnits  :=  uint64 (10 )
3615+ 	buyOrderResp , err  :=  fabiaTap .RfqClient .AddAssetBuyOrder (
3616+ 		ctxb , & rfqrpc.AddAssetBuyOrderRequest {
3617+ 			AssetSpecifier : & rfqrpc.AssetSpecifier {
3618+ 				Id : & rfqrpc.AssetSpecifier_AssetId {
3619+ 					AssetId : assetID ,
3620+ 				},
3621+ 			},
3622+ 			AssetMaxAmt : numUnits ,
3623+ 			Expiry : uint64 (
3624+ 				time .Now ().Add (time .Hour ).Unix (),
3625+ 			),
3626+ 			PeerPubKey :     erin .PubKey [:],
3627+ 			TimeoutSeconds : 10 ,
3628+ 		},
3629+ 	)
3630+ 	require .NoError (t .t , err )
3631+ 
3632+ 	quoteResp  :=  buyOrderResp .Response 
3633+ 	quote , ok  :=  quoteResp .(* rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote )
3634+ 	require .True (t .t , ok )
3635+ 
3636+ 	// We calculate the milli-satoshi amount one below the equivalent of a 
3637+ 	// single asset unit. 
3638+ 	rate , err  :=  oraclerpc .UnmarshalFixedPoint (& oraclerpc.FixedPoint {
3639+ 		Coefficient : quote .AcceptedQuote .AskAssetRate .Coefficient ,
3640+ 		Scale :       quote .AcceptedQuote .AskAssetRate .Scale ,
3641+ 	})
3642+ 	require .NoError (t .t , err )
3643+ 
3644+ 	oneUnit  :=  rfqmath .NewBigIntFixedPoint (1 , 0 )
3645+ 	oneUnitMilliSat  :=  rfqmath .UnitsToMilliSatoshi (oneUnit , * rate )
3646+ 
3647+ 	t .Logf ("Got quote for %v asset units per BTC" , rate )
3648+ 	msatPerUnit  :=  float64 (oneUnitMilliSat ) /  float64 (1 )
3649+ 	t .Logf ("Got quote for %v asset units at %3f msat/unit from peer %s " + 
3650+ 		"with SCID %d" , numUnits , msatPerUnit , erin .PubKeyStr ,
3651+ 		quote .AcceptedQuote .Scid )
3652+ 
3653+ 	// We now manually add the invoice in order to inject the above, 
3654+ 	// manually generated, quote. 
3655+ 	invoiceResp2 , err  :=  fabia .AddInvoice (ctxb , & lnrpc.Invoice {
3656+ 		Memo :      "too small invoice" ,
3657+ 		ValueMsat : int64 (oneUnitMilliSat  -  1 ),
3658+ 		RouteHints : []* lnrpc.RouteHint {{
3659+ 			HopHints : []* lnrpc.HopHint {{
3660+ 				NodeId : erin .PubKeyStr ,
3661+ 				ChanId : quote .AcceptedQuote .Scid ,
3662+ 			}},
3663+ 		}},
3664+ 	})
3665+ 	require .NoError (t .t , err )
3666+ 
3667+ 	payInvoiceWithSatoshi (
3668+ 		t .t , dave , invoiceResp2 , lnrpc .Payment_FAILED ,
3669+ 		fn .Some (errNoRoute ),
3670+ 	)
3671+ 
3672+ 	// Let's make sure we can still use the channel between Erin and Fabia 
3673+ 	// by doing a satoshi keysend payment. 
3674+ 	sendKeySendPayment (t .t , erin , fabia , 2000 )
3675+ 	logBalance (t .t , nodes , assetID , "after BTC only keysend" )
3676+ 
3677+ 	// Finally, we close the channel between Erin and Fabia to make sure 
3678+ 	// everything is settled correctly. 
3679+ 	closeAssetChannelAndAssert (
3680+ 		t , net , erin , fabia , chanPointEF , assetID , nil ,
3681+ 		universeTap , noOpCoOpCloseBalanceCheck ,
3682+ 	)
3683+ }
0 commit comments