Skip to content

Commit 9f67213

Browse files
committed
Validate channel parameters
We apply slightly different validation for zero-fee commitments: - the commit feerate must be `0 sat/byte` - the max number of accepted HTLCs must be at most 114 - `update_fee` cannot be used We verify those requirements during channel creation and add tests for normal channel operation.
1 parent a57bfd7 commit 9f67213

File tree

13 files changed

+200
-43
lines changed

13 files changed

+200
-43
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2121
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong}
2222
import fr.acinq.eclair.Setup.Seeds
2323
import fr.acinq.eclair.blockchain.fee._
24-
import fr.acinq.eclair.channel.ChannelFlags
2524
import fr.acinq.eclair.channel.fsm.Channel
2625
import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy}
26+
import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes}
2727
import fr.acinq.eclair.crypto.Noise.KeyPair
2828
import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager}
2929
import fr.acinq.eclair.db._
@@ -361,7 +361,7 @@ object NodeParams extends Logging {
361361
require(htlcMinimum > 0.msat, "channel.htlc-minimum-msat must be strictly greater than 0")
362362

363363
val maxAcceptedHtlcs = config.getInt("channel.max-accepted-htlcs")
364-
require(maxAcceptedHtlcs <= Channel.MAX_ACCEPTED_HTLCS, s"channel.max-accepted-htlcs must be lower than ${Channel.MAX_ACCEPTED_HTLCS}")
364+
require(maxAcceptedHtlcs <= Channel.maxAcceptedHtlcs(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), s"channel.max-accepted-htlcs must be lower than ${Channel.maxAcceptedHtlcs(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())}")
365365

366366
val maxToLocalCLTV = CltvExpiryDelta(config.getInt("channel.max-to-local-delay-blocks"))
367367
val offeredCLTV = CltvExpiryDelta(config.getInt("channel.to-remote-delay-blocks"))

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,6 @@ object Helpers {
114114
// BOLT #2: The receiving node MUST fail the channel if: to_self_delay is unreasonably large.
115115
if (open.toSelfDelay > nodeParams.channelConf.maxToLocalDelay) return Left(ToSelfDelayTooHigh(open.temporaryChannelId, open.toSelfDelay, nodeParams.channelConf.maxToLocalDelay))
116116

117-
// BOLT #2: The receiving node MUST fail the channel if: max_accepted_htlcs is greater than 483.
118-
if (open.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) return Left(InvalidMaxAcceptedHtlcs(open.temporaryChannelId, open.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS))
119-
120-
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing.
121-
if (isFeeTooSmall(open.feeratePerKw)) return Left(FeerateTooSmall(open.temporaryChannelId, open.feeratePerKw))
122-
123117
if (open.dustLimitSatoshis > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimitSatoshis, nodeParams.channelConf.maxRemoteDustLimit))
124118

125119
// BOLT #2: The receiving node MUST fail the channel if: dust_limit_satoshis is greater than channel_reserve_satoshis.
@@ -143,6 +137,12 @@ object Helpers {
143137
case _: SegwitV0CommitmentFormat => ()
144138
}
145139

140+
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing.
141+
if (isFeeTooSmall(open.feeratePerKw, channelType)) return Left(FeerateTooSmall(open.temporaryChannelId, open.feeratePerKw))
142+
143+
// BOLT #2: The receiving node MUST fail the channel if max_accepted_htlcs is too high.
144+
if (open.maxAcceptedHtlcs > Channel.maxAcceptedHtlcs(channelType)) return Left(InvalidMaxAcceptedHtlcs(open.temporaryChannelId, open.maxAcceptedHtlcs, Channel.maxAcceptedHtlcs(channelType)))
145+
146146
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
147147
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat)
148148
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isProposedCommitFeerateTooHigh(localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw))
@@ -178,12 +178,6 @@ object Helpers {
178178
// BOLT #2: The receiving node MUST fail the channel if: to_self_delay is unreasonably large.
179179
if (open.toSelfDelay > nodeParams.channelConf.maxToLocalDelay) return Left(ToSelfDelayTooHigh(open.temporaryChannelId, open.toSelfDelay, nodeParams.channelConf.maxToLocalDelay))
180180

181-
// BOLT #2: The receiving node MUST fail the channel if: max_accepted_htlcs is greater than 483.
182-
if (open.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) return Left(InvalidMaxAcceptedHtlcs(open.temporaryChannelId, open.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS))
183-
184-
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing.
185-
if (isFeeTooSmall(open.commitmentFeerate)) return Left(FeerateTooSmall(open.temporaryChannelId, open.commitmentFeerate))
186-
187181
if (open.dustLimit < Channel.MIN_DUST_LIMIT) return Left(DustLimitTooSmall(open.temporaryChannelId, open.dustLimit, Channel.MIN_DUST_LIMIT))
188182
if (open.dustLimit > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimit, nodeParams.channelConf.maxRemoteDustLimit))
189183

@@ -193,6 +187,12 @@ object Helpers {
193187
}
194188
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
195189

190+
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing.
191+
if (isFeeTooSmall(open.commitmentFeerate, channelType)) return Left(FeerateTooSmall(open.temporaryChannelId, open.commitmentFeerate))
192+
193+
// BOLT #2: The receiving node MUST fail the channel if max_accepted_htlcs is too high.
194+
if (open.maxAcceptedHtlcs > Channel.maxAcceptedHtlcs(channelType)) return Left(InvalidMaxAcceptedHtlcs(open.temporaryChannelId, open.maxAcceptedHtlcs, Channel.maxAcceptedHtlcs(channelType)))
195+
196196
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
197197
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat)
198198
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isProposedCommitFeerateTooHigh(localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate))
@@ -218,7 +218,7 @@ object Helpers {
218218
case Right(channelType) => channelType
219219
}
220220

221-
if (accept.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) return Left(InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS))
221+
if (accept.maxAcceptedHtlcs > Channel.maxAcceptedHtlcs(channelType)) return Left(InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.maxAcceptedHtlcs(channelType)))
222222

223223
if (accept.dustLimitSatoshis > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, accept.dustLimitSatoshis, nodeParams.channelConf.maxRemoteDustLimit))
224224
if (accept.dustLimitSatoshis < Channel.MIN_DUST_LIMIT) return Left(DustLimitTooSmall(accept.temporaryChannelId, accept.dustLimitSatoshis, Channel.MIN_DUST_LIMIT))
@@ -268,7 +268,7 @@ object Helpers {
268268
// BOLT #2: The receiving node MUST fail the channel if: push_msat is greater than funding_satoshis * 1000.
269269
if (accept.pushAmount > accept.fundingAmount) return Left(InvalidPushAmount(accept.temporaryChannelId, accept.pushAmount, accept.fundingAmount.toMilliSatoshi))
270270

271-
if (accept.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) return Left(InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS))
271+
if (accept.maxAcceptedHtlcs > Channel.maxAcceptedHtlcs(channelType)) return Left(InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.maxAcceptedHtlcs(channelType)))
272272

273273
if (accept.dustLimit < Channel.MIN_DUST_LIMIT) return Left(DustLimitTooSmall(accept.temporaryChannelId, accept.dustLimit, Channel.MIN_DUST_LIMIT))
274274
if (accept.dustLimit > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, accept.dustLimit, nodeParams.channelConf.maxRemoteDustLimit))
@@ -291,8 +291,12 @@ object Helpers {
291291
* @param remoteFeeratePerKw remote fee rate per kiloweight
292292
* @return true if the remote fee rate is too small
293293
*/
294-
private def isFeeTooSmall(remoteFeeratePerKw: FeeratePerKw): Boolean = {
295-
remoteFeeratePerKw < FeeratePerKw.MinimumFeeratePerKw
294+
private def isFeeTooSmall(remoteFeeratePerKw: FeeratePerKw, channelType: SupportedChannelType): Boolean = {
295+
channelType match {
296+
case _: ChannelTypes.AnchorOutputs | _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => remoteFeeratePerKw < FeeratePerKw.MinimumFeeratePerKw
297+
case _: ChannelTypes.SimpleTaprootChannelsStaging | ChannelTypes.SimpleTaprootChannelsPhoenix => remoteFeeratePerKw < FeeratePerKw.MinimumFeeratePerKw
298+
case _: ChannelTypes.ZeroFeeCommitments => false
299+
}
296300
}
297301

298302
/** Compute the temporaryChannelId of a dual-funded channel. */

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ object Channel {
127127
}
128128
}
129129

130-
def commitParams(fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): ProposedCommitParams = ProposedCommitParams(
130+
def commitParams(fundingAmount: Satoshi, channelType: SupportedChannelType, unlimitedMaxHtlcValueInFlight: Boolean): ProposedCommitParams = ProposedCommitParams(
131131
localDustLimit = dustLimit,
132132
localHtlcMinimum = htlcMinimum,
133133
localMaxHtlcValueInFlight = maxHtlcValueInFlight(fundingAmount, unlimitedMaxHtlcValueInFlight),
134-
localMaxAcceptedHtlcs = maxAcceptedHtlcs,
134+
localMaxAcceptedHtlcs = maxAcceptedHtlcs.min(Channel.maxAcceptedHtlcs(channelType)),
135135
toRemoteDelay = toRemoteDelay,
136136
)
137137
}
@@ -151,7 +151,14 @@ object Channel {
151151

152152
// https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#requirements
153153
val MAX_FUNDING_WITHOUT_WUMBO: Satoshi = 16777216 sat // = 2^24
154-
val MAX_ACCEPTED_HTLCS = 483
154+
155+
/** We limit the number of HTLCs that can be pending to ensure that the commit tx doesn't exceed standard weight limits. */
156+
def maxAcceptedHtlcs(channelType: SupportedChannelType): Int = channelType match {
157+
case _: ChannelTypes.AnchorOutputs | _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => 483
158+
case _: ChannelTypes.SimpleTaprootChannelsStaging | ChannelTypes.SimpleTaprootChannelsPhoenix => 483
159+
// When using v3 transactions, the maximum package size is more restrictive than v2 transactions.
160+
case _: ChannelTypes.ZeroFeeCommitments => 114
161+
}
155162

156163
// We may need to rely on our peer's commit tx in certain cases (backup/restore) so we must ensure their transactions
157164
// can propagate through the bitcoin network (assuming bitcoin core nodes with default policies).

eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class Peer(val nodeParams: NodeParams,
229229
requireConfirmedInputs = requireConfirmedInputs,
230230
requestFunding_opt = c.requestFunding_opt,
231231
localChannelParams = localParams,
232-
proposedCommitParams = nodeParams.channelConf.commitParams(c.fundingAmount, unlimitedMaxHtlcValueInFlight = false),
232+
proposedCommitParams = nodeParams.channelConf.commitParams(c.fundingAmount, channelType, unlimitedMaxHtlcValueInFlight = false),
233233
remote = d.peerConnection,
234234
remoteInit = d.remoteInit,
235235
channelFlags = c.channelFlags_opt.getOrElse(nodeParams.channelConf.channelFlags),
@@ -293,7 +293,7 @@ class Peer(val nodeParams: NodeParams,
293293
pushAmount_opt = None,
294294
requireConfirmedInputs = false,
295295
localChannelParams = localParams,
296-
proposedCommitParams = nodeParams.channelConf.commitParams(open.fundingSatoshis, unlimitedMaxHtlcValueInFlight = false),
296+
proposedCommitParams = nodeParams.channelConf.commitParams(open.fundingSatoshis, channelType, unlimitedMaxHtlcValueInFlight = false),
297297
remote = d.peerConnection,
298298
remoteInit = d.remoteInit,
299299
channelConfig = channelConfig,
@@ -308,7 +308,7 @@ class Peer(val nodeParams: NodeParams,
308308
pushAmount_opt = None,
309309
requireConfirmedInputs = nodeParams.channelConf.requireConfirmedInputsForDualFunding,
310310
localChannelParams = localParams,
311-
proposedCommitParams = nodeParams.channelConf.commitParams(open.fundingAmount + addFunding_opt.map(_.fundingAmount).getOrElse(0 sat), unlimitedMaxHtlcValueInFlight = false),
311+
proposedCommitParams = nodeParams.channelConf.commitParams(open.fundingAmount + addFunding_opt.map(_.fundingAmount).getOrElse(0 sat), channelType, unlimitedMaxHtlcValueInFlight = false),
312312
remote = d.peerConnection,
313313
remoteInit = d.remoteInit,
314314
channelConfig = channelConfig,

eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ private[channel] object ChannelCodecs5 {
8282
.typecase(0x02, provide(Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat))
8383
.typecase(0x03, provide(Transactions.PhoenixSimpleTaprootChannelCommitmentFormat))
8484
.typecase(0x04, provide(Transactions.ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat))
85+
.typecase(0x05, provide(Transactions.ZeroFeeCommitmentFormat))
8586
// 0x00 was used for pre-anchor channels, which have been deprecated after eclair v0.13.1.
8687
.typecase(0x00, fail[Transactions.CommitmentFormat](Err("some of your channels are not using anchor outputs: you must restart with your previous eclair version and close those channels before updating to this version of eclair (see the release notes for more details)")))
8788

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ object ChannelStateTestsTags {
9898
val OptionSimpleTaprootPhoenix = "option_simple_taproot_phoenix"
9999
/** If set, channels will use taproot. */
100100
val OptionSimpleTaproot = "option_simple_taproot"
101+
/** If set, channel will use zero-fee commitments. */
102+
val ZeroFeeCommitments = "zero_fee_commitments"
101103
}
102104

103105
trait ChannelStateTestsBase extends Assertions with Eventually {
@@ -142,7 +144,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
142144
dualFunded = dualFunded,
143145
commitTxFeerate = channelType match {
144146
case _: ChannelTypes.AnchorOutputs | ChannelTypes.SimpleTaprootChannelsPhoenix => TestConstants.phoenixCommitFeeratePerKw
145-
case _ => TestConstants.anchorOutputsFeeratePerKw
147+
case _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx | _: ChannelTypes.SimpleTaprootChannelsStaging => TestConstants.anchorOutputsFeeratePerKw
148+
case _: ChannelTypes.ZeroFeeCommitments => FeeratePerKw(0 sat)
146149
},
147150
fundingTxFeerate = TestConstants.feeratePerKw,
148151
fundingTxFeeBudget_opt = None,
@@ -265,6 +268,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
265268
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional))
266269
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
267270
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
271+
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroFeeCommitments))(_.updated(Features.ZeroFeeCommitments, FeatureSupport.Optional))
268272
)
269273
val nodeParamsB1 = nodeParamsB.copy(features = nodeParamsB.features
270274
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableWumbo))(_.removed(Features.Wumbo))
@@ -277,6 +281,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
277281
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional))
278282
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
279283
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
284+
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroFeeCommitments))(_.updated(Features.ZeroFeeCommitments, FeatureSupport.Optional))
280285
)
281286
(nodeParamsA1, nodeParamsB1)
282287
}
@@ -287,7 +292,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
287292

288293
val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel
289294
val zeroConf = canUse(Features.ZeroConf)
290-
if (canUse(Features.SimpleTaprootChannelsStaging)) {
295+
if (canUse(Features.ZeroFeeCommitments)) {
296+
ChannelTypes.ZeroFeeCommitments(scidAlias, zeroConf)
297+
} else if (canUse(Features.SimpleTaprootChannelsStaging)) {
291298
ChannelTypes.SimpleTaprootChannelsStaging(scidAlias, zeroConf)
292299
} else if (canUse(Features.SimpleTaprootChannelsPhoenix)) {
293300
ChannelTypes.SimpleTaprootChannelsPhoenix

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,20 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
135135
test("recv AcceptChannel (invalid max accepted htlcs)") { f =>
136136
import f._
137137
val accept = bob2alice.expectMsgType[AcceptChannel]
138-
// spec says max = 483
139-
val invalidMaxAcceptedHtlcs = 484
140-
alice ! accept.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
138+
alice ! accept.copy(maxAcceptedHtlcs = 484)
141139
val error = alice2bob.expectMsgType[Error]
142-
assert(error == Error(accept.temporaryChannelId, InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage))
140+
assert(error == Error(accept.temporaryChannelId, InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, maxAcceptedHtlcs = 484, max = 483).getMessage))
141+
listener.expectMsgType[ChannelAborted]
142+
awaitCond(alice.stateName == CLOSED)
143+
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected]
144+
}
145+
146+
test("recv AcceptChannel (invalid max accepted htlcs, zero-fee commitments)", Tag(ChannelStateTestsTags.ZeroFeeCommitments)) { f =>
147+
import f._
148+
val accept = bob2alice.expectMsgType[AcceptChannel]
149+
alice ! accept.copy(maxAcceptedHtlcs = 115)
150+
val error = alice2bob.expectMsgType[Error]
151+
assert(error == Error(accept.temporaryChannelId, InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, maxAcceptedHtlcs = 115, max = 114).getMessage))
143152
listener.expectMsgType[ChannelAborted]
144153
awaitCond(alice.stateName == CLOSED)
145154
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected]

0 commit comments

Comments
 (0)