Skip to content

Commit 9911cb7

Browse files
authored
Remove support for non-anchor channels (#3173)
We remove support for `static_remotekey` channels and `default` channels, as advertised in the v0.13 release. This lets us remove some code related to feerate management and simplifies the test matrix. Node operators that still have such channels must not run this version of `eclair`, which will otherwise fail to start. Note that for now, we keep sending `update_fee` whenever necessary. We could remove that now that package relay allows 1p1c packages to propagate even when the parent is below the mempool minimum feerate, but we defer that to a later PR for simplicity.
1 parent 26d0350 commit 9911cb7

File tree

86 files changed

+1129
-3942
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1129
-3942
lines changed

docs/release-notes/eclair-vnext.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
## Major changes
66

7-
<insert changes>
7+
### Remove support for non-anchor channels
8+
9+
We remove the code used to support legacy channels that don't use anchor outputs or taproot.
10+
If you still have such channels, eclair won't start: you will need to close those channels, and will only be able to update eclair once they have been successfully closed.
811

912
### Configuration changes
1013

eclair-core/src/main/resources/reference.conf

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ eclair {
3939
// - unlock: eclair will automatically unlock the corresponding utxos
4040
// - ignore: eclair will leave these utxos locked and start
4141
startup-locked-utxos-behavior = "stop"
42-
final-pubkey-refresh-delay = 3 seconds
42+
final-address-refresh-delay = 3 seconds
4343
// If true, eclair will poll bitcoind for 30 seconds at start-up before giving up.
4444
wait-for-bitcoind-up = true
4545
// The txid of a transaction that exists in your custom signet network or "" to skip this check.
@@ -87,7 +87,7 @@ eclair {
8787
// node that you trust using override-init-features (see below).
8888
option_zeroconf = disabled
8989
keysend = disabled
90-
option_simple_close=optional
90+
option_simple_close = optional
9191
trampoline_payment_prototype = disabled
9292
async_payment_prototype = disabled
9393
on_the_fly_funding = disabled
@@ -177,8 +177,6 @@ eclair {
177177
channel-opener-whitelist = [] // a list of public keys; we will ignore rate limits on pending channels from these peers
178178
}
179179

180-
accept-incoming-static-remote-key-channels = false // whether we accept new incoming static_remote_key channels (which are obsolete, nodes should use anchor_output now)
181-
182180
quiescence-timeout = 1 minutes // maximum time we will stay quiescent (or wait to reach quiescence) before disconnecting
183181

184182
channel-update {
@@ -292,8 +290,8 @@ eclair {
292290
max-closing-feerate = 10
293291

294292
feerate-tolerance {
295-
ratio-low = 0.5 // will allow remote fee rates as low as half our local feerate (only enforced when not using anchor outputs)
296-
ratio-high = 10.0 // will allow remote fee rates as high as 10 times our local feerate (for all commitment formats)
293+
ratio-low = 0.5 // will allow remote fee rates as low as half our local feerate for funding/splice transactions
294+
ratio-high = 10.0 // will allow remote fee rates as high as 10 times our local feerate for commitment transactions and funding/splice transactions
297295
// when using anchor outputs, we only need to use a commitment feerate that allows the tx to propagate: we will use CPFP to speed up confirmation if needed.
298296
// the following value is the maximum feerate we'll use for our commit tx (in sat/byte)
299297
anchor-output-max-commit-feerate = 10

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ object DBChecker extends Logging {
4040
case _ => ()
4141
}
4242
channels
43-
case Failure(_) => throw IncompatibleDBException
43+
case Failure(t) => throw IncompatibleDBException(t)
4444
}
4545
}
4646

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

Lines changed: 11 additions & 9 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
2425
import fr.acinq.eclair.channel.fsm.Channel
2526
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._
@@ -39,7 +39,7 @@ import fr.acinq.eclair.router.Graph.HeuristicsConstants
3939
import fr.acinq.eclair.router.Router._
4040
import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf, Router}
4141
import fr.acinq.eclair.tor.Socks5ProxyParams
42-
import fr.acinq.eclair.transactions.Transactions
42+
import fr.acinq.eclair.transactions.Transactions.{PhoenixSimpleTaprootChannelCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat}
4343
import fr.acinq.eclair.wire.protocol._
4444
import grizzled.slf4j.Logging
4545
import scodec.bits.ByteVector
@@ -129,14 +129,17 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
129129
max = (fundingFeerate * feerateTolerance.ratioHigh).max(minimumFeerate),
130130
)
131131
// We use the most likely commitment format, even though there is no guarantee that this is the one that will be used.
132-
val commitmentFormat = ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, announceChannel = false).commitmentFormat
132+
val commitmentFormat = if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsPhoenix)) {
133+
PhoenixSimpleTaprootChannelCommitmentFormat
134+
} else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsStaging)) {
135+
ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
136+
} else {
137+
ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
138+
}
133139
val commitmentFeerate = onChainFeeConf.getCommitmentFeerate(currentBitcoinCoreFeerates, remoteNodeId, commitmentFormat)
134140
val commitmentRange = RecommendedFeeratesTlv.CommitmentFeerateRange(
135-
min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate),
136-
max = (commitmentFormat match {
137-
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
138-
case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
139-
}).max(minimumFeerate),
141+
min = Seq(commitmentFeerate * feerateTolerance.ratioLow, minimumFeerate).max,
142+
max = Seq(commitmentFeerate * feerateTolerance.ratioHigh, feerateTolerance.anchorOutputMaxCommitFeerate, minimumFeerate).max,
140143
)
141144
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))
142145
}
@@ -599,7 +602,6 @@ object NodeParams extends Logging {
599602
quiescenceTimeout = FiniteDuration(config.getDuration("channel.quiescence-timeout").getSeconds, TimeUnit.SECONDS),
600603
balanceThresholds = config.getConfigList("channel.channel-update.balance-thresholds").asScala.map(conf => BalanceThreshold(Satoshi(conf.getLong("available-sat")), Satoshi(conf.getLong("max-htlc-sat")))).toSeq,
601604
minTimeBetweenUpdates = FiniteDuration(config.getDuration("channel.channel-update.min-time-between-updates").getSeconds, TimeUnit.SECONDS),
602-
acceptIncomingStaticRemoteKeyChannels = config.getBoolean("channel.accept-incoming-static-remote-key-channels")
603605
),
604606
onChainFeeConf = OnChainFeeConf(
605607
feeTargets = feeTargets,

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,24 +268,17 @@ class Setup(val datadir: File,
268268
})
269269
_ <- feeratesRetrieved.future
270270

271-
finalPubkey = new AtomicReference[PublicKey](null)
272271
finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](null)
273-
pubkeyRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-pubkey-refresh-delay").getSeconds, TimeUnit.SECONDS)
272+
finalAddressRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-address-refresh-delay").getSeconds, TimeUnit.SECONDS)
274273
// there are 3 possibilities regarding onchain key management:
275274
// 1) there is no `eclair-signer.conf` file in Eclair's data directory, Eclair will not manage Bitcoin core keys, and Eclair's API will not return bitcoin core descriptors. This is the default mode.
276275
// 2) there is an `eclair-signer.conf` file in Eclair's data directory, but the name of the wallet set in `eclair-signer.conf` does not match the `eclair.bitcoind.wallet` setting in `eclair.conf`.
277276
// Eclair will use the wallet set in `eclair.conf` and will not manage Bitcoin core keys (here we don't set an optional onchain key manager in our bitcoin client) BUT its API will return bitcoin core descriptors.
278277
// This is how you would create a new bitcoin wallet whose private keys are managed by Eclair.
279278
// 3) there is an `eclair-signer.conf` file in Eclair's data directory, and the name of the wallet set in `eclair-signer.conf` matches the `eclair.bitcoind.wallet` setting in `eclair.conf`.
280279
// Eclair will assume that this is a watch-only bitcoin wallet that has been created from descriptors generated by Eclair, and will manage its private keys, and here we pass the onchain key manager to our bitcoin client.
281-
bitcoinClient = new BitcoinCoreClient(bitcoin, nodeParams.liquidityAdsConfig.lockUtxos, if (bitcoin.wallet == onChainKeyManager_opt.map(_.walletName)) onChainKeyManager_opt else None) with OnChainPubkeyCache {
282-
val refresher: typed.ActorRef[OnChainAddressRefresher.Command] = system.spawn(Behaviors.supervise(OnChainAddressRefresher(this, finalPubkey, finalPubkeyScript, pubkeyRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "onchain-address-manager")
283-
284-
override def getP2wpkhPubkey(renew: Boolean): PublicKey = {
285-
val key = finalPubkey.get()
286-
if (renew) refresher ! OnChainAddressRefresher.RenewPubkey
287-
key
288-
}
280+
bitcoinClient = new BitcoinCoreClient(bitcoin, nodeParams.liquidityAdsConfig.lockUtxos, if (bitcoin.wallet == onChainKeyManager_opt.map(_.walletName)) onChainKeyManager_opt else None) with OnChainAddressCache {
281+
val refresher: typed.ActorRef[OnChainAddressRefresher.Command] = system.spawn(Behaviors.supervise(OnChainAddressRefresher(this, finalPubkeyScript, finalAddressRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "on-chain-address-manager")
289282

290283
override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = {
291284
val script = finalPubkeyScript.get()
@@ -294,8 +287,6 @@ class Setup(val datadir: File,
294287
}
295288
}
296289
_ = if (bitcoinClient.useEclairSigner) logger.info("using eclair to sign bitcoin core transactions")
297-
initialPubkey <- bitcoinClient.getP2wpkhPubkey()
298-
_ = finalPubkey.set(initialPubkey)
299290
// We use the default address type configured on the Bitcoin Core node.
300291
initialPubkeyScript <- bitcoinClient.getReceivePublicKeyScript(addressType_opt = None)
301292
_ = finalPubkeyScript.set(initialPubkeyScript)
@@ -494,7 +485,7 @@ case class Kit(nodeParams: NodeParams,
494485
postman: typed.ActorRef[Postman.Command],
495486
offerManager: typed.ActorRef[OfferManager.Command],
496487
defaultOfferHandler: typed.ActorRef[OfferManager.HandlerCommand],
497-
wallet: OnChainWallet with OnChainPubkeyCache)
488+
wallet: OnChainWallet with OnChainAddressCache)
498489

499490
object Kit {
500491

@@ -518,7 +509,7 @@ case class BitcoinWalletNotLoadedException(wallet: String, loaded: List[String])
518509

519510
case object EmptyAPIPasswordException extends RuntimeException("must set a password for the json-rpc api")
520511

521-
case object IncompatibleDBException extends RuntimeException("database is not compatible with this version of eclair")
512+
case class IncompatibleDBException(t: Throwable) extends RuntimeException(s"database is not compatible with this version of eclair: ${t.getMessage}")
522513

523514
case object IncompatibleNetworkDBException extends RuntimeException("network database is not compatible with this version of eclair")
524515

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package fr.acinq.eclair.blockchain
1818

1919
import fr.acinq.bitcoin.psbt.Psbt
20-
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2120
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, ScriptElt, Transaction, TxId}
2221
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType
2322
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
@@ -120,18 +119,10 @@ trait OnChainAddressGenerator {
120119
/** Generate the public key script for a new wallet address. */
121120
def getReceivePublicKeyScript(addressType_opt: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]]
122121

123-
/** Generate a p2wpkh wallet address and return the corresponding public key. */
124-
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey]
125-
126122
}
127123

128124
/** A caching layer for [[OnChainAddressGenerator]] that provides synchronous access to wallet addresses and keys. */
129-
trait OnChainPubkeyCache {
130-
131-
/**
132-
* @param renew applies after requesting the current pubkey, and is asynchronous.
133-
*/
134-
def getP2wpkhPubkey(renew: Boolean): PublicKey
125+
trait OnChainAddressCache {
135126

136127
/**
137128
* @param renew applies after requesting the current script, and is asynchronous.

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresher.scala

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package fr.acinq.eclair.blockchain.bitcoind
33

44
import akka.actor.typed.Behavior
55
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler}
6-
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
76
import fr.acinq.bitcoin.scalacompat.{Script, ScriptElt}
87
import fr.acinq.eclair.blockchain.OnChainAddressGenerator
98

@@ -19,26 +18,23 @@ object OnChainAddressRefresher {
1918

2019
// @formatter:off
2120
sealed trait Command
22-
case object RenewPubkey extends Command
2321
case object RenewPubkeyScript extends Command
24-
private case class SetPubkey(pubkey: PublicKey) extends Command
2522
private case class SetPubkeyScript(script: Seq[ScriptElt]) extends Command
2623
private case class Error(reason: Throwable) extends Command
2724
private case object Done extends Command
2825
// @formatter:on
2926

30-
def apply(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], finalPubkeyScript: AtomicReference[Seq[ScriptElt]], delay: FiniteDuration): Behavior[Command] = {
27+
def apply(generator: OnChainAddressGenerator, finalPubkeyScript: AtomicReference[Seq[ScriptElt]], delay: FiniteDuration): Behavior[Command] = {
3128
Behaviors.setup { context =>
3229
Behaviors.withTimers { timers =>
33-
val refresher = new OnChainAddressRefresher(generator, finalPubkey, finalPubkeyScript, context, timers, delay)
30+
val refresher = new OnChainAddressRefresher(generator, finalPubkeyScript, context, timers, delay)
3431
refresher.idle()
3532
}
3633
}
3734
}
3835
}
3936

4037
private class OnChainAddressRefresher(generator: OnChainAddressGenerator,
41-
finalPubkey: AtomicReference[PublicKey],
4238
finalPubkeyScript: AtomicReference[Seq[ScriptElt]],
4339
context: ActorContext[OnChainAddressRefresher.Command],
4440
timers: TimerScheduler[OnChainAddressRefresher.Command], delay: FiniteDuration) {
@@ -47,13 +43,6 @@ private class OnChainAddressRefresher(generator: OnChainAddressGenerator,
4743

4844
/** In that state, we're ready to renew our on-chain address whenever requested. */
4945
def idle(): Behavior[Command] = Behaviors.receiveMessage {
50-
case RenewPubkey =>
51-
context.log.debug("renewing pubkey (current={})", finalPubkey.get())
52-
context.pipeToSelf(generator.getP2wpkhPubkey()) {
53-
case Success(pubkey) => SetPubkey(pubkey)
54-
case Failure(reason) => Error(reason)
55-
}
56-
renewing()
5746
case RenewPubkeyScript =>
5847
context.log.debug("renewing script (current={})", Script.write(finalPubkeyScript.get()).toHex)
5948
context.pipeToSelf(generator.getReceivePublicKeyScript()) {
@@ -68,44 +57,28 @@ private class OnChainAddressRefresher(generator: OnChainAddressGenerator,
6857

6958
/** We ignore concurrent requests while waiting for bitcoind to respond. */
7059
private def renewing(): Behavior[Command] = Behaviors.receiveMessage {
71-
case SetPubkey(pubkey) =>
72-
timers.startSingleTimer(Done, delay)
73-
delaying(Some(pubkey), None)
7460
case SetPubkeyScript(script) =>
7561
timers.startSingleTimer(Done, delay)
76-
delaying(None, Some(script))
62+
delaying(script)
7763
case Error(reason) =>
78-
context.log.error("cannot renew public key or script", reason)
64+
context.log.error("cannot renew script", reason)
7965
idle()
8066
case cmd =>
8167
context.log.debug("ignoring command={} while waiting for bitcoin core's response", cmd)
8268
Behaviors.same
8369
}
8470

8571
/**
86-
* After receiving our new script or pubkey from bitcoind, we wait before updating our current values.
72+
* After receiving our new address from bitcoind, we wait before updating our current value.
8773
* While waiting, we ignore additional requests to renew.
8874
*
8975
* This ensures that a burst of requests during a mass force-close use the same final on-chain address instead of
9076
* creating a lot of address churn on our bitcoin wallet.
91-
*
92-
* Note that while we're updating our final script, we will ignore requests to update our final public key (and the
93-
* other way around). This is fine, since the public key is only used:
94-
* - when opening static_remotekey channels, which is disabled by default
95-
* - when closing channels with peers that don't support shutdown_anysegwit (which should be widely supported)
96-
*
97-
* In practice, we most likely always use [[RenewPubkeyScript]].
9877
*/
99-
private def delaying(nextPubkey_opt: Option[PublicKey], nextScript_opt: Option[Seq[ScriptElt]]): Behavior[Command] = Behaviors.receiveMessage {
78+
private def delaying(nextScript: Seq[ScriptElt]): Behavior[Command] = Behaviors.receiveMessage {
10079
case Done =>
101-
nextPubkey_opt.foreach { nextPubkey =>
102-
context.log.info("setting pubkey to {}", nextPubkey)
103-
finalPubkey.set(nextPubkey)
104-
}
105-
nextScript_opt.foreach { nextScript =>
106-
context.log.info("setting script to {}", Script.write(nextScript).toHex)
107-
finalPubkeyScript.set(nextScript)
108-
}
80+
context.log.info("setting script to {}", Script.write(nextScript).toHex)
81+
finalPubkeyScript.set(nextScript)
10982
idle()
11083
case cmd =>
11184
context.log.debug("rate-limiting command={}", cmd)

0 commit comments

Comments
 (0)