mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 11:35:47 +01:00
Abort incoming channel after timeout
If a remote node starts opening a channel to us and then becomes unresponsive, we abort the channel. This is particularly useful when they're purchasing liquidity and we've locked utxos.
This commit is contained in:
parent
68c6720c8f
commit
64a12b8028
6 changed files with 23 additions and 2 deletions
|
@ -109,6 +109,9 @@ eclair {
|
|||
max-attempts = 5 // maximum number of RBF attempts our peer is allowed to make
|
||||
attempt-delta-blocks = 6 // minimum number of blocks between RBF attempts
|
||||
}
|
||||
// Duration after which we abort a channel creation. If our peer seems unresponsive and doesn't complete the
|
||||
// funding protocol in time, they're likely buggy or malicious.
|
||||
timeout = 60 seconds
|
||||
}
|
||||
|
||||
dust-limit-satoshis = 546
|
||||
|
|
|
@ -591,6 +591,7 @@ object NodeParams extends Logging {
|
|||
channelOpenerWhitelist = channelOpenerWhitelist,
|
||||
maxPendingChannelsPerPeer = maxPendingChannelsPerPeer,
|
||||
maxTotalPendingChannelsPrivateNodes = maxTotalPendingChannelsPrivateNodes,
|
||||
channelFundingTimeout = FiniteDuration(config.getDuration("channel.funding.timeout").getSeconds, TimeUnit.SECONDS),
|
||||
remoteRbfLimits = Channel.RemoteRbfLimits(config.getInt("channel.funding.remote-rbf-limits.max-attempts"), config.getInt("channel.funding.remote-rbf-limits.attempt-delta-blocks")),
|
||||
quiescenceTimeout = FiniteDuration(config.getDuration("channel.quiescence-timeout").getSeconds, TimeUnit.SECONDS),
|
||||
balanceThresholds = config.getConfigList("channel.channel-update.balance-thresholds").asScala.map(conf => BalanceThreshold(Satoshi(conf.getLong("available-sat")), Satoshi(conf.getLong("max-htlc-sat")))).toSeq,
|
||||
|
@ -683,7 +684,7 @@ object NodeParams extends Logging {
|
|||
batchSize = config.getInt("db.revoked-htlc-info-cleaner.batch-size"),
|
||||
interval = FiniteDuration(config.getDuration("db.revoked-htlc-info-cleaner.interval").getSeconds, TimeUnit.SECONDS)
|
||||
),
|
||||
liquidityAdsConfig = LiquidityAds.Config(willFundRates_opt, lockUtxos = config.getBoolean("liquidity-ads.lock-utxos-during-funding")),
|
||||
liquidityAdsConfig = LiquidityAds.Config(rates_opt = willFundRates_opt, lockUtxos = config.getBoolean("liquidity-ads.lock-utxos-during-funding")),
|
||||
peerWakeUpConfig = PeerReadyNotifier.WakeUpConfig(
|
||||
enabled = config.getBoolean("peer-wake-up.enabled"),
|
||||
timeout = FiniteDuration(config.getDuration("peer-wake-up.timeout").getSeconds, TimeUnit.SECONDS),
|
||||
|
|
|
@ -102,6 +102,7 @@ object Channel {
|
|||
channelOpenerWhitelist: Set[PublicKey],
|
||||
maxPendingChannelsPerPeer: Int,
|
||||
maxTotalPendingChannelsPrivateNodes: Int,
|
||||
channelFundingTimeout: FiniteDuration,
|
||||
remoteRbfLimits: RemoteRbfLimits,
|
||||
quiescenceTimeout: FiniteDuration,
|
||||
balanceThresholds: Seq[BalanceThreshold],
|
||||
|
|
|
@ -198,7 +198,7 @@ class Peer(val nodeParams: NodeParams,
|
|||
|
||||
case Event(SpawnChannelInitiator(replyTo, c, channelConfig, channelType, localParams), d: ConnectedData) =>
|
||||
val channel = spawnChannel()
|
||||
c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher))
|
||||
context.system.scheduler.scheduleOnce(c.timeout_opt.map(_.duration).getOrElse(nodeParams.channelConf.channelFundingTimeout), channel, Channel.TickChannelOpenTimeout)(context.dispatcher)
|
||||
val dualFunded = Features.canUseFeature(d.localFeatures, d.remoteFeatures, Features.DualFunding)
|
||||
val requireConfirmedInputs = c.requireConfirmedInputsOverride_opt.getOrElse(nodeParams.channelConf.requireConfirmedInputsForDualFunding)
|
||||
val temporaryChannelId = if (dualFunded) {
|
||||
|
@ -252,6 +252,7 @@ class Peer(val nodeParams: NodeParams,
|
|||
stay()
|
||||
case accept: OnTheFlyFunding.ValidationResult.Accept =>
|
||||
val channel = spawnChannel()
|
||||
context.system.scheduler.scheduleOnce(nodeParams.channelConf.channelFundingTimeout, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)
|
||||
log.info(s"accepting a new channel with type=$channelType temporaryChannelId=$temporaryChannelId localParams=$localParams")
|
||||
open match {
|
||||
case Left(open) =>
|
||||
|
|
|
@ -145,6 +145,7 @@ object TestConstants {
|
|||
channelOpenerWhitelist = Set.empty,
|
||||
maxPendingChannelsPerPeer = 3,
|
||||
maxTotalPendingChannelsPrivateNodes = 99,
|
||||
channelFundingTimeout = 30 seconds,
|
||||
remoteRbfLimits = RemoteRbfLimits(5, 0),
|
||||
quiescenceTimeout = 2 minutes,
|
||||
balanceThresholds = Nil,
|
||||
|
@ -323,6 +324,7 @@ object TestConstants {
|
|||
channelOpenerWhitelist = Set.empty,
|
||||
maxPendingChannelsPerPeer = 3,
|
||||
maxTotalPendingChannelsPrivateNodes = 99,
|
||||
channelFundingTimeout = 30 seconds,
|
||||
remoteRbfLimits = RemoteRbfLimits(10, 0),
|
||||
quiescenceTimeout = 2 minutes,
|
||||
balanceThresholds = Nil,
|
||||
|
|
|
@ -28,6 +28,7 @@ import fr.acinq.eclair._
|
|||
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
|
||||
import fr.acinq.eclair.blockchain.{CurrentFeerates, DummyOnChainWallet}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.fsm.Channel
|
||||
import fr.acinq.eclair.channel.states.ChannelStateTestsTags
|
||||
import fr.acinq.eclair.io.Peer._
|
||||
import fr.acinq.eclair.message.OnionMessages.{Recipient, buildMessage}
|
||||
|
@ -76,6 +77,7 @@ class PeerSpec extends FixtureSpec {
|
|||
.modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.DualFunding))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))
|
||||
.modify(_.channelConf.maxHtlcValueInFlightMsat).setToIf(testData.tags.contains("max-htlc-value-in-flight-percent"))(100_000_000 msat)
|
||||
.modify(_.channelConf.maxHtlcValueInFlightPercent).setToIf(testData.tags.contains("max-htlc-value-in-flight-percent"))(25)
|
||||
.modify(_.channelConf.channelFundingTimeout).setToIf(testData.tags.contains("channel_funding_timeout"))(100 millis)
|
||||
.modify(_.autoReconnect).setToIf(testData.tags.contains("auto_reconnect"))(true)
|
||||
|
||||
if (testData.tags.contains("with_node_announcement")) {
|
||||
|
@ -718,6 +720,17 @@ class PeerSpec extends FixtureSpec {
|
|||
assert(channelAborted.channelId == open.temporaryChannelId)
|
||||
}
|
||||
|
||||
test("abort incoming channel after funding timeout", Tag(ChannelStateTestsTags.DualFunding), Tag("channel_funding_timeout")) { f =>
|
||||
import f._
|
||||
|
||||
connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional)))
|
||||
val open = createOpenDualFundedChannelMessage()
|
||||
peerConnection.send(peer, open)
|
||||
assert(channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR].dualFunded)
|
||||
channel.expectMsg(open)
|
||||
channel.expectMsg(Channel.TickChannelOpenTimeout)
|
||||
}
|
||||
|
||||
test("kill peer with no channels if connection dies before receiving `ConnectionReady`") { f =>
|
||||
import f._
|
||||
val probe = TestProbe()
|
||||
|
|
Loading…
Add table
Reference in a new issue