Skip to content

Commit a08e051

Browse files
committed
itest: add bandwidth test
1 parent 08958a2 commit a08e051

File tree

2 files changed

+227
-5
lines changed

2 files changed

+227
-5
lines changed

itest/litd_custom_channels_test.go

Lines changed: 223 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
@@ -1730,7 +1731,7 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness,
17301731

17311732
// testCustomChannelsLiquidityEdgeCases is a test that runs through some
17321733
// taproot asset channel liquidity related edge cases.
1733-
func testCustomChannelsLiquidityEdgeCases(_ context.Context,
1734+
func testCustomChannelsLiquidityEdgeCases(ctxb context.Context,
17341735
net *NetworkHarness, t *harnessTest) {
17351736

17361737
lndArgs := slices.Clone(lndArgsTemplate)
@@ -2009,11 +2010,8 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context,
20092010
// satoshi, where we will check whether Dave's strict forwarding works
20102011
// as expected. Charlie is only used as a dummy RFQ peer in this case,
20112012
// Yara totally ignored the RFQ hint and pays agnostically with sats.
2012-
invoiceResp = createAssetInvoice(
2013-
t.t, charlie, dave, 1, assetID,
2014-
)
2013+
invoiceResp = createAssetInvoice(t.t, charlie, dave, 1, assetID)
20152014

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

itest/litd_test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,8 @@ var allTestCases = []*testCase{
6868
name: "test custom channels fee",
6969
test: testCustomChannelsFee,
7070
},
71+
{
72+
name: "test custom channels forward bandwidth",
73+
test: testCustomChannelsForwardBandwidth,
74+
},
7175
}

0 commit comments

Comments
 (0)