diff --git a/go.mod b/go.mod index 06d10a154..af363fa80 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,8 @@ require ( github.com/lightninglabs/pool v0.6.5-beta.0.20241015105339-044cb451b5df github.com/lightninglabs/pool/auctioneerrpc v1.1.2 github.com/lightninglabs/pool/poolrpc v1.0.0 - github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209075049-4a15eb2ab84e - github.com/lightningnetwork/lnd v0.18.4-beta.rc1 + github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209175552-fde673202d0d + github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/fn v1.2.3 github.com/lightningnetwork/lnd/kvdb v1.4.10 @@ -236,6 +236,4 @@ replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-d replace github.com/lightninglabs/lightning-terminal/litrpc => ./litrpc -replace github.com/lightninglabs/aperture => github.com/lightninglabs/aperture v0.3.4-beta - go 1.22.6 diff --git a/go.sum b/go.sum index 445f07ef6..19b15427e 100644 --- a/go.sum +++ b/go.sum @@ -1177,12 +1177,12 @@ github.com/lightninglabs/pool/poolrpc v1.0.0 h1:vvosrgNx9WXF4mcHGqLjZOW8wNM0q+BL github.com/lightninglabs/pool/poolrpc v1.0.0/go.mod h1:ZqpEpBFRMMBAerMmilEjh27tqauSXDwLaLR0O3jvmMA= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209075049-4a15eb2ab84e h1:dFbyAZrkQ9xALiVUf0qikLV2TabucCgPwJZ/Dc6r62Y= -github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209075049-4a15eb2ab84e/go.mod h1:d9GdY5DVoSh6+dEQRS4UUcjpVvZlAHgP8U2DAp4YedA= +github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209175552-fde673202d0d h1:7M7CAHVb2ctF9sgbOkfTJmp2eCY23+LjmwznDt8xDVw= +github.com/lightninglabs/taproot-assets v0.5.0-rc1.0.20241209175552-fde673202d0d/go.mod h1:kIjx0Nppwcpv+CZU6dTMb7iouq5SgPsS0JXhb6a+HPU= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1 h1:z6hFKvtbfo8udPrIb81GbSoKlUWd06d4LRxTkD19IMQ= -github.com/lightningnetwork/lnd v0.18.4-beta.rc1/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5 h1:YJ/DPJd3YyPWmvv5b74KdpCFi3uBMdmDei54AageGpc= +github.com/lightningnetwork/lnd v0.18.4-beta.rc1.0.20241205204908-f312064bfbd5/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= diff --git a/itest/assets_test.go b/itest/assets_test.go index 93695ec2a..82b842d82 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -718,9 +718,12 @@ func addRoutingFee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { } func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64, - assetID []byte, btcAmt fn.Option[int64], - expectedStatus lnrpc.Payment_PaymentStatus, - failReason fn.Option[lnrpc.PaymentFailureReason]) { + assetID []byte, btcAmt fn.Option[int64], opts ...payOpt) { + + cfg := defaultPayConfig() + for _, opt := range opts { + opt(cfg) + } ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -760,12 +763,8 @@ func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64, if result.Status == lnrpc.Payment_FAILED { t.Logf("Failure reason: %v", result.FailureReason) } - require.Equal(t, expectedStatus, result.Status) - - expectedReason := failReason.UnwrapOr( - lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, - ) - require.Equal(t, expectedReason, result.FailureReason) + require.Equal(t, cfg.payStatus, result.Status) + require.Equal(t, cfg.failureReason, result.FailureReason) } func sendKeySendPayment(t *testing.T, src, dst *HarnessNode, @@ -818,9 +817,7 @@ func createAndPayNormalInvoiceWithBtc(t *testing.T, src, dst *HarnessNode, }) require.NoError(t, err) - payInvoiceWithSatoshi( - t, src, invoiceResp, lnrpc.Payment_SUCCEEDED, false, - ) + payInvoiceWithSatoshi(t, src, invoiceResp) } func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode, @@ -846,8 +843,12 @@ func createAndPayNormalInvoice(t *testing.T, src, rfqPeer, dst *HarnessNode, } func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode, - invoice *lnrpc.AddInvoiceResponse, - expectedStatus lnrpc.Payment_PaymentStatus, expectTimeout bool) { + invoice *lnrpc.AddInvoiceResponse, opts ...payOpt) { + + cfg := defaultPayConfig() + for _, opt := range opts { + opt(cfg) + } ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -863,11 +864,12 @@ func payInvoiceWithSatoshi(t *testing.T, payer *HarnessNode, require.NoError(t, err) result, err := getPaymentResult(stream) - if expectTimeout { + if cfg.expectTimeout { require.ErrorContains(t, err, "context deadline exceeded") } else { require.NoError(t, err) - require.Equal(t, expectedStatus, result.Status) + require.Equal(t, cfg.payStatus, result.Status) + require.Equal(t, cfg.failureReason, result.FailureReason) } } @@ -910,6 +912,7 @@ func payInvoiceWithSatoshiLastHop(t *testing.T, payer *HarnessNode, type payConfig struct { smallShards bool + expectTimeout bool payStatus lnrpc.Payment_PaymentStatus failureReason lnrpc.PaymentFailureReason rfq fn.Option[rfqmsg.ID] @@ -918,6 +921,7 @@ type payConfig struct { func defaultPayConfig() *payConfig { return &payConfig{ smallShards: false, + expectTimeout: false, payStatus: lnrpc.Payment_SUCCEEDED, failureReason: lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, } @@ -931,6 +935,12 @@ func withSmallShards() payOpt { } } +func withExpectTimeout() payOpt { + return func(c *payConfig) { + c.expectTimeout = true + } +} + func withFailure(status lnrpc.Payment_PaymentStatus, reason lnrpc.PaymentFailureReason) payOpt { diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index cec25e52f..a7cd81fbe 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -20,6 +20,7 @@ import ( "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" @@ -49,8 +50,6 @@ var ( } shortTimeout = time.Second * 5 - - defaultPaymentStatusOpt = fn.None[lnrpc.Payment_PaymentStatus]() ) var ( @@ -272,8 +271,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness, fabiaInvoiceAssetAmount/2 sendAssetKeySendPayment( t.t, charlie, dave, charlieRemainingBalance, - assetID, fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after keysend") @@ -428,8 +426,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, keySendAmount := charlieFundingAmount sendAssetKeySendPayment( t.t, charlie, dave, charlieFundingAmount, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after keysend") @@ -473,8 +470,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, // Let's keysend the rest of the balance back to Charlie. sendAssetKeySendPayment( t.t, dave, charlie, charlieFundingAmount-charlieInvoiceAmount, - assetID, fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after keysend back") @@ -527,7 +523,9 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, t.t, charlie, dave, daveInvoiceAssetAmount, assetID, ) payInvoiceWithSatoshi( - t.t, charlie, invoiceResp, lnrpc.Payment_FAILED, false, + t.t, charlie, invoiceResp, withFailure( + lnrpc.Payment_FAILED, failureIncorrectDetails, + ), ) logBalance(t.t, nodes, assetID, "after asset invoice paid with sats") @@ -574,9 +572,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, invoiceResp = createAssetInvoice( t.t, erin, fabia, fabiaInvoiceAssetAmount2, assetID, ) - payInvoiceWithSatoshi( - t.t, dave, invoiceResp, lnrpc.Payment_SUCCEEDED, false, - ) + payInvoiceWithSatoshi(t.t, dave, invoiceResp) logBalance(t.t, nodes, assetID, "after invoice") erinAssetBalance -= fabiaInvoiceAssetAmount2 @@ -899,7 +895,6 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness, const keySendAmount = 100 sendAssetKeySendPayment( t.t, charlie, dave, keySendAmount, assetID, fn.None[int64](), - lnrpc.Payment_SUCCEEDED, fn.None[lnrpc.PaymentFailureReason](), ) logBalance(t.t, nodes, assetID, "after keysend") @@ -911,7 +906,6 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness, // an HTLC. sendAssetKeySendPayment( t.t, dave, charlie, keySendAmount, assetID, fn.None[int64](), - lnrpc.Payment_SUCCEEDED, fn.None[lnrpc.PaymentFailureReason](), ) logBalance(t.t, nodes, assetID, "after keysend back") @@ -1008,9 +1002,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness, invoiceResp = createAssetInvoice( t.t, erin, fabia, fabiaInvoiceAssetAmount2, assetID, ) - payInvoiceWithSatoshi( - t.t, dave, invoiceResp, lnrpc.Payment_SUCCEEDED, false, - ) + payInvoiceWithSatoshi(t.t, dave, invoiceResp) logBalance(t.t, nodes, assetID, "after invoice") erinAssetBalance -= fabiaInvoiceAssetAmount2 @@ -1322,8 +1314,7 @@ func testCustomChannelsForceClose(_ context.Context, net *NetworkHarness, for i := 0; i < numPayments; i++ { sendAssetKeySendPayment( t.t, charlie, dave, keySendAmount, assetID, - fn.Some(btcAmt), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + fn.Some(btcAmt), ) } @@ -1652,8 +1643,7 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness, for i := 0; i < numPayments; i++ { sendAssetKeySendPayment( t.t, charlie, dave, keySendAmount, assetID, - fn.Some(btcAmt), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + fn.Some(btcAmt), ) } @@ -1668,8 +1658,6 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness, // just at above. sendAssetKeySendPayment( t.t, charlie, dave, keySendAmount, assetID, fn.Some(btcAmt), - lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), ) logBalance(t.t, nodes, assetID, "after keysend -- final state") @@ -1743,7 +1731,7 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness, // testCustomChannelsLiquidityEdgeCases is a test that runs through some // taproot asset channel liquidity related edge cases. -func testCustomChannelsLiquidityEdgeCases(_ context.Context, +func testCustomChannelsLiquidityEdgeCases(ctxb context.Context, net *NetworkHarness, t *harnessTest) { lndArgs := slices.Clone(lndArgsTemplate) @@ -1866,9 +1854,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // Normal case. // Send 50 assets from Charlie to Dave. sendAssetKeySendPayment( - t.t, charlie, dave, 50, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, charlie, dave, 50, assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after 50 assets") @@ -1891,12 +1877,10 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, timeoutChan := time.After(PaymentTimeout / 2) done := make(chan bool, 1) - //nolint:lll go func() { sendAssetKeySendPayment( - t.t, dave, charlie, 50, assetID, - fn.None[int64](), lnrpc.Payment_FAILED, - fn.Some(lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE), + t.t, dave, charlie, 50, assetID, fn.None[int64](), + withFailure(lnrpc.Payment_FAILED, failureNoRoute), ) done <- true @@ -1918,9 +1902,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // Now Dave tries to send 50 assets again, this time he should have // enough sats. sendAssetKeySendPayment( - t.t, dave, charlie, 50, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, dave, charlie, 50, assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after 50 sats backwards") @@ -1990,9 +1972,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // Dave sends 200k assets and 5k sats to Yara. sendAssetKeySendPayment( - t.t, dave, yara, 2*bigAssetAmount, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, dave, yara, 2*bigAssetAmount, assetID, fn.None[int64](), ) sendKeySendPayment(t.t, dave, yara, 5_000) @@ -2018,9 +1998,9 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // channels, where the total asset value is less than the default anchor // amount of 354 sats. invoiceResp = createAssetInvoice(t.t, dave, charlie, 1, assetID) - payInvoiceWithSatoshi( - t.t, yara, invoiceResp, lnrpc.Payment_FAILED, false, - ) + payInvoiceWithSatoshi(t.t, yara, invoiceResp, withFailure( + lnrpc.Payment_FAILED, failureNoRoute, + )) logBalance(t.t, nodes, assetID, "after small payment (asset "+ "invoice, <354sats)") @@ -2030,11 +2010,8 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // satoshi, where we will check whether Dave's strict forwarding works // as expected. Charlie is only used as a dummy RFQ peer in this case, // Yara totally ignored the RFQ hint and pays agnostically with sats. - invoiceResp = createAssetInvoice( - t.t, charlie, dave, 1, assetID, - ) + invoiceResp = createAssetInvoice(t.t, charlie, dave, 1, assetID) - ctxb := context.Background() stream, err := dave.InvoicesClient.SubscribeSingleInvoice( ctxb, &invoicesrpc.SubscribeSingleInvoiceRequest{ RHash: invoiceResp.RHash, @@ -2060,9 +2037,7 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // We start by sloshing some funds in the Erin<->Fabia. sendAssetKeySendPayment( - t.t, erin, fabia, 100_000, assetID, - fn.Some[int64](20_000), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, erin, fabia, 100_000, assetID, fn.Some[int64](20_000), ) logBalance(t.t, nodes, assetID, "balance after 1st slosh") @@ -2188,7 +2163,10 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // Now Erin tries to pay the invoice. Since rfq quote cannot satisfy the // total amount of the invoice this payment will fail. - payInvoiceWithSatoshi(t.t, erin, iResp, lnrpc.Payment_FAILED, true) + payInvoiceWithSatoshi( + t.t, erin, iResp, withExpectTimeout(), + withFailure(lnrpc.Payment_FAILED, failureNone), + ) logBalance(t.t, nodes, assetID, "after small manual rfq") } @@ -2333,9 +2311,7 @@ func testCustomChannelsBalanceConsistency(_ context.Context, // Normal case. // Send 500 assets from Charlie to Dave. sendAssetKeySendPayment( - t.t, charlie, dave, 500, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, charlie, dave, 500, assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after 500 assets") @@ -2351,9 +2327,7 @@ func testCustomChannelsBalanceConsistency(_ context.Context, // Now Dave tries to send 250 assets. sendAssetKeySendPayment( - t.t, dave, charlie, 250, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + t.t, dave, charlie, 250, assetID, fn.None[int64](), ) logBalance(t.t, nodes, assetID, "after 250 sats backwards") @@ -3012,8 +2986,7 @@ func runCustomChannelsHtlcForceClose(ctxb context.Context, t *harnessTest, for i := 0; i < numPayments; i++ { sendAssetKeySendPayment( t.t, alice, bob, keySendAmount, assetID, - fn.None[int64](), lnrpc.Payment_SUCCEEDED, - fn.None[lnrpc.PaymentFailureReason](), + fn.None[int64](), ) } @@ -3459,3 +3432,223 @@ func runCustomChannelsHtlcForceClose(ctxb context.Context, t *harnessTest, t.t, zaneTap, assetID, zaneExpectedBalance, ) } + +// testCustomChannelsForwardBandwidth is a test that runs through some Taproot +// Assets Channel liquidity edge cases, specifically related to forwarding HTLCs +// into channels with no available asset bandwidth. +func testCustomChannelsForwardBandwidth(ctxb context.Context, + net *NetworkHarness, t *harnessTest) { + + lndArgs := slices.Clone(lndArgsTemplate) + litdArgs := slices.Clone(litdArgsTemplate) + + // Explicitly set the proof courier as Zane (now has no other role + // other than proof shuffling), otherwise a hashmail courier will be + // used. For the funding transaction, we're just posting it and don't + // expect a true receiver. + zane, err := net.NewNode( + t.t, "Zane", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + + litdArgs = append(litdArgs, fmt.Sprintf( + "--taproot-assets.proofcourieraddr=%s://%s", + proof.UniverseRpcCourierType, zane.Cfg.LitAddr(), + )) + + // The topology we are going for looks like the following: + // + // Charlie --[assets]--> Dave --[sats]--> Erin --[assets]--> Fabia + // | + // | + // [assets] + // | + // v + // Yara + // + // With [assets] being a custom channel and [sats] being a normal, BTC + // only channel. + // All 5 nodes need to be full litd nodes running in integrated mode + // with tapd included. We also need specific flags to be enabled, so we + // create 5 completely new nodes, ignoring the two default nodes that + // are created by the harness. + charlie, err := net.NewNode( + t.t, "Charlie", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + + dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...) + require.NoError(t.t, err) + erin, err := net.NewNode(t.t, "Erin", lndArgs, false, true, litdArgs...) + require.NoError(t.t, err) + fabia, err := net.NewNode( + t.t, "Fabia", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + yara, err := net.NewNode( + t.t, "Yara", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + + nodes := []*HarnessNode{charlie, dave, erin, fabia, yara} + connectAllNodes(t.t, net, nodes) + fundAllNodes(t.t, net, nodes) + + // Create the normal channel between Dave and Erin. + t.Logf("Opening normal channel between Dave and Erin...") + channelOp := openChannelAndAssert( + t, net, dave, erin, lntest.OpenChannelParams{ + Amt: 10_000_000, + SatPerVByte: 5, + }, + ) + defer closeChannelAndAssert(t, net, dave, channelOp, false) + + // This is the only public channel, we need everyone to be aware of it. + assertChannelKnown(t.t, charlie, channelOp) + assertChannelKnown(t.t, fabia, channelOp) + + universeTap := newTapClient(t.t, zane) + charlieTap := newTapClient(t.t, charlie) + daveTap := newTapClient(t.t, dave) + erinTap := newTapClient(t.t, erin) + fabiaTap := newTapClient(t.t, fabia) + yaraTap := newTapClient(t.t, yara) + + // Mint an asset on Charlie and sync all nodes to Charlie as the + // universe. + mintedAssets := itest.MintAssetsConfirmBatch( + t.t, t.lndHarness.Miner.Client, charlieTap, + []*mintrpc.MintAssetRequest{ + { + Asset: itestAsset, + }, + }, + ) + cents := mintedAssets[0] + assetID := cents.AssetGenesis.AssetId + + t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount) + syncUniverses(t.t, charlieTap, dave, erin, fabia, yara) + t.Logf("Universes synced between all nodes, distributing assets...") + + const ( + daveFundingAmount = uint64(400_000) + erinFundingAmount = uint64(200_000) + ) + charlieFundingAmount := cents.Amount - uint64(2*400_000) + + _, _, chanPointEF := createTestAssetNetwork( + t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, + universeTap, cents, 400_000, charlieFundingAmount, + daveFundingAmount, erinFundingAmount, 0, + ) + + // Before we start sending out payments, let's make sure each node can + // see the other one in the graph and has all required features. + require.NoError(t.t, t.lndHarness.AssertNodeKnown(charlie, dave)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(dave, charlie)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(dave, yara)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(yara, dave)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(erin, fabia)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(fabia, erin)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(charlie, erin)) + + logBalance(t.t, nodes, assetID, "initial") + + // We now deplete the channel between Erin and Fabia by moving all + // assets to Fabia. + sendAssetKeySendPayment( + t.t, erin, fabia, erinFundingAmount, assetID, fn.None[int64](), + ) + logBalance(t.t, nodes, assetID, "after moving assets to Fabia") + + // Test case 1: We cannot keysend more assets from Erin to Fabia. + sendAssetKeySendPayment( + t.t, erin, fabia, 1, assetID, fn.None[int64](), + withFailure(lnrpc.Payment_FAILED, failureNoBalance), + ) + + // Test case 2: We cannot pay an invoice from Charlie to Fabia. + invoiceResp := createAssetInvoice(t.t, erin, fabia, 123, assetID) + payInvoiceWithSatoshi( + t.t, charlie, invoiceResp, + withFailure(lnrpc.Payment_FAILED, failureNoRoute), + ) + + // Test case 3: We now create an asset buy order for a normal amount of + // assets. We then "fake" an invoice referencing that buy order that + // is for an amount that is too small to be paid with a single asset + // unit. This should be handled gracefully and not lead to a crash. + // Ideally such an invoice shouldn't be created in the first place, but + // we want to make sure that the system doesn't crash in this case. + numUnits := uint64(10) + buyOrderResp, err := fabiaTap.RfqClient.AddAssetBuyOrder( + ctxb, &rfqrpc.AddAssetBuyOrderRequest{ + AssetSpecifier: &rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_AssetId{ + AssetId: assetID, + }, + }, + AssetMaxAmt: numUnits, + Expiry: uint64( + time.Now().Add(time.Hour).Unix(), + ), + PeerPubKey: erin.PubKey[:], + TimeoutSeconds: 10, + }, + ) + require.NoError(t.t, err) + + quoteResp := buyOrderResp.Response + quote, ok := quoteResp.(*rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote) + require.True(t.t, ok) + + // We calculate the milli-satoshi amount one below the equivalent of a + // single asset unit. + rate, err := oraclerpc.UnmarshalFixedPoint(&oraclerpc.FixedPoint{ + Coefficient: quote.AcceptedQuote.AskAssetRate.Coefficient, + Scale: quote.AcceptedQuote.AskAssetRate.Scale, + }) + require.NoError(t.t, err) + + oneUnit := uint64(1) + oneUnitFP := rfqmath.NewBigIntFixedPoint(oneUnit, 0) + oneUnitMilliSat := rfqmath.UnitsToMilliSatoshi(oneUnitFP, *rate) + + t.Logf("Got quote for %v asset units per BTC", rate) + msatPerUnit := float64(oneUnitMilliSat) / float64(oneUnit) + t.Logf("Got quote for %v asset units at %3f msat/unit from peer %s "+ + "with SCID %d", numUnits, msatPerUnit, erin.PubKeyStr, + quote.AcceptedQuote.Scid) + + // We now manually add the invoice in order to inject the above, + // manually generated, quote. + invoiceResp2, err := fabia.AddInvoice(ctxb, &lnrpc.Invoice{ + Memo: "too small invoice", + ValueMsat: int64(oneUnitMilliSat - 1), + RouteHints: []*lnrpc.RouteHint{{ + HopHints: []*lnrpc.HopHint{{ + NodeId: erin.PubKeyStr, + ChanId: quote.AcceptedQuote.Scid, + }}, + }}, + }) + require.NoError(t.t, err) + + payInvoiceWithSatoshi(t.t, dave, invoiceResp2, withFailure( + lnrpc.Payment_FAILED, failureNoRoute, + )) + + // Let's make sure we can still use the channel between Erin and Fabia + // by doing a satoshi keysend payment. + sendKeySendPayment(t.t, erin, fabia, 2000) + logBalance(t.t, nodes, assetID, "after BTC only keysend") + + // Finally, we close the channel between Erin and Fabia to make sure + // everything is settled correctly. + closeAssetChannelAndAssert( + t, net, erin, fabia, chanPointEF, assetID, nil, + universeTap, noOpCoOpCloseBalanceCheck, + ) +} diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index 2bee9ca63..4d2cef88b 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -68,4 +68,8 @@ var allTestCases = []*testCase{ name: "test custom channels fee", test: testCustomChannelsFee, }, + { + name: "test custom channels forward bandwidth", + test: testCustomChannelsForwardBandwidth, + }, } diff --git a/terminal.go b/terminal.go index 05fb256f6..b27e3b2c5 100644 --- a/terminal.go +++ b/terminal.go @@ -40,6 +40,7 @@ import ( "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/funding" + "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -57,7 +58,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/msgmux" - "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sweep" @@ -1370,7 +1370,7 @@ func (g *LightningTerminal) buildAuxComponents() (*lnd.AuxComponents, error) { tapd, ), AuxSigner: fn.Some[lnwallet.AuxSigner](tapd), - TrafficShaper: fn.Some[routing.TlvTrafficShaper](tapd), + TrafficShaper: fn.Some[htlcswitch.AuxTrafficShaper](tapd), AuxDataParser: fn.Some[lnd.AuxDataParser](tapd), AuxChanCloser: fn.Some[chancloser.AuxChanCloser](tapd), AuxSweeper: fn.Some[sweep.AuxSweeper](tapd),