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 e4ba5ed58a..e7b0550ba8 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 @@ -387,12 +387,12 @@ object Commitments { // If sending this htlc would overflow our dust exposure, we reject it. val maxDustExposure = feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.maxExposure - val localReduced = CommitmentSpec.reduceForDustExposure(commitments.localCommit.spec, commitments1.localChanges.all, commitments.remoteChanges.all) + val localReduced = DustExposure.reduceForDustExposure(commitments.localCommit.spec, commitments1.localChanges.all, commitments.remoteChanges.all) val localDustExposureAfterAdd = DustExposure.computeExposure(localReduced, commitments.localParams.dustLimit, commitments.commitmentFormat) if (localDustExposureAfterAdd > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localDustExposureAfterAdd)) } - val remoteReduced = CommitmentSpec.reduceForDustExposure(remoteCommit1.spec, commitments.remoteChanges.all, commitments1.localChanges.all) + val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit1.spec, commitments.remoteChanges.all, commitments1.localChanges.all) val remoteDustExposureAfterAdd = DustExposure.computeExposure(remoteReduced, commitments.remoteParams.dustLimit, commitments.commitmentFormat) if (remoteDustExposureAfterAdd > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteDustExposureAfterAdd)) @@ -558,12 +558,12 @@ object Commitments { val maxDustExposure = feeConf.feerateToleranceFor(commitments.remoteNodeId).dustTolerance.maxExposure // this is the commitment as it would be if our update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) - val localReduced = CommitmentSpec.reduceForDustExposure(commitments.localCommit.spec, commitments1.localChanges.all, commitments.remoteChanges.all) + val localReduced = DustExposure.reduceForDustExposure(commitments.localCommit.spec, commitments1.localChanges.all, commitments.remoteChanges.all) val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, cmd.feeratePerKw, commitments.localParams.dustLimit, commitments.commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } - val remoteReduced = CommitmentSpec.reduceForDustExposure(commitments.remoteCommit.spec, commitments.remoteChanges.all, commitments1.localChanges.all) + val remoteReduced = DustExposure.reduceForDustExposure(commitments.remoteCommit.spec, commitments.remoteChanges.all, commitments1.localChanges.all) val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, cmd.feeratePerKw, commitments.remoteParams.dustLimit, commitments.commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) @@ -606,14 +606,14 @@ 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 localReduced = CommitmentSpec.reduceForDustExposure(commitments.localCommit.spec, commitments.localChanges.all, commitments1.remoteChanges.all) + val localReduced = DustExposure.reduceForDustExposure(commitments.localCommit.spec, commitments.localChanges.all, commitments1.remoteChanges.all) val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, fee.feeratePerKw, commitments.localParams.dustLimit, commitments.commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } // this is the commitment as it would be if their update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) - val remoteReduced = CommitmentSpec.reduceForDustExposure(commitments.remoteCommit.spec, commitments1.remoteChanges.all, commitments.localChanges.all) + val remoteReduced = DustExposure.reduceForDustExposure(commitments.remoteCommit.spec, commitments1.remoteChanges.all, commitments.localChanges.all) val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, fee.feeratePerKw, commitments.remoteParams.dustLimit, commitments.commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(commitments.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) @@ -782,9 +782,9 @@ object Commitments { case OutgoingHtlc(add) if receivedHtlcs.contains(add) => false case _ => true }) - val localReduced = CommitmentSpec.reduceForDustExposure(localSpecWithoutNewHtlcs, commitments.localChanges.all, commitments.remoteChanges.acked) + val localReduced = DustExposure.reduceForDustExposure(localSpecWithoutNewHtlcs, commitments.localChanges.all, commitments.remoteChanges.acked) val localCommitDustExposure = DustExposure.computeExposure(localReduced, commitments.localParams.dustLimit, commitments.commitmentFormat) - val remoteReduced = CommitmentSpec.reduceForDustExposure(remoteSpecWithoutNewHtlcs, commitments.remoteChanges.acked, commitments.localChanges.all) + val remoteReduced = DustExposure.reduceForDustExposure(remoteSpecWithoutNewHtlcs, commitments.remoteChanges.acked, commitments.localChanges.all) val remoteCommitDustExposure = DustExposure.computeExposure(remoteReduced, commitments.remoteParams.dustLimit, commitments.commitmentFormat) // we sort incoming htlcs by decreasing amount: we want to prioritize higher amounts. val sortedReceivedHtlcs = receivedHtlcs.sortBy(_.amountMsat).reverse diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/DustExposure.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/DustExposure.scala index ab59dd6931..84c68182b3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/DustExposure.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/DustExposure.scala @@ -21,7 +21,7 @@ import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.transactions.Transactions.CommitmentFormat import fr.acinq.eclair.transactions._ -import fr.acinq.eclair.wire.protocol.UpdateAddHtlc +import fr.acinq.eclair.wire.protocol._ /** * Created by t-bast on 07/10/2021. @@ -93,4 +93,63 @@ object DustExposure { (acceptedHtlcs, rejectedHtlcs) } + def reduceForDustExposure(localCommitSpec: CommitmentSpec, localChanges: List[UpdateMessage], remoteChanges: List[UpdateMessage]): CommitmentSpec = { + // NB: when computing dust exposure, we usually apply all pending updates (proposed, signed and acked), which means + // that we will sometimes apply fulfill/fail on htlcs that have already been removed: that's why we don't use the + // normal functions from CommitmentSpec that would throw when that happens. + def fulfillIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { + spec.findIncomingHtlcById(htlcId) match { + case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) + case None => spec + } + } + + def fulfillOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { + spec.findOutgoingHtlcById(htlcId) match { + case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) + case None => spec + } + } + + def failIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { + spec.findIncomingHtlcById(htlcId) match { + case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) + case None => spec + } + } + + def failOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { + spec.findOutgoingHtlcById(htlcId) match { + case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) + case None => spec + } + } + + val spec1 = localChanges.foldLeft(localCommitSpec) { + case (spec, u: UpdateAddHtlc) => CommitmentSpec.addHtlc(spec, OutgoingHtlc(u)) + case (spec, _) => spec + } + val spec2 = remoteChanges.foldLeft(spec1) { + case (spec, u: UpdateAddHtlc) => CommitmentSpec.addHtlc(spec, IncomingHtlc(u)) + case (spec, _) => spec + } + val spec3 = localChanges.foldLeft(spec2) { + case (spec, u: UpdateFulfillHtlc) => fulfillIncomingHtlc(spec, u.id) + case (spec, u: UpdateFailHtlc) => failIncomingHtlc(spec, u.id) + case (spec, u: UpdateFailMalformedHtlc) => failIncomingHtlc(spec, u.id) + case (spec, _) => spec + } + val spec4 = remoteChanges.foldLeft(spec3) { + case (spec, u: UpdateFulfillHtlc) => fulfillOutgoingHtlc(spec, u.id) + case (spec, u: UpdateFailHtlc) => failOutgoingHtlc(spec, u.id) + case (spec, u: UpdateFailMalformedHtlc) => failOutgoingHtlc(spec, u.id) + case (spec, _) => spec + } + val spec5 = (localChanges ++ remoteChanges).foldLeft(spec4) { + case (spec, u: UpdateFee) => spec.copy(commitTxFeerate = u.feeratePerKw) + case (spec, _) => spec + } + spec5 + } + } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala index 03dbf9bfc2..414897a7cd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala @@ -155,63 +155,4 @@ object CommitmentSpec { spec5 } - def reduceForDustExposure(localCommitSpec: CommitmentSpec, localChanges: List[UpdateMessage], remoteChanges: List[UpdateMessage]): CommitmentSpec = { - // NB: when computing dust exposure, we usually apply all pending updates (proposed, signed and acked), which means - // that we will sometimes apply fulfill/fail on htlcs that have already been removed: that's why we don't use the - // normal function that would throw when that happens. - def safeFulfillIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { - spec.findIncomingHtlcById(htlcId) match { - case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) - case None => spec - } - } - - def safeFulfillOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { - spec.findOutgoingHtlcById(htlcId) match { - case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) - case None => spec - } - } - - def safeFailIncomingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { - spec.findIncomingHtlcById(htlcId) match { - case Some(htlc) => spec.copy(toRemote = spec.toRemote + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) - case None => spec - } - } - - def safeFailOutgoingHtlc(spec: CommitmentSpec, htlcId: Long): CommitmentSpec = { - spec.findOutgoingHtlcById(htlcId) match { - case Some(htlc) => spec.copy(toLocal = spec.toLocal + htlc.add.amountMsat, htlcs = spec.htlcs - htlc) - case None => spec - } - } - - val spec1 = localChanges.foldLeft(localCommitSpec) { - case (spec, u: UpdateAddHtlc) => addHtlc(spec, OutgoingHtlc(u)) - case (spec, _) => spec - } - val spec2 = remoteChanges.foldLeft(spec1) { - case (spec, u: UpdateAddHtlc) => addHtlc(spec, IncomingHtlc(u)) - case (spec, _) => spec - } - val spec3 = localChanges.foldLeft(spec2) { - case (spec, u: UpdateFulfillHtlc) => safeFulfillIncomingHtlc(spec, u.id) - case (spec, u: UpdateFailHtlc) => safeFailIncomingHtlc(spec, u.id) - case (spec, u: UpdateFailMalformedHtlc) => safeFailIncomingHtlc(spec, u.id) - case (spec, _) => spec - } - val spec4 = remoteChanges.foldLeft(spec3) { - case (spec, u: UpdateFulfillHtlc) => safeFulfillOutgoingHtlc(spec, u.id) - case (spec, u: UpdateFailHtlc) => safeFailOutgoingHtlc(spec, u.id) - case (spec, u: UpdateFailMalformedHtlc) => safeFailOutgoingHtlc(spec, u.id) - case (spec, _) => spec - } - val spec5 = (localChanges ++ remoteChanges).foldLeft(spec4) { - case (spec, u: UpdateFee) => spec.copy(commitTxFeerate = u.feeratePerKw) - case (spec, _) => spec - } - spec5 - } - } \ No newline at end of file