Skip to content

Commit bb2016a

Browse files
t-bastsstone
andauthored
Improve taproot channels support and add tests (#3136)
* Add more parameters to nonce generation We provide both funding keys to the nonce generation algorithm, which better matches the interface provided by libsecp256k1. We also provide the `funding_txid` to the signing nonce generation to improve the extra randomness used. * Remove `ignoreRetransmittedCommitSig` This isn't necessary since we now use `next_commitment_number` to indicate whether we want a retransmission of `commit_sig` or not on reconnection. * Fix preimage extraction from remote HTLC-success txs When extracting the preimage from potential HTLC-success transactions, we remove the unnecessary check on the signature size, which could be gamed by our peer if they sign with a different sighash than none. * Define signature functions without `extraUtxos` `CommitTx` and `ClosingTx` never have extra utxos, we can thus define a dedicated function overload instead of explicitly providing `Map.empty` in many places. * Refactor `channel_reestablish` local nonces creation The previous implementation didn't handle the dual-funding RBF case. We add support for this case and clean-up the local nonces TLVs created during `channel_reestablish`. * Clean-up nonces and partial sigs TLVs and codecs We add documentation to taproot TLVs and clean-up the codec files. We clean-up the constructors for various lightning messages and better type channel spending signatures (see `commit_sig` for example). We remove the redundant `NextLocalNonce` TLV from `revoke_and_ack`. We also rename closing nonces to be more clear, as local/remote only is confusing. We add codecs tests, as the PR didn't contain any encoding test for the new TLVs. Writing those tests allowed us to discover a bug in the encoding of `txId`s, which must be encoded in reverse endianness. * Fix `channel_ready` retransmission We were missing our local nonce when retransmitting `channel_ready` on reconnection for taproot channels. * Refactor closing helpers We refactor the taproot closing helpers and fix a bug where the `closer` TLV was filled instead of using the `closee` TLV (when signing remote transactions in `signSimpleClosingTx`). The diff with the base branch may look complex, but it is actually to make the diff with `master` much smaller and easier to review, by grouping logic that applies to both taproot and non-taproot cases. We also move the `shutdown` creation helpers to a separate file, like what is done for the `channel_ready` creation helper. * nit: rename Phoenix simple taproot feature The `tweaked` suffix doesn't seem to be useful, we're already stating that this is specific to Phoenix. * More detailed remote nonce exceptions We rename the exceptions on remote nonces to more clearly apply to the commit tx, funding tx or closing tx. We add more fields to help debug. We remove the unnecessary log line on setting the remote commit nonces (the messages are logged and contain the nonces TLV, which is enough for debugging). We clear nonces on disconnection, to avoid polluting a fresh connection with invalid data from the previous connection. We fix a few `channel_reestablish` bugs, where the *next* commit nonce was used instead of the *current* commit nonce when retransmitting our `commit_sig`. * Simplify error handling The previous code was calling `handleLocalError` in `spendLocalCurrent` which created an infinite loop, since `handleLocalError` was itself calling `spendLocalCurrent` to publish the commit tx. This was clearly untested, otherwise this issue would have been noticed earlier. More tests will be added in future commits to address this issue. We can actually greatly simplify error handling during force-close by making `fullySignedCommitTx` ignore errors, which is safe since we only use this *after* receiving the remote `commit_sig`, which we validate (we force-close with the previous commitment if the remote nonce or partial sig is invalid). We improve pattern matching to fully match on the commitment format, which is important to ensure that when adding v3 taproot channels we automatically get a compiler error to give us the opportunity to set nonces correctly, otherwise we will silently miss some code paths. We also fix the `Shutdown` state, where the remote commit nonces were not provided to the `sendCommit` method, which means we couldn't settle HTLCs for taproot channels after exchanging `shutdown`. This needs unit tests as well, which will be added in future commits. * Rename phoenix taproot commitment format and channel type We're tweaking the official taproot channel type for Phoenix by using HTLC transactions that have the same feerate as the commit tx. This will only be support for Phoenix users, so we rename those cases to explicitly mention that this is for Phoenix. We also more explicitly only support upgrades of the commitment format to this specific commitment format during a splice: this isn't a BOLT mechanism so we can simply ignore malicious nodes who try to update to something different. We can change this in the future when this becomes a standard mechanism. * Refactor and simplify interactive-tx for taproot We refactor the `InteractiveTxBuilder` to: - leverage existing signing helpers (from `Transactions.SpliceTx`) - revert unnecessary changes (`receiveInput()`) - add more documentation around `tx_complete` nonce management - add early nonce validation (in `validateTx`) - clean-up error handling * Add interactive-tx taproot tests The existing code duplicated the interactive-tx test suite for taproot. This has many drawbacks: - it runs a lot of tests twice for exactly the same codepaths - it spins up another `bitcoind` instance, which is expensive - it doesn't test the taproot-related stuff in enough depth - it doesn't test taproot with `eclair-signer = true` We improve this by reverting these test changes and instead: - changing some of the tests to use taproot - duplicating a couple of tests that have different behavior in taproot and non-taproot cases - adding a test for the commitment upgrade during a splice * Add missing taproot channel tests The base branch simply duplicated some test suites for taproot without any changes: this gave a false sense of safety, as it actually simply ran twice a lot of tests that exercised the same code paths that didn't have anything related to taproot. It also made the test suite slower, since the duplicated tests were some of our slowest tests. We revert this test suite duplication and instead add tests specific to taproot channels. This showed that there were a lot of bugs in subtle edge cases that weren't discovered previously: - simple close RBF didn't work at all (nonce management was missing) - shutting down a taproot channels that had pending HTLCs didn't work at all either (nonce management wasn't handled) - reconnecting a channel during mutual close lead to a force-close - reconnection with partially signed interactive-tx session wasn't properly checking the next commit nonce for the partially signed tx We also correctly test all scenarios of revoked commitments during and after a commitment upgrade to taproot. Thankfully, this scenario didn't require any code changes, since a revoked commitment that doesn't spend the latest funding transaction must be kept in either our `active` or `inactive` commitments list, which means we have access to the format and parameters of this specific commitment and don't need to brute-force various commitment formats. * Only generate local nonce if remote nonce is provided * Verify nonces on reconnection *after* we've pruned funding transactions (#3138) If we have not received their funding locked, we may have active commitments that our peer has already pruned and will not send a nonce for, and which we need to ignore when checking nonces sent in their `channel_reestablish` message. * More refactoring of `channel_reestablish` We iterate on the refactoring from #3138 to further simplify the channel reestablish handler: we more explicitly return a single optional message from helper functions when possible, and move comments back to the handler instead of the helper functions. --------- Co-authored-by: Fabrice Drouin <[email protected]>
1 parent be3eb9e commit bb2016a

File tree

48 files changed

+3017
-1576
lines changed

Some content is hidden

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

48 files changed

+3017
-1576
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ object Features {
342342
}
343343

344344
case object SimpleTaprootChannelsPhoenix extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
345-
val rfcName = "option_simple_taproot_phoenix_tweaked"
345+
val rfcName = "option_simple_taproot_phoenix"
346346
val mandatory = 564
347347
}
348348

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2020
import fr.acinq.bitcoin.scalacompat.Satoshi
2121
import fr.acinq.eclair.BlockHeight
2222
import fr.acinq.eclair.transactions.Transactions
23-
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, LegacySimpleTaprootChannelCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
23+
import fr.acinq.eclair.transactions.Transactions._
2424

2525
// @formatter:off
2626
sealed trait ConfirmationPriority extends Ordered[ConfirmationPriority] {
@@ -76,16 +76,16 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax
7676

7777
def isProposedFeerateTooHigh(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
7878
commitmentFormat match {
79-
case Transactions.DefaultCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
80-
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | LegacySimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
79+
case Transactions.DefaultCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
80+
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | PhoenixSimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
8181
}
8282
}
8383

8484
def isProposedFeerateTooLow(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
8585
commitmentFormat match {
8686
case Transactions.DefaultCommitmentFormat => proposedFeerate < networkFeerate * ratioLow
8787
// When using anchor outputs, we allow low feerates: fees will be set with CPFP and RBF at broadcast time.
88-
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | LegacySimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => false
88+
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | PhoenixSimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => false
8989
}
9090
}
9191
}
@@ -122,7 +122,7 @@ case class OnChainFeeConf(feeTargets: FeeTargets,
122122

123123
commitmentFormat match {
124124
case Transactions.DefaultCommitmentFormat => networkFeerate
125-
case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat=>
125+
case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat =>
126126
val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate)
127127
// We make sure the feerate is always greater than the propagation threshold.
128128
targetFeerate.max(networkMinFee * 1.25)

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ case class ForbiddenDuringQuiescence (override val channelId: Byte
153153
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
154154
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelJammingException(channelId, s"too many small htlcs: $number HTLCs below $below")
155155
case class ConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"confidence too low: confidence=$confidence occupancy=$occupancy")
156-
case class MissingNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is missing")
157-
case class InvalidNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is not valid")
158-
case class MissingFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing funding nonce")
159-
case class InvalidFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "invalid funding nonce")
160-
case class MissingShutdownNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing shutdown nonce")
156+
case class MissingCommitNonce (override val channelId: ByteVector32, fundingTxId: TxId, commitmentNumber: Long) extends ChannelException(channelId, s"commit nonce for funding tx $fundingTxId and commitmentNumber=$commitmentNumber is missing")
157+
case class InvalidCommitNonce (override val channelId: ByteVector32, fundingTxId: TxId, commitmentNumber: Long) extends ChannelException(channelId, s"commit nonce for funding tx $fundingTxId and commitmentNumber=$commitmentNumber is not valid")
158+
case class MissingFundingNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"funding nonce for funding tx $fundingTxId is missing")
159+
case class InvalidFundingNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"funding nonce for funding tx $fundingTxId is not valid")
160+
case class MissingClosingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "closing nonce is missing")
161161
// @formatter:on

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package fr.acinq.eclair.channel
1818

19-
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, LegacySimpleTaprootChannelCommitmentFormat, SimpleTaprootChannelCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat}
19+
import fr.acinq.eclair.transactions.Transactions._
2020
import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature}
2121

2222
/**
@@ -122,16 +122,16 @@ object ChannelTypes {
122122
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
123123
override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
124124
}
125-
case class SimpleTaprootChannelsStagingLegacy(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
125+
case class SimpleTaprootChannelsPhoenix(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
126126
/** Known channel-type features */
127127
override def features: Set[ChannelTypeFeature] = Set(
128128
if (scidAlias) Some(Features.ScidAlias) else None,
129129
if (zeroConf) Some(Features.ZeroConf) else None,
130130
Some(Features.SimpleTaprootChannelsPhoenix),
131131
).flatten
132132
override def paysDirectlyToWallet: Boolean = false
133-
override def commitmentFormat: CommitmentFormat = LegacySimpleTaprootChannelCommitmentFormat
134-
override def toString: String = s"simple_taproot_channel_staging_legacy${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
133+
override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat
134+
override def toString: String = s"simple_taproot_channel_phoenix${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
135135
}
136136
case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
137137
/** Known channel-type features */
@@ -168,10 +168,10 @@ object ChannelTypes {
168168
AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
169169
AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
170170
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
171-
SimpleTaprootChannelsStagingLegacy(),
172-
SimpleTaprootChannelsStagingLegacy(zeroConf = true),
173-
SimpleTaprootChannelsStagingLegacy(scidAlias = true),
174-
SimpleTaprootChannelsStagingLegacy(scidAlias = true, zeroConf = true),
171+
SimpleTaprootChannelsPhoenix(),
172+
SimpleTaprootChannelsPhoenix(zeroConf = true),
173+
SimpleTaprootChannelsPhoenix(scidAlias = true),
174+
SimpleTaprootChannelsPhoenix(scidAlias = true, zeroConf = true),
175175
SimpleTaprootChannelsStaging(),
176176
SimpleTaprootChannelsStaging(zeroConf = true),
177177
SimpleTaprootChannelsStaging(scidAlias = true),
@@ -192,7 +192,7 @@ object ChannelTypes {
192192
if (canUse(Features.SimpleTaprootChannelsStaging)) {
193193
SimpleTaprootChannelsStaging(scidAlias, zeroConf)
194194
} else if (canUse(Features.SimpleTaprootChannelsPhoenix)) {
195-
SimpleTaprootChannelsStagingLegacy(scidAlias, zeroConf)
195+
SimpleTaprootChannelsPhoenix(scidAlias, zeroConf)
196196
} else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
197197
AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf)
198198
} else if (canUse(Features.AnchorOutputs)) {

0 commit comments

Comments
 (0)