diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index 6a74524c9a..e5805a4511 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -78,7 +78,8 @@ case class ExpiryTooBig (override val channelId: Byte case class HtlcValueTooSmall (override val channelId: ByteVector32, minimum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual") case class HtlcValueTooHighInFlight (override val channelId: ByteVector32, maximum: UInt64, actual: MilliSatoshi) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual") case class TooManyAcceptedHtlcs (override val channelId: ByteVector32, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum") -case class DustHtlcExposureTooHighInFlight (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual") +case class LocalDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual") +case class RemoteDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual") case class InsufficientFunds (override val channelId: ByteVector32, amount: MilliSatoshi, missing: Satoshi, reserve: Satoshi, fees: Satoshi) extends ChannelException(channelId, s"insufficient funds: missing=$missing reserve=$reserve fees=$fees") case class RemoteCannotAffordFeesForNewHtlc (override val channelId: ByteVector32, amount: MilliSatoshi, missing: Satoshi, reserve: Satoshi, fees: Satoshi) extends ChannelException(channelId, s"remote can't afford increased commit tx fees once new HTLC is added: missing=$missing reserve=$reserve fees=$fees") case class InvalidHtlcPreimage (override val channelId: ByteVector32, id: Long) extends ChannelException(channelId, s"invalid htlc preimage for htlc id=$id") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index d9f7af6b38..cfa1ba5e72 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -425,10 +425,10 @@ object Commitments { val (localCommitDustExposure, remoteCommitDustExposure) = commitments.currentDustExposure() val (contributesToLocalCommitDustExposure, contributesToRemoteCommitDustExposure) = commitments.contributesToDustExposure(OutgoingHtlc(add)) if (contributesToLocalCommitDustExposure && localCommitDustExposure + add.amountMsat > maxDustExposure) { - return Left(DustHtlcExposureTooHighInFlight(commitments.channelId, maxDustExposure, localCommitDustExposure + add.amountMsat)) + return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localCommitDustExposure + add.amountMsat)) } if (contributesToRemoteCommitDustExposure && remoteCommitDustExposure + add.amountMsat > maxDustExposure) { - return Left(DustHtlcExposureTooHighInFlight(commitments.channelId, maxDustExposure, remoteCommitDustExposure + add.amountMsat)) + return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteCommitDustExposure + add.amountMsat)) } Right(commitments1, add) @@ -589,12 +589,13 @@ object Commitments { // if we would overflow our dust exposure with the new feerate, we avoid sending this fee update if (feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.closeOnUpdateFeeOverflow) { val maxDustExposure = feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.maxExposure - val dustExposureAfterFeeUpdate = Seq( - CommitmentSpec.dustExposure(commitments1.localCommit.spec, cmd.feeratePerKw, commitments1.localParams.dustLimit, commitments1.commitmentFormat), - CommitmentSpec.dustExposure(reduced, cmd.feeratePerKw, commitments1.remoteParams.dustLimit, commitments1.commitmentFormat) - ).max - if (dustExposureAfterFeeUpdate > maxDustExposure) { - return Left(DustHtlcExposureTooHighInFlight(commitments.channelId, maxDustExposure, dustExposureAfterFeeUpdate)) + val localDustExposureAfterFeeUpdate = CommitmentSpec.dustExposure(commitments1.localCommit.spec, cmd.feeratePerKw, commitments1.localParams.dustLimit, commitments1.commitmentFormat) + if (localDustExposureAfterFeeUpdate > maxDustExposure) { + return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) + } + val remoteDustExposureAfterFeeUpdate = CommitmentSpec.dustExposure(reduced, cmd.feeratePerKw, commitments1.remoteParams.dustLimit, commitments1.commitmentFormat) + if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { + return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } } @@ -634,12 +635,13 @@ object Commitments { // if we would overflow our dust exposure with the new feerate, we reject this fee update if (feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.closeOnUpdateFeeOverflow) { val maxDustExposure = feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.maxExposure - val dustExposureAfterFeeUpdate = Seq( - CommitmentSpec.dustExposure(commitments1.localCommit.spec, fee.feeratePerKw, commitments1.localParams.dustLimit, commitments1.commitmentFormat), - CommitmentSpec.dustExposure(reduced, fee.feeratePerKw, commitments1.remoteParams.dustLimit, commitments1.commitmentFormat), - ).max - if (dustExposureAfterFeeUpdate > maxDustExposure) { - return Left(DustHtlcExposureTooHighInFlight(commitments.channelId, maxDustExposure, dustExposureAfterFeeUpdate)) + val localDustExposureAfterFeeUpdate = CommitmentSpec.dustExposure(commitments1.localCommit.spec, fee.feeratePerKw, commitments1.localParams.dustLimit, commitments1.commitmentFormat) + if (localDustExposureAfterFeeUpdate > maxDustExposure) { + return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) + } + val remoteDustExposureAfterFeeUpdate = CommitmentSpec.dustExposure(reduced, fee.feeratePerKw, commitments1.remoteParams.dustLimit, commitments1.commitmentFormat) + if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { + return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 486ad5aedf..bcda556b0d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -403,13 +403,13 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // HTLCs that take Alice's dust exposure above her threshold are rejected. val dustAdd = CMD_ADD_HTLC(sender.ref, 501.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, localOrigin(sender.ref)) alice ! dustAdd - sender.expectMsg(RES_ADD_FAILED(dustAdd, DustHtlcExposureTooHighInFlight(channelId(alice), 25000.sat, 25001.sat.toMilliSatoshi), Some(initialState.channelUpdate))) + sender.expectMsg(RES_ADD_FAILED(dustAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25001.sat.toMilliSatoshi), Some(initialState.channelUpdate))) val trimmedAdd = CMD_ADD_HTLC(sender.ref, 5000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, localOrigin(sender.ref)) alice ! trimmedAdd - sender.expectMsg(RES_ADD_FAILED(trimmedAdd, DustHtlcExposureTooHighInFlight(channelId(alice), 25000.sat, 29500.sat.toMilliSatoshi), Some(initialState.channelUpdate))) + sender.expectMsg(RES_ADD_FAILED(trimmedAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 29500.sat.toMilliSatoshi), Some(initialState.channelUpdate))) val justAboveTrimmedAdd = CMD_ADD_HTLC(sender.ref, 8500.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, localOrigin(sender.ref)) alice ! justAboveTrimmedAdd - sender.expectMsg(RES_ADD_FAILED(justAboveTrimmedAdd, DustHtlcExposureTooHighInFlight(channelId(alice), 25000.sat, 33000.sat.toMilliSatoshi), Some(initialState.channelUpdate))) + sender.expectMsg(RES_ADD_FAILED(justAboveTrimmedAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 33000.sat.toMilliSatoshi), Some(initialState.channelUpdate))) // HTLCs that don't contribute to dust exposure are accepted. alice ! CMD_ADD_HTLC(sender.ref, 25000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, localOrigin(sender.ref)) @@ -1778,7 +1778,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val sender = TestProbe() val cmd = CMD_UPDATE_FEE(FeeratePerKw(20000 sat), replyTo_opt = Some(sender.ref)) alice ! cmd - sender.expectMsg(RES_FAILURE(cmd, DustHtlcExposureTooHighInFlight(channelId(alice), 25000 sat, 29000000 msat))) + sender.expectMsg(RES_FAILURE(cmd, RemoteDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 29000000 msat))) } test("recv CMD_UPDATE_FEE (two in a row)") { f => @@ -1953,7 +1953,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(25000 sat))) bob ! UpdateFee(channelId(bob), FeeratePerKw(25000 sat)) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) === DustHtlcExposureTooHighInFlight(channelId(bob), 50000 sat, 59000000 msat).getMessage) + assert(new String(error.data.toArray) === LocalDustHtlcExposureTooHigh(channelId(bob), 50000 sat, 59000000 msat).getMessage) assert(bob2blockchain.expectMsgType[PublishRawTx].tx.txid === tx.txid) awaitCond(bob.stateName == CLOSING) }