diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 2015d25fb8..059d57da5b 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -64,6 +64,7 @@ eclair { channel-flags = 1 // announce channels dust-limit-satoshis = 546 + max-remote-dust-limit-satoshis = 600 max-htlc-value-in-flight-msat = 5000000000 // 50 mBTC htlc-minimum-msat = 1 max-accepted-htlcs = 30 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index f8c8430675..61e8784942 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -58,6 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, syncWhitelist: Set[PublicKey], pluginParams: Seq[PluginParams], dustLimit: Satoshi, + maxRemoteDustLimit: Satoshi, onChainFeeConf: OnChainFeeConf, maxHtlcValueInFlightMsat: UInt64, maxAcceptedHtlcs: Int, @@ -323,6 +324,7 @@ object NodeParams extends Logging { overrideFeatures = overrideFeatures, syncWhitelist = syncWhitelist, dustLimit = dustLimitSatoshis, + maxRemoteDustLimit = Satoshi(config.getLong("max-remote-dust-limit-satoshis")), onChainFeeConf = OnChainFeeConf( feeTargets = feeTargets, feeEstimator = feeEstimator, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 040f7d456e..96439d7bcd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -103,6 +103,8 @@ object Helpers { // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing. if (isFeeTooSmall(open.feeratePerKw)) return Left(FeerateTooSmall(open.temporaryChannelId, open.feeratePerKw)) + if (open.dustLimitSatoshis > nodeParams.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimitSatoshis, nodeParams.maxRemoteDustLimit)) + // BOLT #2: The receiving node MUST fail the channel if: dust_limit_satoshis is greater than channel_reserve_satoshis. if (open.dustLimitSatoshis > open.channelReserveSatoshis) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimitSatoshis, open.channelReserveSatoshis)) @@ -140,6 +142,8 @@ object Helpers { if (accept.dustLimitSatoshis < Channel.MIN_DUSTLIMIT) return Left(DustLimitTooSmall(accept.temporaryChannelId, accept.dustLimitSatoshis, Channel.MIN_DUSTLIMIT)) } + if (accept.dustLimitSatoshis > nodeParams.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, accept.dustLimitSatoshis, nodeParams.maxRemoteDustLimit)) + // BOLT #2: The receiving node MUST fail the channel if: dust_limit_satoshis is greater than channel_reserve_satoshis. if (accept.dustLimitSatoshis > accept.channelReserveSatoshis) return Left(DustLimitTooLarge(accept.temporaryChannelId, accept.dustLimitSatoshis, accept.channelReserveSatoshis)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index ab9aa5ffbf..0205b8feb7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -167,6 +167,7 @@ object TestConstants { overrideFeatures = Map.empty, syncWhitelist = Set.empty, dustLimit = 1100 sat, + maxRemoteDustLimit = 1500 sat, onChainFeeConf = OnChainFeeConf( feeTargets = FeeTargets(6, 2, 2, 6), feeEstimator = new TestFeeEstimator, @@ -271,6 +272,7 @@ object TestConstants { overrideFeatures = Map.empty, syncWhitelist = Set.empty, dustLimit = 1000 sat, + maxRemoteDustLimit = 1500 sat, onChainFeeConf = OnChainFeeConf( feeTargets = FeeTargets(6, 2, 2, 6), feeEstimator = new TestFeeEstimator, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 7d581a3d71..14fddda581 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -52,6 +52,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS val aliceNodeParams = Alice.nodeParams .modify(_.chainHash).setToIf(test.tags.contains("mainnet"))(Block.LivenetGenesisBlock.hash) .modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-size"))(Btc(100)) + .modify(_.maxRemoteDustLimit).setToIf(test.tags.contains("high-remote-dust-limit"))(15000 sat) val aliceParams = Alice.channelParams .modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional)))) @@ -98,7 +99,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(alice.stateName == CLOSED) } - test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { f => + test("recv AcceptChannel (dust limit too low)", Tag("mainnet")) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] // we don't want their dust limit to be below 546 @@ -109,6 +110,16 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(alice.stateName == CLOSED) } + test("recv AcceptChannel (dust limit too high)") { f => + import f._ + val accept = bob2alice.expectMsgType[AcceptChannel] + val highDustLimitSatoshis = 2000.sat + alice ! accept.copy(dustLimitSatoshis = highDustLimitSatoshis) + val error = alice2bob.expectMsgType[Error] + assert(error === Error(accept.temporaryChannelId, DustLimitTooLarge(accept.temporaryChannelId, highDustLimitSatoshis, Alice.nodeParams.maxRemoteDustLimit).getMessage)) + awaitCond(alice.stateName == CLOSED) + } + test("recv AcceptChannel (to_self_delay too high)") { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] @@ -151,7 +162,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(alice.stateName == CLOSED) } - test("recv AcceptChannel (dust limit above our reserve)") { f => + test("recv AcceptChannel (dust limit above our reserve)", Tag("high-remote-dust-limit")) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index b01d8de409..1bd41d3448 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -188,6 +188,16 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui awaitCond(bob.stateName == CLOSED) } + test("recv OpenChannel (dust limit too high)") { f => + import f._ + val open = alice2bob.expectMsgType[OpenChannel] + val dustLimitTooHigh = 2000.sat + bob ! open.copy(dustLimitSatoshis = dustLimitTooHigh) + val error = bob2alice.expectMsgType[Error] + assert(error === Error(open.temporaryChannelId, DustLimitTooLarge(open.temporaryChannelId, dustLimitTooHigh, Bob.nodeParams.maxRemoteDustLimit).getMessage)) + awaitCond(bob.stateName == CLOSED) + } + test("recv OpenChannel (toLocal + toRemote below reserve)") { f => import f._ val open = alice2bob.expectMsgType[OpenChannel]