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.
This commit is contained in:
Oliver Gugger 2025-06-16 15:20:09 +02:00
parent 4b45384e84
commit fe9a36ceee
No known key found for this signature in database
GPG key ID: 8E4256593F177720
2 changed files with 54 additions and 2 deletions

View file

@ -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) {

View file

@ -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(