Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/release-notes/eclair-vnext.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ To open a taproot channel with a node that supports the `option_simple_taproot`
$ eclair-cli open --nodeId=<node_id> --fundingSatoshis=<funding_amount> --channelType=simple_taproot_channel --announceChannel=false
```

### Zero-fee Channels (experimental)

This release adds support for zero-fee commitments (using v3/TRUC transactions), as specified in [the BOLTs](https://github.com/lightning/bolts/pull/1228).
With this new type of channels, commitment transactions don't pay any mining fee (in most cases), which gets rid of the `update_fee` mechanism and all of the related channel reserve issues.
It also gets rid of the undesired channel force-closes that happen when the mempool feerate spikes and channel participants disagree on what feerate to use, which has been a major source of wasted on-chain space.
It also offers better protection against pinning attacks (thanks to package relay) and reduces the on-chain footprint compared to anchor output channels.

This feature is not actived by default, because we haven't tested yet that v3/TRUC transactions always propagate to miners.
To enable it, add the following to your `eclair.conf`:

```conf
eclair.features.zero_fee_commitments = optional
```

To open a zero-fee channel with a node that supports the `zero_fee_commitments` feature, use the following command:

```sh
$ eclair-cli open --nodeId=<node_id> --fundingSatoshis=<funding_amount> --channelType=zero_fee_commitments
```

### Remove support for non-anchor channels

We remove the code used to support legacy channels that don't use anchor outputs or taproot.
Expand Down
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ eclair {
option_quiesce = optional
option_attribution_data = optional
option_onion_messages = optional
zero_fee_commitments = disabled
// This feature should only be enabled when acting as an LSP for mobile wallets.
// When activating this feature, the peer-storage section should be customized to match desired SLAs.
option_provide_storage = disabled
Expand Down
6 changes: 6 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ object Features {
val mandatory = 38
}

case object ZeroFeeCommitments extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
val rfcName = "zero_fee_commitments"
val mandatory = 40
}

case object ProvideStorage extends Feature with InitFeature with NodeFeature {
val rfcName = "option_provide_storage"
val mandatory = 42
Expand Down Expand Up @@ -479,6 +484,7 @@ object Features {
Quiescence,
AttributionData,
OnionMessages,
ZeroFeeCommitments,
ProvideStorage,
ChannelType,
ScidAlias,
Expand Down
4 changes: 2 additions & 2 deletions eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong}
import fr.acinq.eclair.Setup.Seeds
import fr.acinq.eclair.blockchain.fee._
import fr.acinq.eclair.channel.ChannelFlags
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy}
import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes}
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager}
import fr.acinq.eclair.db._
Expand Down Expand Up @@ -364,7 +364,7 @@ object NodeParams extends Logging {
require(htlcMinimum > 0.msat, "channel.htlc-minimum-msat must be strictly greater than 0")

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

val maxToLocalCLTV = CltvExpiryDelta(config.getInt("channel.max-to-local-delay-blocks"))
val offeredCLTV = CltvExpiryDelta(config.getInt("channel.to-remote-delay-blocks"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ case class OnChainFeeConf(feeTargets: FeeTargets,
case Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | Transactions.ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat =>
// If the fee has a large enough change, we update the fee.
currentFeeratePerKw.toLong == 0 || Math.abs((currentFeeratePerKw.toLong - nextFeeratePerKw.toLong).toDouble / currentFeeratePerKw.toLong) > updateFeeMinDiffRatio
case Transactions.ZeroFeeCommitmentFormat =>
// We never send update_fee when using zero-fee commitments.
false
}
}

Expand All @@ -111,6 +114,7 @@ case class OnChainFeeConf(feeTargets: FeeTargets,
val networkFeerate = feerates.fast
val networkMinFee = feerates.minimum
commitmentFormat match {
case Transactions.ZeroFeeCommitmentFormat => FeeratePerKw(0 sat)
case Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat | Transactions.PhoenixSimpleTaprootChannelCommitmentFormat =>
// Since Bitcoin Core v28, 1-parent-1-child package relay has been deployed: it should be ok if the commit tx
// doesn't propagate on its own.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ object ChannelTypes {
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
override def toString: String = s"simple_taproot_channel${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
}
case class ZeroFeeCommitments(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
override def features: Set[ChannelTypeFeature] = Set(
if (scidAlias) Some(Features.ScidAlias) else None,
if (zeroConf) Some(Features.ZeroConf) else None,
Some(Features.ZeroFeeCommitments)
).flatten
override def commitmentFormat: CommitmentFormat = ZeroFeeCommitmentFormat
override def toString: String = s"zero_fee_commitments${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
}

case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
override def features: Set[InitFeature] = featureBits.activated.keySet
Expand Down Expand Up @@ -131,6 +140,10 @@ object ChannelTypes {
SimpleTaprootChannel(zeroConf = true),
SimpleTaprootChannel(scidAlias = true),
SimpleTaprootChannel(scidAlias = true, zeroConf = true),
ZeroFeeCommitments(),
ZeroFeeCommitments(zeroConf = true),
ZeroFeeCommitments(scidAlias = true),
ZeroFeeCommitments(scidAlias = true, zeroConf = true),
SimpleTaprootChannelsPhoenix,
).map {
channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType
Expand All @@ -154,6 +167,8 @@ object ChannelTypes {
if (!announceChannel && Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannels)) {
// We currently only support unannounced taproot channels.
Some(SimpleTaprootChannel(scidAlias = useScidAlias))
} else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.ZeroFeeCommitments)) {
Some(ZeroFeeCommitments(scidAlias = useScidAlias))
} else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputsZeroFeeHtlcTx)) {
Some(AnchorOutputsZeroFeeHtlcTx(scidAlias = useScidAlias))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,8 +740,8 @@ object Commitment {
commitmentInput: InputInfo,
commitmentFormat: CommitmentFormat,
spec: CommitmentSpec): (CommitTx, Seq[UnsignedHtlcTx]) = {
val outputs = makeCommitTxOutputs(localFundingKey.publicKey, remoteFundingPubKey, commitKeys.publicKeys, channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, commitKeys.ourPaymentBasePoint, channelParams.remoteParams.paymentBasepoint, channelParams.localParams.isChannelOpener, outputs)
val outputs = makeCommitTxOutputs(localFundingKey.publicKey, remoteFundingPubKey, commitmentInput, commitKeys.publicKeys, channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, commitKeys.ourPaymentBasePoint, channelParams.remoteParams.paymentBasepoint, channelParams.localParams.isChannelOpener, commitmentFormat, outputs)
val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat)
(commitTx, htlcTxs)
}
Expand All @@ -755,8 +755,8 @@ object Commitment {
commitmentInput: InputInfo,
commitmentFormat: CommitmentFormat,
spec: CommitmentSpec): (CommitTx, Seq[UnsignedHtlcTx]) = {
val outputs = makeCommitTxOutputs(remoteFundingPubKey, localFundingKey.publicKey, commitKeys.publicKeys, !channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, channelParams.remoteParams.paymentBasepoint, commitKeys.ourPaymentBasePoint, !channelParams.localParams.isChannelOpener, outputs)
val outputs = makeCommitTxOutputs(remoteFundingPubKey, localFundingKey.publicKey, commitmentInput, commitKeys.publicKeys, !channelParams.localParams.paysCommitTxFees, commitParams.dustLimit, commitParams.toSelfDelay, spec, commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, channelParams.remoteParams.paymentBasepoint, commitKeys.ourPaymentBasePoint, !channelParams.localParams.isChannelOpener, commitmentFormat, outputs)
val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat)
(commitTx, htlcTxs)
}
Expand Down
Loading
Loading