mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopd: account for channel reserves in loop out validation
Use each channel's local balance minus its reserve when simulating Loop Out payment bandwidth, clamping the result at zero. This prevents swaps from starting when the gross balance covers the amount and routing fee but the spendable balance does not. Add one-sat boundary tests and a bug-fix release note. Leave a TODO for server-fee and prepay capacity because enforcing their maximum fee caps could reject viable swaps whose actual routing fees are lower.
This commit is contained in:
parent
1b81a06b42
commit
95e184a40f
3 changed files with 72 additions and 6 deletions
|
|
@ -3013,6 +3013,13 @@ func validateLoopOutRequest(ctx context.Context, lnd lndclient.LightningClient,
|
|||
// the amount requested, the maximum possible routing fees,
|
||||
// the available channel set and the fact that equal splitting is
|
||||
// used for MPP.
|
||||
//
|
||||
// TODO: Also account for the quoted server fee and the concurrent prepay
|
||||
// payment. The CLI and autoloop obtain the fee and prepay amounts from a
|
||||
// quote, but this RPC only carries maximum limits, which direct callers may
|
||||
// set higher than the quoted amounts. Treating those limits as exact can
|
||||
// reject a viable swap, while the actual routing fees are only known when
|
||||
// the invoices are paid.
|
||||
requiredBalance := btcutil.Amount(req.Amt + req.MaxSwapRoutingFee)
|
||||
isRoutable, _ := hasBandwidth(activeChannelSet, requiredBalance,
|
||||
int(maxParts))
|
||||
|
|
@ -3047,11 +3054,24 @@ func hasBandwidth(channels []lndclient.ChannelInfo, amt btcutil.Amount,
|
|||
localBalances := make([]btcutil.Amount, len(channels))
|
||||
var totalBandwidth btcutil.Amount
|
||||
for i, channel := range channels {
|
||||
tracef("Channel %v: local=%v remote=%v", channel.ChannelID,
|
||||
channel.LocalBalance, channel.RemoteBalance)
|
||||
localBalance := channel.LocalBalance
|
||||
var reserve btcutil.Amount
|
||||
if channel.LocalConstraints != nil {
|
||||
reserve = channel.LocalConstraints.Reserve
|
||||
}
|
||||
|
||||
localBalances[i] = channel.LocalBalance
|
||||
totalBandwidth += channel.LocalBalance
|
||||
if reserve >= localBalance {
|
||||
localBalance = 0
|
||||
} else {
|
||||
localBalance -= reserve
|
||||
}
|
||||
|
||||
tracef("Channel %v: local=%v reserve=%v available=%v "+
|
||||
"remote=%v", channel.ChannelID, channel.LocalBalance,
|
||||
reserve, localBalance, channel.RemoteBalance)
|
||||
|
||||
localBalances[i] = localBalance
|
||||
totalBandwidth += localBalance
|
||||
}
|
||||
|
||||
tracef("Total bandwidth: %v", totalBandwidth)
|
||||
|
|
@ -3073,8 +3093,6 @@ func hasBandwidth(channels []lndclient.ChannelInfo, amt btcutil.Amount,
|
|||
|
||||
paid := false
|
||||
for i := range len(localBalances) {
|
||||
// TODO(hieblmi): Consider channel reserves because the
|
||||
// channel can't send its full local balance.
|
||||
if localBalances[i] >= split {
|
||||
tracef("len(shards)=%v: Local channel "+
|
||||
"balance %v can pay %v sats",
|
||||
|
|
|
|||
|
|
@ -1378,6 +1378,50 @@ func TestValidateLoopOutRequest(t *testing.T) {
|
|||
err: errBalanceTooLow,
|
||||
expectedTarget: 0,
|
||||
},
|
||||
{
|
||||
name: "channel reserve leaves balance one sat short",
|
||||
chain: chaincfg.MainNetParams,
|
||||
destAddr: mainnetAddr,
|
||||
label: "label ok",
|
||||
confTarget: 2,
|
||||
channels: []lndclient.ChannelInfo{
|
||||
{
|
||||
Active: true,
|
||||
ChannelID: chanID2.ToUint64(),
|
||||
LocalBalance: 10100,
|
||||
LocalConstraints: &lndclient.ChannelConstraints{
|
||||
Reserve: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
amount: 10000,
|
||||
maxRoutingFee: 1,
|
||||
maxParts: 1,
|
||||
err: errBalanceTooLow,
|
||||
expectedTarget: 0,
|
||||
},
|
||||
{
|
||||
name: "channel reserve leaves exact balance",
|
||||
chain: chaincfg.MainNetParams,
|
||||
destAddr: mainnetAddr,
|
||||
label: "label ok",
|
||||
confTarget: 2,
|
||||
channels: []lndclient.ChannelInfo{
|
||||
{
|
||||
Active: true,
|
||||
ChannelID: chanID2.ToUint64(),
|
||||
LocalBalance: 10101,
|
||||
LocalConstraints: &lndclient.ChannelConstraints{
|
||||
Reserve: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
amount: 10000,
|
||||
maxRoutingFee: 1,
|
||||
maxParts: 1,
|
||||
err: nil,
|
||||
expectedTarget: 2,
|
||||
},
|
||||
{
|
||||
name: "can split between channels",
|
||||
chain: chaincfg.MainNetParams,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue