diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 5104f7777b..12e5127208 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -312,6 +312,11 @@ object Features { val mandatory = 60 } + case object PhoenixZeroReserve extends Feature with InitFeature with ChannelTypeFeature with PermanentChannelFeature { + val rfcName = "phoenix_zero_reserve" + val mandatory = 128 + } + /** This feature bit indicates that the node is a mobile wallet that can be woken up via push notifications. */ case object WakeUpNotificationClient extends Feature with InitFeature { val rfcName = "wake_up_notification_client" @@ -401,7 +406,8 @@ object Features { AsyncPaymentPrototype, SplicePrototype, OnTheFlyFunding, - FundingFeeCredit + FundingFeeCredit, + PhoenixZeroReserve ) // Features may depend on other features, as specified in Bolt 9. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index ad4799e909..7ca5b539b2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -118,17 +118,6 @@ object ChannelTypes { override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } - case class SimpleTaprootChannelsPhoenix(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { - /** Known channel-type features */ - override def features: Set[ChannelTypeFeature] = Set( - if (scidAlias) Some(Features.ScidAlias) else None, - if (zeroConf) Some(Features.ZeroConf) else None, - Some(Features.SimpleTaprootChannelsPhoenix), - ).flatten - override def paysDirectlyToWallet: Boolean = false - override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat - override def toString: String = s"simple_taproot_channel_phoenix${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" - } case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { /** Known channel-type features */ override def features: Set[ChannelTypeFeature] = Set( @@ -145,6 +134,16 @@ object ChannelTypes { override def features: Set[InitFeature] = featureBits.activated.keySet override def toString: String = s"0x${featureBits.toByteVector.toHex}" } + + // Phoenix uses custom channel types, that we may remove in the future. + case object SimpleTaprootChannelsPhoenix extends SupportedChannelType { + /** Known channel-type features */ + override def features: Set[ChannelTypeFeature] = Set(Features.PhoenixZeroReserve, Features.SimpleTaprootChannelsPhoenix) + override def paysDirectlyToWallet: Boolean = false + override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat + override def toString: String = "phoenix_simple_taproot_channel" + } + // @formatter:on private val features2ChannelType: Map[Features[_ <: InitFeature], SupportedChannelType] = Set( @@ -164,10 +163,7 @@ object ChannelTypes { AnchorOutputsZeroFeeHtlcTx(zeroConf = true), AnchorOutputsZeroFeeHtlcTx(scidAlias = true), AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), - SimpleTaprootChannelsPhoenix(), - SimpleTaprootChannelsPhoenix(zeroConf = true), - SimpleTaprootChannelsPhoenix(scidAlias = true), - SimpleTaprootChannelsPhoenix(scidAlias = true, zeroConf = true), + SimpleTaprootChannelsPhoenix, SimpleTaprootChannelsStaging(), SimpleTaprootChannelsStaging(zeroConf = true), SimpleTaprootChannelsStaging(scidAlias = true), @@ -188,7 +184,7 @@ object ChannelTypes { if (canUse(Features.SimpleTaprootChannelsStaging)) { SimpleTaprootChannelsStaging(scidAlias, zeroConf) } else if (canUse(Features.SimpleTaprootChannelsPhoenix)) { - SimpleTaprootChannelsPhoenix(scidAlias, zeroConf) + SimpleTaprootChannelsPhoenix } else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) { AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf) } else if (canUse(Features.AnchorOutputs)) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index ccbbabc623..fbad954c02 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -30,6 +30,7 @@ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient +import fr.acinq.eclair.channel.ChannelTypes.SimpleTaprootChannelsPhoenix import fr.acinq.eclair.channel.Commitments.PostRevocationAction import fr.acinq.eclair.channel.Helpers.Closing.MutualClose import fr.acinq.eclair.channel.Helpers.Syncing.SyncResult @@ -1113,8 +1114,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We only support updating phoenix channels to taproot: we ignore other attempts at upgrading the // commitment format and will simply apply the previous commitment format. val nextCommitmentFormat = msg.channelType_opt match { - case Some(channelType: ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => - log.info(s"accepting upgrade to $channelType during splice from commitment format ${parentCommitment.commitmentFormat}") + case Some(ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => + log.info(s"accepting upgrade to SimpleTaprootChannelsPhoenix during splice from commitment format ${parentCommitment.commitmentFormat}") PhoenixSimpleTaprootChannelCommitmentFormat case Some(channelType) => log.info(s"rejecting upgrade to $channelType during splice from commitment format ${parentCommitment.commitmentFormat}") @@ -1181,7 +1182,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We only support updating phoenix channels to taproot: we ignore other attempts at upgrading the // commitment format and will simply apply the previous commitment format. val nextCommitmentFormat = msg.channelType_opt match { - case Some(_: ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => PhoenixSimpleTaprootChannelCommitmentFormat + case Some(ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => PhoenixSimpleTaprootChannelCommitmentFormat case _ => parentCommitment.commitmentFormat } val fundingParams = InteractiveTxParams( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index a5302ae7a9..9d27115cf0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -825,7 +825,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val utxosA = Seq(150_000 sat) val fundingB1 = 90_000 sat val utxosB = Seq(130_000 sat) - withFixture(ChannelTypes.SimpleTaprootChannelsPhoenix(), fundingA1, utxosA, fundingB1, utxosB, FeeratePerKw(1000 sat), 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f => + withFixture(ChannelTypes.SimpleTaprootChannelsPhoenix, fundingA1, utxosA, fundingB1, utxosB, FeeratePerKw(1000 sat), 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f => import f._ val probe = TestProbe() @@ -2934,7 +2934,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit test("invalid commit_sig (taproot)") { val (alice2bob, bob2alice) = (TestProbe(), TestProbe()) val wallet = new SingleKeyOnChainWallet() - val params = createFixtureParams(ChannelTypes.SimpleTaprootChannelsPhoenix(), 100_000 sat, 25_000 sat, FeeratePerKw(5000 sat), 330 sat, 0) + val params = createFixtureParams(ChannelTypes.SimpleTaprootChannelsPhoenix, 100_000 sat, 25_000 sat, FeeratePerKw(5000 sat), 330 sat, 0) val alice = params.spawnTxBuilderAlice(wallet) val bob = params.spawnTxBuilderBob(wallet) alice ! Start(alice2bob.ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 5f79b82b2c..d82fe3ffea 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -110,7 +110,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv AcceptChannel (simple taproot channels phoenix)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix())) + assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(accept.commitNonce_opt.isDefined) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) @@ -121,7 +121,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv AcceptChannel (simple taproot channels outputs, missing nonce)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix())) + assert(accept.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(accept.commitNonce_opt.isDefined) bob2alice.forward(alice, accept.copy(tlvStream = accept.tlvStream.copy(records = accept.tlvStream.records.filterNot(_.isInstanceOf[ChannelTlv.NextLocalNonceTlv])))) alice2bob.expectMsg(Error(accept.temporaryChannelId, MissingCommitNonce(accept.temporaryChannelId, TxId(ByteVector32.Zeroes), 0).getMessage)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala index 5f8c3db3fa..ff1645aa5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala @@ -757,7 +757,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik import f._ val htlcs = setupHtlcs(f) - initiateSplice(f, spliceIn_opt = Some(SpliceIn(400_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + initiateSplice(f, spliceIn_opt = Some(SpliceIn(400_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == PhoenixSimpleTaprootChannelCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) resolveHtlcs(f, htlcs) @@ -767,7 +767,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik import f._ val htlcs = setupHtlcs(f) - initiateSplice(f, spliceIn_opt = Some(SpliceIn(400_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + initiateSplice(f, spliceIn_opt = Some(SpliceIn(400_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) resolveHtlcs(f, htlcs) @@ -3724,7 +3724,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val htlcs = setupHtlcs(f) // Our first splice upgrades the channel to taproot. - val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) checkWatchConfirmed(f, fundingTx1) // The first splice confirms on Bob's side. @@ -3819,7 +3819,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val htlcs = setupHtlcs(f) // Our splice upgrades the channel to taproot. - val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == PhoenixSimpleTaprootChannelCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) checkWatchConfirmed(f, spliceTx) @@ -3860,7 +3860,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val htlcs = setupHtlcs(f) // Our splice upgrades the channel to taproot. - val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == PhoenixSimpleTaprootChannelCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) checkWatchConfirmed(f, spliceTx) @@ -3911,7 +3911,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val htlcs = setupHtlcs(f) // Our splice upgrades the channel to taproot. - val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == PhoenixSimpleTaprootChannelCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) checkWatchConfirmed(f, spliceTx) @@ -3947,7 +3947,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val htlcs = setupHtlcs(f) // Our splice upgrades the channel to taproot. - val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix())) + val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), channelType_opt = Some(ChannelTypes.SimpleTaprootChannelsPhoenix)) assert(alice.commitments.active.head.commitmentFormat == PhoenixSimpleTaprootChannelCommitmentFormat) assert(alice.commitments.active.last.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) assert(alice2blockchain.expectMsgType[WatchPublished].txId == spliceTx.txid)