Skip to content

Commit 4de2b89

Browse files
committed
Use official feature bit for option_simple_taproot
Use the official feature bit and name for taproot channels and the corresponding channel types. Activate taproot channels support by default (without support for announcing such channels yet).
1 parent d7ee663 commit 4de2b89

File tree

12 files changed

+78
-59
lines changed

12 files changed

+78
-59
lines changed

docs/release-notes/eclair-vnext.md

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

55
## Major changes
66

7-
<insert changes>
7+
### Taproot Channels (without announcements)
8+
9+
This release adds support for taproot channels, as specified in [the BOLTs](https://github.com/lightning/bolts/pull/995).
10+
Taproot channels improve privacy and cost less on-chain fees by using musig2 for the channel output.
11+
This release is fully compatible with the `lnd` implementation of taproot channels.
12+
13+
We don't support public taproot channels yet, as the gossip mechanism for this isn't finalized yet.
14+
It is thus only possible to open "private" (unannounced) taproot channels.
15+
You may follow progress on the specification for public taproot channels [here](https://github.com/lightning/bolts/pull/1059).
16+
17+
This feature is active by default. To disable it, add the following to your `eclair.conf`:
18+
19+
```conf
20+
eclair.features.option_simple_taproot = disabled
21+
```
22+
23+
To open a taproot channel with a node that supports the `option_simple_taproot` feature, use the following command:
24+
25+
```sh
26+
$ eclair-cli open --nodeId=<node_id> --fundingSatoshis=<funding_amount> --channelType=simple_taproot_channel --announceChannel=false
27+
```
828

929
### Package relay
1030

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ 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
91+
option_simple_taproot = optional
9192
trampoline_payment_prototype = disabled
9293
async_payment_prototype = disabled
9394
on_the_fly_funding = disabled

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ object Features {
312312
val mandatory = 60
313313
}
314314

315+
case object SimpleTaprootChannels extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
316+
val rfcName = "option_simple_taproot"
317+
val mandatory = 80
318+
}
319+
315320
/** This feature bit indicates that the node is a mobile wallet that can be woken up via push notifications. */
316321
case object WakeUpNotificationClient extends Feature with InitFeature {
317322
val rfcName = "wake_up_notification_client"
@@ -346,11 +351,6 @@ object Features {
346351
val mandatory = 564
347352
}
348353

349-
case object SimpleTaprootChannelsStaging extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
350-
val rfcName = "option_simple_taproot_staging"
351-
val mandatory = 180
352-
}
353-
354354
/**
355355
* Activate this feature to provide on-the-fly funding to remote nodes, as specified in bLIP 36: https://github.com/lightning/blips/blob/master/blip-0036.md.
356356
* TODO: add NodeFeature once bLIP is merged.
@@ -394,8 +394,8 @@ object Features {
394394
ZeroConf,
395395
KeySend,
396396
SimpleClose,
397+
SimpleTaprootChannels,
397398
SimpleTaprootChannelsPhoenix,
398-
SimpleTaprootChannelsStaging,
399399
WakeUpNotificationClient,
400400
TrampolinePaymentPrototype,
401401
AsyncPaymentPrototype,
@@ -415,8 +415,8 @@ object Features {
415415
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
416416
KeySend -> (VariableLengthOnion :: Nil),
417417
SimpleClose -> (ShutdownAnySegwit :: Nil),
418+
SimpleTaprootChannels -> (ChannelType :: SimpleClose :: Nil),
418419
SimpleTaprootChannelsPhoenix -> (ChannelType :: SimpleClose :: Nil),
419-
SimpleTaprootChannelsStaging -> (ChannelType :: SimpleClose :: Nil),
420420
AsyncPaymentPrototype -> (TrampolinePaymentPrototype :: Nil),
421421
OnTheFlyFunding -> (SplicePrototype :: Nil),
422422
FundingFeeCredit -> (OnTheFlyFunding :: Nil)

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

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -118,27 +118,25 @@ object ChannelTypes {
118118
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
119119
override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
120120
}
121-
case class SimpleTaprootChannelsPhoenix(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
122-
/** Known channel-type features */
121+
case class SimpleTaprootChannel(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
123122
override def features: Set[ChannelTypeFeature] = Set(
124123
if (scidAlias) Some(Features.ScidAlias) else None,
125124
if (zeroConf) Some(Features.ZeroConf) else None,
126-
Some(Features.SimpleTaprootChannelsPhoenix),
125+
Some(Features.SimpleTaprootChannels),
127126
).flatten
128127
override def paysDirectlyToWallet: Boolean = false
129-
override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat
130-
override def toString: String = s"simple_taproot_channel_phoenix${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
128+
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
129+
override def toString: String = s"simple_taproot_channel${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
131130
}
132-
case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
133-
/** Known channel-type features */
131+
case class SimpleTaprootChannelPhoenix(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
134132
override def features: Set[ChannelTypeFeature] = Set(
135133
if (scidAlias) Some(Features.ScidAlias) else None,
136134
if (zeroConf) Some(Features.ZeroConf) else None,
137-
Some(Features.SimpleTaprootChannelsStaging),
135+
Some(Features.SimpleTaprootChannelsPhoenix),
138136
).flatten
139137
override def paysDirectlyToWallet: Boolean = false
140-
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
141-
override def toString: String = s"simple_taproot_channel_staging${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
138+
override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat
139+
override def toString: String = s"simple_taproot_channel_phoenix${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
142140
}
143141

144142
case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
@@ -164,17 +162,17 @@ object ChannelTypes {
164162
AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
165163
AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
166164
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
167-
SimpleTaprootChannelsPhoenix(),
168-
SimpleTaprootChannelsPhoenix(zeroConf = true),
169-
SimpleTaprootChannelsPhoenix(scidAlias = true),
170-
SimpleTaprootChannelsPhoenix(scidAlias = true, zeroConf = true),
171-
SimpleTaprootChannelsStaging(),
172-
SimpleTaprootChannelsStaging(zeroConf = true),
173-
SimpleTaprootChannelsStaging(scidAlias = true),
174-
SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true),
175-
)
176-
.map(channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType)
177-
.toMap
165+
SimpleTaprootChannel(),
166+
SimpleTaprootChannel(zeroConf = true),
167+
SimpleTaprootChannel(scidAlias = true),
168+
SimpleTaprootChannel(scidAlias = true, zeroConf = true),
169+
SimpleTaprootChannelPhoenix(),
170+
SimpleTaprootChannelPhoenix(zeroConf = true),
171+
SimpleTaprootChannelPhoenix(scidAlias = true),
172+
SimpleTaprootChannelPhoenix(scidAlias = true, zeroConf = true),
173+
).map {
174+
channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType
175+
}.toMap
178176

179177
// NB: Bolt 2: features must exactly match in order to identify a channel type.
180178
def fromFeatures(features: Features[InitFeature]): ChannelType = features2ChannelType.getOrElse(features, UnsupportedChannelType(features))
@@ -185,10 +183,10 @@ object ChannelTypes {
185183

186184
val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel
187185
val zeroConf = canUse(Features.ZeroConf)
188-
if (canUse(Features.SimpleTaprootChannelsStaging)) {
189-
SimpleTaprootChannelsStaging(scidAlias, zeroConf)
186+
if (canUse(Features.SimpleTaprootChannels)) {
187+
SimpleTaprootChannel(scidAlias, zeroConf)
190188
} else if (canUse(Features.SimpleTaprootChannelsPhoenix)) {
191-
SimpleTaprootChannelsPhoenix(scidAlias, zeroConf)
189+
SimpleTaprootChannelPhoenix(scidAlias, zeroConf)
192190
} else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
193191
AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf)
194192
} else if (canUse(Features.AnchorOutputs)) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
11071107
// We only support updating phoenix channels to taproot: we ignore other attempts at upgrading the
11081108
// commitment format and will simply apply the previous commitment format.
11091109
val nextCommitmentFormat = msg.channelType_opt match {
1110-
case Some(channelType: ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat =>
1110+
case Some(channelType: ChannelTypes.SimpleTaprootChannelPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat =>
11111111
log.info(s"accepting upgrade to $channelType during splice from commitment format ${parentCommitment.commitmentFormat}")
11121112
PhoenixSimpleTaprootChannelCommitmentFormat
11131113
case Some(channelType) =>
@@ -1175,7 +1175,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
11751175
// We only support updating phoenix channels to taproot: we ignore other attempts at upgrading the
11761176
// commitment format and will simply apply the previous commitment format.
11771177
val nextCommitmentFormat = msg.channelType_opt match {
1178-
case Some(_: ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => PhoenixSimpleTaprootChannelCommitmentFormat
1178+
case Some(_: ChannelTypes.SimpleTaprootChannelPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => PhoenixSimpleTaprootChannelCommitmentFormat
11791179
case _ => parentCommitment.commitmentFormat
11801180
}
11811181
val fundingParams = InteractiveTxParams(

eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
479479
val targetFeerate = FeeratePerKw(2500 sat)
480480
val fundingA = 150_000 sat
481481
val utxosA = Seq(80_000 sat, 120_000 sat)
482-
withFixture(ChannelTypes.SimpleTaprootChannelsStaging(), fundingA, utxosA, 0 sat, Nil, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
482+
withFixture(ChannelTypes.SimpleTaprootChannel(), fundingA, utxosA, 0 sat, Nil, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
483483
import f._
484484

485485
alice ! Start(alice2bob.ref)
@@ -825,7 +825,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
825825
val utxosA = Seq(150_000 sat)
826826
val fundingB1 = 90_000 sat
827827
val utxosB = Seq(130_000 sat)
828-
withFixture(ChannelTypes.SimpleTaprootChannelsPhoenix(), fundingA1, utxosA, fundingB1, utxosB, FeeratePerKw(1000 sat), 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f =>
828+
withFixture(ChannelTypes.SimpleTaprootChannelPhoenix(), fundingA1, utxosA, fundingB1, utxosB, FeeratePerKw(1000 sat), 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f =>
829829
import f._
830830

831831
val probe = TestProbe()
@@ -1996,7 +1996,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
19961996
val utxosA = Seq(340_000 sat, 40_000 sat, 35_000 sat)
19971997
val fundingB1 = 80_000 sat
19981998
val utxosB = Seq(290_000 sat, 20_000 sat, 15_000 sat)
1999-
withFixture(ChannelTypes.SimpleTaprootChannelsStaging(), fundingA1, utxosA, fundingB1, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
1999+
withFixture(ChannelTypes.SimpleTaprootChannel(), fundingA1, utxosA, fundingB1, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
20002000
import f._
20012001

20022002
val probe = TestProbe()
@@ -2384,7 +2384,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
23842384
}
23852385

23862386
test("invalid tx_signatures (missing shared input signature, taproot)") {
2387-
testTxSignaturesMissingSharedInputSigs(ChannelTypes.SimpleTaprootChannelsStaging())
2387+
testTxSignaturesMissingSharedInputSigs(ChannelTypes.SimpleTaprootChannel())
23882388
}
23892389

23902390
test("invalid commitment index") {
@@ -2934,7 +2934,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
29342934
test("invalid commit_sig (taproot)") {
29352935
val (alice2bob, bob2alice) = (TestProbe(), TestProbe())
29362936
val wallet = new SingleKeyOnChainWallet()
2937-
val params = createFixtureParams(ChannelTypes.SimpleTaprootChannelsPhoenix(), 100_000 sat, 25_000 sat, FeeratePerKw(5000 sat), 330 sat, 0)
2937+
val params = createFixtureParams(ChannelTypes.SimpleTaprootChannelPhoenix(), 100_000 sat, 25_000 sat, FeeratePerKw(5000 sat), 330 sat, 0)
29382938
val alice = params.spawnTxBuilderAlice(wallet)
29392939
val bob = params.spawnTxBuilderBob(wallet)
29402940
alice ! Start(alice2bob.ref)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
267267
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ScidAlias))(_.updated(Features.ScidAlias, FeatureSupport.Optional))
268268
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DualFunding))(_.updated(Features.DualFunding, FeatureSupport.Optional))
269269
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional))
270+
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannels, FeatureSupport.Optional))
270271
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional))
271-
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
272272
)
273273
val nodeParamsB1 = nodeParamsB.copy(features = nodeParamsB.features
274274
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableWumbo))(_.removed(Features.Wumbo))
@@ -282,8 +282,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
282282
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DualFunding))(_.updated(Features.DualFunding, FeatureSupport.Optional))
283283
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional))
284284
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableSplice))(_.removed(Features.SplicePrototype))
285+
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannels, FeatureSupport.Optional))
285286
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional))
286-
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
287287
)
288288
(nodeParamsA1, nodeParamsB1)
289289
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
111111
test("recv AcceptChannel (simple taproot channels phoenix)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f =>
112112
import f._
113113
val accept = bob2alice.expectMsgType[AcceptChannel]
114-
assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix()))
114+
assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelPhoenix()))
115115
assert(accept.commitNonce_opt.isDefined)
116116
bob2alice.forward(alice)
117117
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
@@ -122,7 +122,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
122122
test("recv AcceptChannel (simple taproot channels outputs, missing nonce)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f =>
123123
import f._
124124
val accept = bob2alice.expectMsgType[AcceptChannel]
125-
assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix()))
125+
assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelPhoenix()))
126126
assert(accept.commitNonce_opt.isDefined)
127127
bob2alice.forward(alice, accept.copy(tlvStream = accept.tlvStream.copy(records = accept.tlvStream.records.filterNot(_.isInstanceOf[ChannelTlv.NextLocalNonceTlv]))))
128128
alice2bob.expectMsg(Error(accept.temporaryChannelId, MissingCommitNonce(accept.temporaryChannelId, TxId(ByteVector32.Zeroes), 0).getMessage))

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
110110
test("recv OpenChannel (simple taproot channels)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
111111
import f._
112112
val open = alice2bob.expectMsgType[OpenChannel]
113-
assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsStaging()))
113+
assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannel()))
114114
alice2bob.forward(bob)
115115
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
116116
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)
@@ -120,7 +120,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
120120
test("recv OpenChannel (simple taproot channels, missing nonce)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
121121
import f._
122122
val open = alice2bob.expectMsgType[OpenChannel]
123-
assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsStaging()))
123+
assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannel()))
124124
assert(open.commitNonce_opt.isDefined)
125125
alice2bob.forward(bob, open.copy(tlvStream = open.tlvStream.copy(records = open.tlvStream.records.filterNot(_.isInstanceOf[ChannelTlv.NextLocalNonceTlv]))))
126126
val error = bob2alice.expectMsgType[Error]

0 commit comments

Comments
 (0)