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