From fe9a36ceeea0379dea7db5f3af76eb15ee1f1114 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 16 Jun 2025 15:20:09 +0200 Subject: [PATCH] itest: test single sat keysend payment with no asset balance Tests an edge case that previously lead to a force close due to the following error: unable to sort commitment transaction: output and allocation size mismatch with error Having a below-dust satoshi balance is only allowed when there is no asset balance. But since such a dust output isn't materialized on-chain, tapd needs to filter it out correctly and not create an allocation. --- itest/assets_test.go | 23 ++++++++++++++++++++- itest/litd_custom_channels_test.go | 33 +++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/itest/assets_test.go b/itest/assets_test.go index b6786878..e6de75fc 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -1364,7 +1364,12 @@ func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64, } func sendKeySendPayment(t *testing.T, src, dst *HarnessNode, - amt btcutil.Amount) { + amt btcutil.Amount, opts ...payOpt) { + + cfg := defaultPayConfig() + for _, opt := range opts { + opt(cfg) + } ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -1382,12 +1387,21 @@ func sendKeySendPayment(t *testing.T, src, dst *HarnessNode, customRecords := make(map[uint64][]byte) customRecords[record.KeySendType] = preimage[:] + for key, value := range cfg.destCustomRecords { + customRecords[key] = value + } + req := &routerrpc.SendPaymentRequest{ Dest: dst.PubKey[:], Amt: int64(amt), DestCustomRecords: customRecords, PaymentHash: hash[:], TimeoutSeconds: int32(PaymentTimeout.Seconds()), + FeeLimitMsat: 1_000_000, + MaxParts: cfg.maxShards, + OutgoingChanIds: cfg.outgoingChanIDs, + AllowSelfPayment: cfg.allowSelfPayment, + RouteHints: cfg.routeHints, } stream, err := src.RouterClient.SendPaymentV2(ctxt, req) @@ -1548,6 +1562,7 @@ type payConfig struct { groupKey []byte outgoingChanIDs []uint64 allowSelfPayment bool + routeHints []*lnrpc.RouteHint } func defaultPayConfig() *payConfig { @@ -1631,6 +1646,12 @@ func withAllowSelfPayment() payOpt { } } +func withPayRouteHints(hints []*lnrpc.RouteHint) payOpt { + return func(c *payConfig) { + c.routeHints = hints + } +} + func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode, payReq string, assetID []byte, opts ...payOpt) (uint64, rfqmath.BigIntFixedPoint) { diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index f6b746fa..ac1f2f55 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -2389,7 +2389,7 @@ func testCustomChannelsLiquidityEdgeCasesCore(ctx context.Context, ) charlieFundingAmount := cents.Amount - uint64(2*400_000) - _, _, _ = createTestAssetNetwork( + _, _, chanPointEF := createTestAssetNetwork( t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, universeTap, cents, 400_000, charlieFundingAmount, daveFundingAmount, erinFundingAmount, 0, @@ -2407,6 +2407,37 @@ func testCustomChannelsLiquidityEdgeCasesCore(ctx context.Context, logBalance(t.t, nodes, assetID, "initial") + // Edge case: We send a single satoshi keysend payment from Dave to + // Fabia. Which will make it so that Fabia's balance in the channel + // between Erin and her is 1 satoshi, which is below the dust limit. + // This is only allowed while Fabia doesn't have any assets on her side + // yet. + erinFabiaChan := fetchChannel(t.t, fabia, chanPointEF) + hinEF := &lnrpc.HopHint{ + NodeId: erin.PubKeyStr, + ChanId: erinFabiaChan.PeerScidAlias, + CltvExpiryDelta: 80, + FeeBaseMsat: 1000, + FeeProportionalMillionths: 1, + } + sendKeySendPayment( + t.t, dave, fabia, 1, withPayRouteHints([]*lnrpc.RouteHint{{ + HopHints: []*lnrpc.HopHint{hinEF}, + }}), + ) + logBalance(t.t, nodes, assetID, "after single sat keysend") + + // We make sure that a single sat keysend payment is not allowed when + // it carries assets. + sendAssetKeySendPayment( + t.t, erin, fabia, 123, assetID, fn.Some[int64](1), + withPayErrSubStr( + fmt.Sprintf("keysend payment satoshi amount must be "+ + "greater than or equal to %d satoshis", + rfqmath.DefaultOnChainHtlcSat), + ), + ) + // Normal case. // Send 50 assets from Charlie to Dave. sendAssetKeySendPayment(