mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 14:40:34 +01:00
Check funds in millisatoshi when sending/receiving an HTLC (#1128)
Instead of satoshi, which could introduce rounding errors. Also, we check first the balance before the max-inflight amount, because it makes more sense in terms of error management. Co-Authored-By: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
This commit is contained in:
parent
ff0b4c81e6
commit
0e704549d7
2 changed files with 27 additions and 15 deletions
|
@ -148,6 +148,14 @@ object Commitments {
|
|||
// the HTLC we are about to create is outgoing, but from their point of view it is incoming
|
||||
val outgoingHtlcs = reduced.htlcs.filter(_.direction == IN)
|
||||
|
||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||
// we look from remote's point of view, so if local is funder remote doesn't pay the fees
|
||||
val fees = if (commitments1.localParams.isFunder) commitTxFee(commitments1.remoteParams.dustLimit, reduced) else 0.sat
|
||||
val missing = reduced.toRemote - commitments1.remoteParams.channelReserve - fees
|
||||
if (missing < 0.msat) {
|
||||
return Left(InsufficientFunds(commitments.channelId, amount = cmd.amount, missing = -missing.truncateToSatoshi, reserve = commitments1.remoteParams.channelReserve, fees = fees))
|
||||
}
|
||||
|
||||
val htlcValueInFlight = outgoingHtlcs.map(_.add.amountMsat).sum
|
||||
if (commitments1.remoteParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
|
||||
// TODO: this should be a specific UPDATE error
|
||||
|
@ -158,14 +166,6 @@ object Commitments {
|
|||
return Left(TooManyAcceptedHtlcs(commitments.channelId, maximum = commitments1.remoteParams.maxAcceptedHtlcs))
|
||||
}
|
||||
|
||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||
// we look from remote's point of view, so if local is funder remote doesn't pay the fees
|
||||
val fees = if (commitments1.localParams.isFunder) commitTxFee(commitments1.remoteParams.dustLimit, reduced) else 0.sat
|
||||
val missing = reduced.toRemote.truncateToSatoshi - commitments1.remoteParams.channelReserve - fees
|
||||
if (missing < 0.sat) {
|
||||
return Left(InsufficientFunds(commitments.channelId, amount = cmd.amount, missing = -missing, reserve = commitments1.remoteParams.channelReserve, fees = fees))
|
||||
}
|
||||
|
||||
Right(commitments1, add)
|
||||
}
|
||||
|
||||
|
@ -183,6 +183,13 @@ object Commitments {
|
|||
val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed)
|
||||
val incomingHtlcs = reduced.htlcs.filter(_.direction == IN)
|
||||
|
||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||
val fees = if (commitments1.localParams.isFunder) 0.sat else Transactions.commitTxFee(commitments1.localParams.dustLimit, reduced)
|
||||
val missing = reduced.toRemote - commitments1.localParams.channelReserve - fees
|
||||
if (missing < 0.msat) {
|
||||
throw InsufficientFunds(commitments.channelId, amount = add.amountMsat, missing = -missing.truncateToSatoshi, reserve = commitments1.localParams.channelReserve, fees = fees)
|
||||
}
|
||||
|
||||
val htlcValueInFlight = incomingHtlcs.map(_.add.amountMsat).sum
|
||||
if (commitments1.localParams.maxHtlcValueInFlightMsat < htlcValueInFlight) {
|
||||
throw HtlcValueTooHighInFlight(commitments.channelId, maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
|
||||
|
@ -192,13 +199,6 @@ object Commitments {
|
|||
throw TooManyAcceptedHtlcs(commitments.channelId, maximum = commitments1.localParams.maxAcceptedHtlcs)
|
||||
}
|
||||
|
||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||
val fees = if (commitments1.localParams.isFunder) 0.sat else Transactions.commitTxFee(commitments1.localParams.dustLimit, reduced)
|
||||
val missing = reduced.toRemote.truncateToSatoshi - commitments1.localParams.channelReserve - fees
|
||||
if (missing < 0.sat) {
|
||||
throw InsufficientFunds(commitments.channelId, amount = add.amountMsat, missing = -missing, reserve = commitments1.localParams.channelReserve, fees = fees)
|
||||
}
|
||||
|
||||
commitments1
|
||||
}
|
||||
|
||||
|
|
|
@ -161,6 +161,18 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds, missing 1 msat)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add = CMD_ADD_HTLC(initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))
|
||||
sender.send(bob, add)
|
||||
|
||||
val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat)
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
|
|
Loading…
Add table
Reference in a new issue