@@ -18,6 +18,8 @@ import (
1818	"github.com/lightninglabs/taproot-assets/tapchannel" 
1919	"github.com/lightninglabs/taproot-assets/taprpc" 
2020	"github.com/lightninglabs/taproot-assets/taprpc/mintrpc" 
21+ 	oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" 
22+ 	"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" 
2123	tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" 
2224	"github.com/lightninglabs/taproot-assets/taprpc/universerpc" 
2325	"github.com/lightninglabs/taproot-assets/tapscript" 
@@ -1751,7 +1753,7 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness,
17511753
17521754// testCustomChannelsLiquidityEdgeCases is a test that runs through some 
17531755// taproot asset channel liquidity related edge cases. 
1754- func  testCustomChannelsLiquidityEdgeCases (_  context.Context ,
1756+ func  testCustomChannelsLiquidityEdgeCases (ctxb  context.Context ,
17551757	net  * NetworkHarness , t  * harnessTest ) {
17561758
17571759	lndArgs  :=  slices .Clone (lndArgsTemplate )
@@ -2039,11 +2041,8 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20392041	// Yara with satoshi. For the last hop we try to settle the invoice in 
20402042	// satoshi, where we will check whether Charlie's strict forwarding 
20412043	// works as expected. 
2042- 	invoiceResp  =  createAssetInvoice (
2043- 		t .t , charlie , dave , 1 , assetID ,
2044- 	)
2044+ 	invoiceResp  =  createAssetInvoice (t .t , charlie , dave , 1 , assetID )
20452045
2046- 	ctxb  :=  context .Background ()
20472046	stream , err  :=  dave .InvoicesClient .SubscribeSingleInvoice (
20482047		ctxb , & invoicesrpc.SubscribeSingleInvoiceRequest {
20492048			RHash : invoiceResp .RHash ,
@@ -3319,3 +3318,220 @@ func runCustomChannelsHtlcForceClose(ctxb context.Context, t *harnessTest,
33193318		t .t , zaneTap , assetID , zaneExpectedBalance ,
33203319	)
33213320}
3321+ 
3322+ // testCustomChannelsForwardBandwidth is a test that runs through some Taproot 
3323+ // Assets Channel liquidity edge cases, specifically related to forwarding HTLCs 
3324+ // into channels with no available asset bandwidth. 
3325+ func  testCustomChannelsForwardBandwidth (ctxb  context.Context ,
3326+ 	net  * NetworkHarness , t  * harnessTest ) {
3327+ 
3328+ 	lndArgs  :=  slices .Clone (lndArgsTemplate )
3329+ 	litdArgs  :=  slices .Clone (litdArgsTemplate )
3330+ 
3331+ 	// Explicitly set the proof courier as Zane (now has no other role 
3332+ 	// other than proof shuffling), otherwise a hashmail courier will be 
3333+ 	// used. For the funding transaction, we're just posting it and don't 
3334+ 	// expect a true receiver. 
3335+ 	zane , err  :=  net .NewNode (
3336+ 		t .t , "Zane" , lndArgs , false , true , litdArgs ... ,
3337+ 	)
3338+ 	require .NoError (t .t , err )
3339+ 
3340+ 	litdArgs  =  append (litdArgs , fmt .Sprintf (
3341+ 		"--taproot-assets.proofcourieraddr=%s://%s" ,
3342+ 		proof .UniverseRpcCourierType , zane .Cfg .LitAddr (),
3343+ 	))
3344+ 
3345+ 	// The topology we are going for looks like the following: 
3346+ 	// 
3347+ 	// Charlie  --[assets]-->  Dave  --[sats]-->  Erin  --[assets]-->  Fabia 
3348+ 	//                          | 
3349+ 	//                          | 
3350+ 	//                       [assets] 
3351+ 	//                          | 
3352+ 	//                          v 
3353+ 	//                        Yara 
3354+ 	// 
3355+ 	// With [assets] being a custom channel and [sats] being a normal, BTC 
3356+ 	// only channel. 
3357+ 	// All 5 nodes need to be full litd nodes running in integrated mode 
3358+ 	// with tapd included. We also need specific flags to be enabled, so we 
3359+ 	// create 5 completely new nodes, ignoring the two default nodes that 
3360+ 	// are created by the harness. 
3361+ 	charlie , err  :=  net .NewNode (
3362+ 		t .t , "Charlie" , lndArgs , false , true , litdArgs ... ,
3363+ 	)
3364+ 	require .NoError (t .t , err )
3365+ 
3366+ 	dave , err  :=  net .NewNode (t .t , "Dave" , lndArgs , false , true , litdArgs ... )
3367+ 	require .NoError (t .t , err )
3368+ 	erin , err  :=  net .NewNode (t .t , "Erin" , lndArgs , false , true , litdArgs ... )
3369+ 	require .NoError (t .t , err )
3370+ 	fabia , err  :=  net .NewNode (
3371+ 		t .t , "Fabia" , lndArgs , false , true , litdArgs ... ,
3372+ 	)
3373+ 	require .NoError (t .t , err )
3374+ 	yara , err  :=  net .NewNode (
3375+ 		t .t , "Yara" , lndArgs , false , true , litdArgs ... ,
3376+ 	)
3377+ 	require .NoError (t .t , err )
3378+ 
3379+ 	nodes  :=  []* HarnessNode {charlie , dave , erin , fabia , yara }
3380+ 	connectAllNodes (t .t , net , nodes )
3381+ 	fundAllNodes (t .t , net , nodes )
3382+ 
3383+ 	// Create the normal channel between Dave and Erin. 
3384+ 	t .Logf ("Opening normal channel between Dave and Erin..." )
3385+ 	channelOp  :=  openChannelAndAssert (
3386+ 		t , net , dave , erin , lntest.OpenChannelParams {
3387+ 			Amt :         10_000_000 ,
3388+ 			SatPerVByte : 5 ,
3389+ 		},
3390+ 	)
3391+ 	defer  closeChannelAndAssert (t , net , dave , channelOp , false )
3392+ 
3393+ 	// This is the only public channel, we need everyone to be aware of it. 
3394+ 	assertChannelKnown (t .t , charlie , channelOp )
3395+ 	assertChannelKnown (t .t , fabia , channelOp )
3396+ 
3397+ 	universeTap  :=  newTapClient (t .t , zane )
3398+ 	charlieTap  :=  newTapClient (t .t , charlie )
3399+ 	daveTap  :=  newTapClient (t .t , dave )
3400+ 	erinTap  :=  newTapClient (t .t , erin )
3401+ 	fabiaTap  :=  newTapClient (t .t , fabia )
3402+ 	yaraTap  :=  newTapClient (t .t , yara )
3403+ 
3404+ 	// Mint an asset on Charlie and sync all nodes to Charlie as the 
3405+ 	// universe. 
3406+ 	mintedAssets  :=  itest .MintAssetsConfirmBatch (
3407+ 		t .t , t .lndHarness .Miner .Client , charlieTap ,
3408+ 		[]* mintrpc.MintAssetRequest {
3409+ 			{
3410+ 				Asset : itestAsset ,
3411+ 			},
3412+ 		},
3413+ 	)
3414+ 	cents  :=  mintedAssets [0 ]
3415+ 	assetID  :=  cents .AssetGenesis .AssetId 
3416+ 
3417+ 	t .Logf ("Minted %d lightning cents, syncing universes..." , cents .Amount )
3418+ 	syncUniverses (t .t , charlieTap , dave , erin , fabia , yara )
3419+ 	t .Logf ("Universes synced between all nodes, distributing assets..." )
3420+ 
3421+ 	const  (
3422+ 		daveFundingAmount  =  uint64 (400_000 )
3423+ 		erinFundingAmount  =  uint64 (200_000 )
3424+ 	)
3425+ 	charlieFundingAmount  :=  cents .Amount  -  uint64 (2 * 400_000 )
3426+ 
3427+ 	_ , _ , chanPointEF  :=  createTestAssetNetwork (
3428+ 		t , net , charlieTap , daveTap , erinTap , fabiaTap , yaraTap ,
3429+ 		universeTap , cents , 400_000 , charlieFundingAmount ,
3430+ 		daveFundingAmount , erinFundingAmount , 0 ,
3431+ 	)
3432+ 
3433+ 	// Before we start sending out payments, let's make sure each node can 
3434+ 	// see the other one in the graph and has all required features. 
3435+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (charlie , dave ))
3436+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (dave , charlie ))
3437+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (dave , yara ))
3438+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (yara , dave ))
3439+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (erin , fabia ))
3440+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (fabia , erin ))
3441+ 	require .NoError (t .t , t .lndHarness .AssertNodeKnown (charlie , erin ))
3442+ 
3443+ 	logBalance (t .t , nodes , assetID , "initial" )
3444+ 
3445+ 	// We now deplete the channel between Erin and Fabia by moving all 
3446+ 	// assets to Fabia. 
3447+ 	sendAssetKeySendPayment (
3448+ 		t .t , erin , fabia , erinFundingAmount , assetID ,
3449+ 		fn .None [int64 ](), lnrpc .Payment_SUCCEEDED ,
3450+ 		fn .None [lnrpc.PaymentFailureReason ](),
3451+ 	)
3452+ 	logBalance (t .t , nodes , assetID , "after moving assets to Fabia" )
3453+ 
3454+ 	// Test case 1: We cannot keysend more assets from Erin to Fabia. 
3455+ 	sendAssetKeySendPayment (
3456+ 		t .t , erin , fabia , 1 , assetID , fn .None [int64 ](),
3457+ 		lnrpc .Payment_FAILED , fn .Some (errNoBalance ),
3458+ 	)
3459+ 
3460+ 	// Test case 2: We cannot pay an invoice from Charlie to Fabia. 
3461+ 	invoiceResp  :=  createAssetInvoice (t .t , erin , fabia , 123 , assetID )
3462+ 	payInvoiceWithSatoshi (
3463+ 		t .t , charlie , invoiceResp , lnrpc .Payment_FAILED ,
3464+ 		fn .Some (errNoRoute ),
3465+ 	)
3466+ 
3467+ 	// Test case 3: We now create an asset buy order for a normal amount of 
3468+ 	// assets. We then "fake" an invoice referencing that buy order that 
3469+ 	// is for an amount that is too small to be paid with a single asset 
3470+ 	// unit. This should be handled gracefully and not lead to a crash. 
3471+ 	// Ideally such an invoice shouldn't be created in the first place, but 
3472+ 	// we want to make sure that the system doesn't crash in this case. 
3473+ 	numUnits  :=  uint64 (10 )
3474+ 	buyOrderResp , err  :=  fabiaTap .RfqClient .AddAssetBuyOrder (
3475+ 		ctxb , & rfqrpc.AddAssetBuyOrderRequest {
3476+ 			AssetSpecifier : & rfqrpc.AssetSpecifier {
3477+ 				Id : & rfqrpc.AssetSpecifier_AssetId {
3478+ 					AssetId : assetID ,
3479+ 				},
3480+ 			},
3481+ 			AssetMaxAmt : numUnits ,
3482+ 			Expiry : uint64 (
3483+ 				time .Now ().Add (time .Hour ).Unix (),
3484+ 			),
3485+ 			PeerPubKey :     erin .PubKey [:],
3486+ 			TimeoutSeconds : 10 ,
3487+ 		},
3488+ 	)
3489+ 	require .NoError (t .t , err )
3490+ 
3491+ 	quoteResp  :=  buyOrderResp .Response 
3492+ 	quote , ok  :=  quoteResp .(* rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote )
3493+ 	require .True (t .t , ok )
3494+ 
3495+ 	// We calculate the milli-satoshi amount one below the equivalent of a 
3496+ 	// single asset unit. 
3497+ 	rate , err  :=  oraclerpc .UnmarshalFixedPoint (& oraclerpc.FixedPoint {
3498+ 		Coefficient : quote .AcceptedQuote .AskAssetRate .Coefficient ,
3499+ 		Scale :       quote .AcceptedQuote .AskAssetRate .Scale ,
3500+ 	})
3501+ 	require .NoError (t .t , err )
3502+ 
3503+ 	oneUnit  :=  rfqmath .NewBigIntFixedPoint (1 , 0 )
3504+ 	oneUnitMilliSat  :=  rfqmath .UnitsToMilliSatoshi (oneUnit , * rate )
3505+ 
3506+ 	t .Logf ("Got quote for %v asset units per BTC" , rate )
3507+ 	msatPerUnit  :=  float64 (oneUnitMilliSat ) /  float64 (1 )
3508+ 	t .Logf ("Got quote for %v asset units at %3f msat/unit from peer %s " + 
3509+ 		"with SCID %d" , numUnits , msatPerUnit , erin .PubKeyStr ,
3510+ 		quote .AcceptedQuote .Scid )
3511+ 
3512+ 	// We now manually add the invoice in order to inject the above, 
3513+ 	// manually generated, quote. 
3514+ 	invoiceResp2 , err  :=  fabia .AddInvoice (ctxb , & lnrpc.Invoice {
3515+ 		Memo :      "too small invoice" ,
3516+ 		ValueMsat : int64 (oneUnitMilliSat  -  1 ),
3517+ 		RouteHints : []* lnrpc.RouteHint {{
3518+ 			HopHints : []* lnrpc.HopHint {{
3519+ 				NodeId : erin .PubKeyStr ,
3520+ 				ChanId : quote .AcceptedQuote .Scid ,
3521+ 			}},
3522+ 		}},
3523+ 	})
3524+ 	require .NoError (t .t , err )
3525+ 
3526+ 	payInvoiceWithSatoshi (
3527+ 		t .t , dave , invoiceResp2 , lnrpc .Payment_FAILED ,
3528+ 		fn .Some (errNoRoute ),
3529+ 	)
3530+ 
3531+ 	// Finally, we close the channel between Erin and Fabia to make sure 
3532+ 	// everything is settled correctly. 
3533+ 	closeAssetChannelAndAssert (
3534+ 		t , net , erin , fabia , chanPointEF , assetID , nil ,
3535+ 		universeTap , noOpCoOpCloseBalanceCheck ,
3536+ 	)
3537+ }
0 commit comments