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 080a2e4e27..f594e78934 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -36,17 +36,15 @@ object FeatureSupport { /** Not a sealed trait, so it can be extended by plugins. */ trait Feature { - def rfcName: String def mandatory: Int def optional: Int = mandatory + 1 - def supportBit(support: FeatureSupport): Int = support match { case Mandatory => mandatory case Optional => optional } - override def toString = rfcName + override def toString: String = rfcName } /** Feature scope as defined in Bolt 9. */ @@ -68,11 +66,9 @@ trait Bolt12Feature extends InvoiceFeature */ trait PermanentChannelFeature extends InitFeature // <- not in the spec /** - * Permanent channel feature negotiated in the channel type. Those features take precedence over permanent channel - * features negotiated in init messages. For example, if the channel type is option_static_remotekey, then even if - * the option_anchor_outputs feature is supported by both peers, it won't apply to the channel. + * Features that can be included in the [[fr.acinq.eclair.wire.protocol.ChannelTlv.ChannelTypeTlv]]. */ -trait ChannelTypeFeature extends PermanentChannelFeature +trait ChannelTypeFeature extends InitFeature // @formatter:on case class UnknownFeature(bitIndex: Int) @@ -275,6 +271,7 @@ object Features { val rfcName = "option_attribution_data" val mandatory = 36 } + case object OnionMessages extends Feature with InitFeature with NodeFeature { val rfcName = "option_onion_messages" val mandatory = 38 @@ -290,7 +287,7 @@ object Features { val mandatory = 44 } - case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { + case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature { val rfcName = "option_scid_alias" val mandatory = 46 } @@ -300,7 +297,7 @@ object Features { val mandatory = 48 } - case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { + case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature { val rfcName = "option_zeroconf" val mandatory = 50 } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala index dbd6120d10..5196498093 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala @@ -41,7 +41,7 @@ trait SpendFromChannelAddress { inputTx <- appKit.wallet.getTransaction(outPoint.txid) channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath) localFundingKey = channelKeys.fundingKey(fundingTxIndex) - inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), ByteVector.empty) + inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt)) // classify as splice, doesn't really matter tx = Transactions.SpliceTx(inputInfo, unsignedTx) localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map.empty) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala index dc62645202..24ea89d04f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala @@ -176,8 +176,8 @@ object CheckBalance { case d: DATA_SHUTDOWN => this.copy(shutdown = this.shutdown.addChannelBalance(d.commitments)) // If one of our closing transactions is in the mempool or recently confirmed, and thus included in our on-chain // balance, we ignore this channel in our off-chain balance to avoid counting it twice. - case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this - case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this + case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this + case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this // Otherwise, that means the closing transactions aren't in the mempool yet, so we include our off-chain balance. case d: DATA_NEGOTIATING => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments)) case d: DATA_NEGOTIATING_SIMPLE => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 93c9c7647e..ccead6c9d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -20,7 +20,6 @@ import akka.actor.{ActorRef, PossiblyHarmful, typed} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxId, TxOut} import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} -import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} import fr.acinq.eclair.io.Peer @@ -430,6 +429,7 @@ case class RevokedCommitPublished(commitTx: Transaction, localOutput_opt: Option case class ShortIdAliases(localAlias: Alias, remoteAlias_opt: Option[Alias]) sealed trait LocalFundingStatus { + /** While the transaction is unconfirmed, we keep the funding transaction (if available) to allow rebroadcasting. */ def signedTx_opt: Option[Transaction] /** We store local signatures for the purpose of retransmitting if the funding/splicing flow is interrupted. */ def localSigs_opt: Option[TxSignatures] @@ -458,8 +458,8 @@ object LocalFundingStatus { case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends UnconfirmedFundingTx with Locked { override val signedTx_opt: Option[Transaction] = Some(tx) } - case class ConfirmedFundingTx(tx: Transaction, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked { - override val signedTx_opt: Option[Transaction] = Some(tx) + case class ConfirmedFundingTx(txOut: TxOut, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked { + override val signedTx_opt: Option[Transaction] = None } } @@ -567,6 +567,9 @@ final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INI val channelId: ByteVector32 = initFunder.temporaryChannelId } final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, + channelType: SupportedChannelType, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, @@ -574,16 +577,24 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams, remoteFirstPerCommitmentPoint: PublicKey, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CREATED(channelParams: ChannelParams, + channelType: SupportedChannelType, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, + channelType: SupportedChannelType, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, remoteFundingPubKey: PublicKey, fundingTx: Transaction, fundingTxFee: Satoshi, @@ -593,6 +604,7 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams, lastSent: FundingCreated, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = channelParams.channelId + val commitmentFormat: CommitmentFormat = channelType.commitmentFormat } final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm @@ -610,6 +622,8 @@ final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANN } final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, secondRemotePerCommitmentPoint: PublicKey, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, @@ -620,8 +634,7 @@ final case class DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams: ChannelParams, secondRemotePerCommitmentPoint: PublicKey, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, - signingSession: InteractiveTxSigningSession.WaitingForSigs, - remoteChannelData_opt: Option[ByteVector]) extends ChannelDataWithoutCommitments + signingSession: InteractiveTxSigningSession.WaitingForSigs) extends ChannelDataWithoutCommitments final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, @@ -629,9 +642,9 @@ final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments, lastChecked: BlockHeight, // last time we checked if the channel was double-spent status: DualFundingStatus, deferred: Option[ChannelReady]) extends ChannelDataWithCommitments { - def allFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: DualFundedUnconfirmedFundingTx => fundingTx } - def latestFundingTx: DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[DualFundedUnconfirmedFundingTx] - def previousFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx) + def allFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx => fundingTx } + def latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx] + def previousFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx) } final case class DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments: Commitments, aliases: ShortIdAliases) extends ChannelDataWithCommitments @@ -639,10 +652,10 @@ final case class DATA_NORMAL(commitments: Commitments, aliases: ShortIdAliases, lastAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, + spliceStatus: SpliceStatus, localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], - closeStatus_opt: Option[CloseStatus], - spliceStatus: SpliceStatus) extends ChannelDataWithCommitments { + closeStatus_opt: Option[CloseStatus]) extends ChannelDataWithCommitments { val lastAnnouncedCommitment_opt: Option[AnnouncedCommitment] = lastAnnouncement_opt.flatMap(ann => commitments.resolveCommitment(ann.shortChannelId).map(c => AnnouncedCommitment(c, ann))) val lastAnnouncedFundingTxId_opt: Option[TxId] = lastAnnouncedCommitment_opt.map(_.fundingTxId) val isNegotiatingQuiescence: Boolean = spliceStatus.isNegotiatingQuiescence @@ -675,24 +688,21 @@ final case class DATA_CLOSING(commitments: Commitments, remoteCommitPublished: Option[RemoteCommitPublished] = None, nextRemoteCommitPublished: Option[RemoteCommitPublished] = None, futureRemoteCommitPublished: Option[RemoteCommitPublished] = None, - revokedCommitPublished: List[RevokedCommitPublished] = Nil) extends ChannelDataWithCommitments { + revokedCommitPublished: List[RevokedCommitPublished] = Nil, + maxClosingFeerate_opt: Option[FeeratePerKw] = None) extends ChannelDataWithCommitments { val spendingTxs: List[Transaction] = mutualClosePublished.map(_.tx) ::: localCommitPublished.map(_.commitTx).toList ::: remoteCommitPublished.map(_.commitTx).toList ::: nextRemoteCommitPublished.map(_.commitTx).toList ::: futureRemoteCommitPublished.map(_.commitTx).toList ::: revokedCommitPublished.map(_.commitTx) require(spendingTxs.nonEmpty, "there must be at least one tx published in this state") } final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends ChannelDataWithCommitments +/** Local params that apply for the channel's lifetime. */ case class LocalChannelParams(nodeId: PublicKey, fundingKeyPath: DeterministicWallet.KeyPath, - dustLimit: Satoshi, - maxHtlcValueInFlightMsat: UInt64, // Channel reserve applied to the remote peer, if we're not using [[Features.DualFunding]] (in // which case the reserve is set to 1%). If the channel is spliced, this initial value will be // ignored in favor of a 1% reserve of the resulting capacity. initialRequestedChannelReserve_opt: Option[Satoshi], - htlcMinimum: MilliSatoshi, - toRemoteDelay: CltvExpiryDelta, - maxAcceptedHtlcs: Int, isChannelOpener: Boolean, paysCommitTxFees: Boolean, upfrontShutdownScript_opt: Option[ByteVector], @@ -704,18 +714,12 @@ case class LocalChannelParams(nodeId: PublicKey, // The node responsible for the commit tx fees is also the node paying the mutual close fees. // The other node's balance may be empty, which wouldn't allow them to pay the closing fees. val paysClosingFees: Boolean = paysCommitTxFees - - val proposedCommitParams: ProposedCommitParams = ProposedCommitParams(dustLimit, htlcMinimum, maxHtlcValueInFlightMsat, maxAcceptedHtlcs, toRemoteDelay) } +/** Remote params that apply for the channel's lifetime. */ case class RemoteChannelParams(nodeId: PublicKey, - dustLimit: Satoshi, - maxHtlcValueInFlightMsat: UInt64, // See comment in LocalChannelParams for details. initialRequestedChannelReserve_opt: Option[Satoshi], - htlcMinimum: MilliSatoshi, - toRemoteDelay: CltvExpiryDelta, - maxAcceptedHtlcs: Int, revocationBasepoint: PublicKey, paymentBasepoint: PublicKey, delayedPaymentBasepoint: PublicKey, 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 3ab4c20b0e..dc38e86015 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 @@ -25,22 +25,10 @@ import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeatur /** * Subset of Bolt 9 features used to configure a channel and applicable over the lifetime of that channel. - * Even if one of these features is later disabled at the connection level, it will still apply to the channel until the - * channel is upgraded or closed. + * Even if one of these features is later disabled at the connection level, it will still apply to the channel. */ case class ChannelFeatures(features: Set[PermanentChannelFeature]) { - /** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */ - val paysDirectlyToWallet: Boolean = hasFeature(Features.StaticRemoteKey) && !hasFeature(Features.AnchorOutputs) && !hasFeature(Features.AnchorOutputsZeroFeeHtlcTx) - /** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */ - val commitmentFormat: CommitmentFormat = if (hasFeature(Features.AnchorOutputs)) { - UnsafeLegacyAnchorOutputsCommitmentFormat - } else if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) { - ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - } else { - DefaultCommitmentFormat - } - def hasFeature(feature: PermanentChannelFeature): Boolean = features.contains(feature) override def toString: String = features.mkString(",") @@ -51,19 +39,20 @@ object ChannelFeatures { def apply(features: PermanentChannelFeature*): ChannelFeatures = ChannelFeatures(Set.from(features)) - /** Enrich the channel type with other permanent features that will be applied to the channel. */ + /** Configure permanent channel features based on local and remote feature. */ def apply(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): ChannelFeatures = { - val additionalPermanentFeatures = Features.knownFeatures.collect { + val permanentFeatures = Features.knownFeatures.collect { // If we both support 0-conf or scid_alias, we use it even if it wasn't in the channel-type. + // Note that we cannot use scid_alias if the channel is announced. case Features.ScidAlias if Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel => Some(Features.ScidAlias) + case Features.ScidAlias => None case Features.ZeroConf if Features.canUseFeature(localFeatures, remoteFeatures, Features.ZeroConf) => Some(Features.ZeroConf) - // Other channel-type features are negotiated in the channel-type, we ignore their value from the init message. - case _: ChannelTypeFeature => None - // We add all other permanent channel features that aren't negotiated as part of the channel-type. + // We add all other permanent channel features that we both support. case f: PermanentChannelFeature if Features.canUseFeature(localFeatures, remoteFeatures, f) => Some(f) }.flatten - val allPermanentFeatures = channelType.features.toSeq ++ additionalPermanentFeatures - ChannelFeatures(allPermanentFeatures: _*) + // Some permanent features can be negotiated as part of the channel-type. + val channelTypeFeatures = channelType.features.collect { case f: PermanentChannelFeature => f } + ChannelFeatures(permanentFeatures ++ channelTypeFeatures) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 4d1a2e5839..729f9893ed 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -3,7 +3,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong, Transaction, TxId} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Satoshi, SatoshiLong, Transaction, TxId} import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw, OnChainFeeConf} import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel.Monitoring.{Metrics, Tags} @@ -25,28 +25,16 @@ case class ChannelParams(channelId: ByteVector32, channelFeatures: ChannelFeatures, localParams: LocalChannelParams, remoteParams: RemoteChannelParams, channelFlags: ChannelFlags) { - require(channelFeatures.paysDirectlyToWallet == localParams.walletStaticPaymentBasepoint.isDefined, s"localParams.walletStaticPaymentBasepoint must be defined only for commitments that pay directly to our wallet (channel features: $channelFeatures") require(channelFeatures.hasFeature(Features.DualFunding) == localParams.initialRequestedChannelReserve_opt.isEmpty, "custom local channel reserve is incompatible with dual-funded channels") require(channelFeatures.hasFeature(Features.DualFunding) == remoteParams.initialRequestedChannelReserve_opt.isEmpty, "custom remote channel reserve is incompatible with dual-funded channels") - val commitmentFormat: CommitmentFormat = channelFeatures.commitmentFormat val announceChannel: Boolean = channelFlags.announceChannel - val localNodeId: PublicKey = localParams.nodeId val remoteNodeId: PublicKey = remoteParams.nodeId - - val localCommitParams: CommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) - val remoteCommitParams: CommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toRemoteDelay) - - // We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount. - val maxHtlcValueInFlight: MilliSatoshi = Seq(localParams.maxHtlcValueInFlightMsat, remoteParams.maxHtlcValueInFlightMsat, UInt64(MilliSatoshi.MaxMoney.toLong)).min.toBigInt.toLong.msat - // If we've set the 0-conf feature bit for this peer, we will always use 0-conf with them. val zeroConf: Boolean = localParams.initFeatures.hasFeature(Features.ZeroConf) - /** - * We update local/global features at reconnection - */ + /** We update local/global features at reconnection. */ def updateFeatures(localInit: Init, remoteInit: Init): ChannelParams = copy( localParams = localParams.copy(initFeatures = localInit.features), remoteParams = remoteParams.copy(initFeatures = remoteInit.features), @@ -58,20 +46,6 @@ case class ChannelParams(channelId: ByteVector32, */ def minDepth(defaultMinDepth: Int): Option[Int] = if (zeroConf) None else Some(defaultMinDepth) - /** Channel reserve that applies to our funds. */ - def localChannelReserveForCapacity(capacity: Satoshi, isSplice: Boolean): Satoshi = if (channelFeatures.hasFeature(Features.DualFunding) || isSplice) { - (capacity / 100).max(remoteCommitParams.dustLimit) - } else { - remoteParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams - } - - /** Channel reserve that applies to our peer's funds. */ - def remoteChannelReserveForCapacity(capacity: Satoshi, isSplice: Boolean): Satoshi = if (channelFeatures.hasFeature(Features.DualFunding) || isSplice) { - (capacity / 100).max(localCommitParams.dustLimit) - } else { - localParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams - } - /** * @param localScriptPubKey local script pubkey (provided in CMD_CLOSE, as an upfront shutdown script, or set to the current final onchain script) * @return an exception if the provided script is not valid @@ -189,51 +163,51 @@ object ChannelSpendSignature { * The local commitment maps to a commitment transaction that we can sign and broadcast if necessary. * The [[htlcRemoteSigs]] are stored in the order in which HTLC outputs appear in the commitment transaction. */ -case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo, remoteSig: ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) +case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, remoteSig: ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) object LocalCommit { - def fromCommitSig(params: ChannelParams, commitKeys: LocalCommitmentKeys, fundingTxId: TxId, + def fromCommitSig(channelParams: ChannelParams, commitParams: CommitParams, commitKeys: LocalCommitmentKeys, fundingTxId: TxId, fundingKey: PrivateKey, remoteFundingPubKey: PublicKey, commitInput: InputInfo, - commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec): Either[ChannelException, LocalCommit] = { - val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommitIndex, fundingKey, remoteFundingPubKey, commitInput, spec) - val remoteCommitSigOk = params.commitmentFormat match { + commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, commitmentFormat: CommitmentFormat): Either[ChannelException, LocalCommit] = { + val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(channelParams, commitParams, commitKeys, localCommitIndex, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec) + val remoteCommitSigOk = commitmentFormat match { case _: SegwitV0CommitmentFormat => localCommitTx.checkRemoteSig(fundingKey.publicKey, remoteFundingPubKey, ChannelSpendSignature.IndividualSignature(commit.signature)) case _: SimpleTaprootChannelCommitmentFormat => ??? } if (!remoteCommitSigOk) { - return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, localCommitIndex, localCommitTx.tx)) + return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx)) } - val commitTxRemoteSig = params.commitmentFormat match { + val commitTxRemoteSig = commitmentFormat match { case _: SegwitV0CommitmentFormat => ChannelSpendSignature.IndividualSignature(commit.signature) case _: SimpleTaprootChannelCommitmentFormat => ??? } val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) if (commit.htlcSignatures.size != sortedHtlcTxs.size) { - return Left(HtlcSigCountMismatch(params.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)) + return Left(HtlcSigCountMismatch(channelParams.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)) } val htlcRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map { case (htlcTx: HtlcTx, remoteSig) => if (!htlcTx.checkRemoteSig(commitKeys, remoteSig)) { - return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid)) + return Left(InvalidHtlcSignature(channelParams.channelId, htlcTx.tx.txid)) } remoteSig } - Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, localCommitTx.input, commitTxRemoteSig, htlcRemoteSigs)) + Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, commitTxRemoteSig, htlcRemoteSigs)) } } /** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */ case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) { - def sign(params: ChannelParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo): CommitSig = { + def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat): CommitSig = { val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val commitKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, spec) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint, commitmentFormat) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, commitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) val htlcSigs = sortedHtlcTxs.map(_.localSig(commitKeys)) - params.commitmentFormat match { + commitmentFormat match { case _: SegwitV0CommitmentFormat => val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey).sig - CommitSig(params.channelId, sig, htlcSigs.toList) + CommitSig(channelParams.channelId, sig, htlcSigs.toList) case _: SimpleTaprootChannelCommitmentFormat => ??? } } @@ -268,28 +242,51 @@ case class CommitTxIds(localCommitTxId: TxId, remoteCommitTxId: TxId, nextRemote */ case class Commitment(fundingTxIndex: Long, firstRemoteCommitIndex: Long, + fundingInput: OutPoint, + fundingAmount: Satoshi, remoteFundingPubKey: PublicKey, - localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, - localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { - val commitInput: InputInfo = localCommit.input - val fundingTxId: TxId = commitInput.outPoint.txid + localFundingStatus: LocalFundingStatus, + remoteFundingStatus: RemoteFundingStatus, + commitmentFormat: CommitmentFormat, + localCommitParams: CommitParams, + localCommit: LocalCommit, + remoteCommitParams: CommitParams, + remoteCommit: RemoteCommit, + nextRemoteCommit_opt: Option[NextRemoteCommit]) { + val fundingTxId: TxId = fundingInput.txid val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId)) - val capacity: Satoshi = commitInput.txOut.amount + val capacity: Satoshi = fundingAmount + // We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount. + val maxHtlcValueInFlight: MilliSatoshi = Seq(localCommitParams.maxHtlcValueInFlight, remoteCommitParams.maxHtlcValueInFlight, UInt64(MilliSatoshi.MaxMoney.toLong)).min.toBigInt.toLong.msat /** Once the funding transaction is confirmed, short_channel_id matching this transaction. */ val shortChannelId_opt: Option[RealShortChannelId] = localFundingStatus match { case f: LocalFundingStatus.ConfirmedFundingTx => Some(f.shortChannelId) case _ => None } - def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index) + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + + def commitInput(fundingKey: PrivateKey): InputInfo = Transactions.makeFundingInputInfo(fundingInput.txid, fundingInput.index.toInt, fundingAmount, fundingKey.publicKey, remoteFundingPubKey, commitmentFormat) + + def commitInput(channelKeys: ChannelKeys): InputInfo = commitInput(localFundingKey(channelKeys)) - def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) + def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index, commitmentFormat) + + def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint, commitmentFormat) /** Channel reserve that applies to our funds. */ - def localChannelReserve(params: ChannelParams): Satoshi = params.localChannelReserveForCapacity(capacity, fundingTxIndex > 0) + def localChannelReserve(params: ChannelParams): Satoshi = if (params.channelFeatures.hasFeature(Features.DualFunding) || fundingTxIndex > 0) { + (fundingAmount / 100).max(remoteCommitParams.dustLimit) + } else { + params.remoteParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams + } /** Channel reserve that applies to our peer's funds. */ - def remoteChannelReserve(params: ChannelParams): Satoshi = params.remoteChannelReserveForCapacity(capacity, fundingTxIndex > 0) + def remoteChannelReserve(params: ChannelParams): Satoshi = if (params.channelFeatures.hasFeature(Features.DualFunding) || fundingTxIndex > 0) { + (fundingAmount / 100).max(localCommitParams.dustLimit) + } else { + params.localParams.initialRequestedChannelReserve_opt.get // this is guarded by a require() in ChannelParams + } // NB: when computing availableBalanceForSend and availableBalanceForReceive, the initiator keeps an extra buffer on // top of its usual channel reserve to avoid getting channels stuck in case the on-chain feerate increases (see @@ -457,11 +454,11 @@ case class Commitment(fundingTxIndex: Long, // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk // we need to verify that we're not disagreeing on feerates anymore before offering new HTLCs // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } // What we want to avoid is having an HTLC in a commitment transaction that has a very low feerate, which we won't // be able to confirm in time to claim the HTLC, so we only need to check that the feerate isn't too low. - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, feerate)) match { + remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) case None => } @@ -474,10 +471,10 @@ case class Commitment(fundingTxIndex: Long, val outgoingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming) // note that the initiator pays the fee, so if sender != initiator, both sides will have to afford this payment - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, reduced, commitmentFormat) // the initiator needs to keep an extra buffer to be able to handle a x2 feerate increase and an additional htlc to avoid // getting the channel stuck (see https://github.com/lightningnetwork/lightning-rfc/issues/728). - val funderFeeBuffer = commitTxTotalCostMsat(params.remoteCommitParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), params.commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, params.commitmentFormat) + val funderFeeBuffer = commitTxTotalCostMsat(remoteCommitParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, commitmentFormat) // NB: increasing the feerate can actually remove htlcs from the commit tx (if they fall below the trim threshold) // which may result in a lower commit tx fee; this is why we take the max of the two. val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.paysCommitTxFees) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat) @@ -508,39 +505,38 @@ case class Commitment(fundingTxIndex: Long, // We apply local *and* remote restrictions, to ensure both peers are happy with the resulting number of HTLCs. // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since outgoingHtlcs is a Set). val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.amountMsat).sum - val allowedHtlcValueInFlight = UInt64(params.maxHtlcValueInFlight.toLong) - if (allowedHtlcValueInFlight < htlcValueInFlight) { - return Left(HtlcValueTooHighInFlight(params.channelId, maximum = allowedHtlcValueInFlight, actual = htlcValueInFlight)) + if (maxHtlcValueInFlight < htlcValueInFlight) { + return Left(HtlcValueTooHighInFlight(params.channelId, maximum = UInt64(maxHtlcValueInFlight.toLong), actual = htlcValueInFlight)) } - if (Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min < outgoingHtlcs.size) { - return Left(TooManyAcceptedHtlcs(params.channelId, maximum = Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min)) + if (Seq(localCommitParams.maxAcceptedHtlcs, remoteCommitParams.maxAcceptedHtlcs).min < outgoingHtlcs.size) { + return Left(TooManyAcceptedHtlcs(params.channelId, maximum = Seq(localCommitParams.maxAcceptedHtlcs, remoteCommitParams.maxAcceptedHtlcs).min)) } // If sending this htlc would overflow our dust exposure, we reject it. val maxDustExposure = feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.maxExposure val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterAdd = DustExposure.computeExposure(localReduced, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterAdd = DustExposure.computeExposure(localReduced, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterAdd > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterAdd)) } val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit1.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterAdd = DustExposure.computeExposure(remoteReduced, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterAdd = DustExposure.computeExposure(remoteReduced, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterAdd > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterAdd)) } // Jamming protection // Must be the last checks so that they can be ignored for shadow deployment. - reputationScore.checkOutgoingChannelOccupancy(outgoingHtlcs.toSeq, params) + reputationScore.checkOutgoingChannelOccupancy(params.channelId, this, outgoingHtlcs.toSeq) } def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk // we need to verify that we're not disagreeing on feerates anymore before accepting new HTLCs // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, feerate)) match { + remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) case None => } @@ -550,7 +546,7 @@ case class Commitment(fundingTxIndex: Long, val incomingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming) // note that the initiator pays the fee, so if sender != initiator, both sides will have to afford this payment - val fees = commitTxTotalCost(params.localCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(localCommitParams.dustLimit, reduced, commitmentFormat) // NB: we don't enforce the funderFeeReserve (see sendAdd) because it would confuse a remote initiator that doesn't have this mitigation in place // We could enforce it once we're confident a large portion of the network implements it. val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.paysCommitTxFees) 0.sat else fees) @@ -570,12 +566,12 @@ case class Commitment(fundingTxIndex: Long, // NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since incomingHtlcs is a Set). val htlcValueInFlight = incomingHtlcs.toSeq.map(_.amountMsat).sum - if (params.localCommitParams.maxHtlcValueInFlight < htlcValueInFlight) { - return Left(HtlcValueTooHighInFlight(params.channelId, maximum = params.localCommitParams.maxHtlcValueInFlight, actual = htlcValueInFlight)) + if (localCommitParams.maxHtlcValueInFlight < htlcValueInFlight) { + return Left(HtlcValueTooHighInFlight(params.channelId, maximum = localCommitParams.maxHtlcValueInFlight, actual = htlcValueInFlight)) } - if (incomingHtlcs.size > params.localCommitParams.maxAcceptedHtlcs) { - return Left(TooManyAcceptedHtlcs(params.channelId, maximum = params.localCommitParams.maxAcceptedHtlcs)) + if (incomingHtlcs.size > localCommitParams.maxAcceptedHtlcs) { + return Left(TooManyAcceptedHtlcs(params.channelId, maximum = localCommitParams.maxAcceptedHtlcs)) } Right(()) @@ -586,7 +582,7 @@ case class Commitment(fundingTxIndex: Long, val reduced = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee // we look from remote's point of view, so if local is initiator remote doesn't pay the fees - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, reduced, commitmentFormat) val missing = reduced.toRemote.truncateToSatoshi - localChannelReserve(params) - fees if (missing < 0.sat) { return Left(CannotAffordFees(params.channelId, missing = -missing, reserve = localChannelReserve(params), fees = fees)) @@ -597,12 +593,12 @@ case class Commitment(fundingTxIndex: Long, // this is the commitment as it would be if our update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } @@ -611,10 +607,10 @@ case class Commitment(fundingTxIndex: Long, } def canReceiveFee(targetFeerate: FeeratePerKw, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.commitmentFormat, capacity) - if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooHigh(params.commitmentFormat, localFeerate, targetFeerate)) { + val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat, capacity) + if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooHigh(commitmentFormat, localFeerate, targetFeerate)) { return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) - } else if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(params.commitmentFormat, localFeerate, targetFeerate) && hasPendingOrProposedHtlcs(changes)) { + } else if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, targetFeerate) && hasPendingOrProposedHtlcs(changes)) { // If the proposed feerate is too low, but we don't have any pending HTLC, we temporarily accept it. return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) } else { @@ -625,7 +621,7 @@ case class Commitment(fundingTxIndex: Long, // (it also means that we need to check the fee of the initial commitment tx somewhere) val reduced = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) // a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee - val fees = commitTxTotalCost(params.localCommitParams.dustLimit, reduced, params.commitmentFormat) + val fees = commitTxTotalCost(localCommitParams.dustLimit, reduced, commitmentFormat) val missing = reduced.toRemote.truncateToSatoshi - remoteChannelReserve(params) - fees if (missing < 0.sat) { return Left(CannotAffordFees(params.channelId, missing = -missing, reserve = remoteChannelReserve(params), fees = fees)) @@ -634,14 +630,14 @@ case class Commitment(fundingTxIndex: Long, if (feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.closeOnUpdateFeeOverflow) { val maxDustExposure = feeConf.feerateToleranceFor(params.remoteNodeId).dustTolerance.maxExposure val localReduced = DustExposure.reduceForDustExposure(localCommit.spec, changes.localChanges.all, changes.remoteChanges.all) - val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, params.localCommitParams.dustLimit, params.commitmentFormat) + val localDustExposureAfterFeeUpdate = DustExposure.computeExposure(localReduced, targetFeerate, localCommitParams.dustLimit, commitmentFormat) if (localDustExposureAfterFeeUpdate > maxDustExposure) { return Left(LocalDustHtlcExposureTooHigh(params.channelId, maxDustExposure, localDustExposureAfterFeeUpdate)) } // this is the commitment as it would be if their update_fee was immediately signed by both parties (it is only an // estimate because there can be concurrent updates) val remoteReduced = DustExposure.reduceForDustExposure(remoteCommit.spec, changes.remoteChanges.all, changes.localChanges.all) - val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, params.remoteCommitParams.dustLimit, params.commitmentFormat) + val remoteDustExposureAfterFeeUpdate = DustExposure.computeExposure(remoteReduced, targetFeerate, remoteCommitParams.dustLimit, commitmentFormat) if (remoteDustExposureAfterFeeUpdate > maxDustExposure) { return Left(RemoteDustHtlcExposureTooHigh(params.channelId, maxDustExposure, remoteDustExposureAfterFeeUpdate)) } @@ -653,8 +649,8 @@ case class Commitment(fundingTxIndex: Long, def sendCommit(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, batchSize: Int)(implicit log: LoggingAdapter): (Commitment, CommitSig) = { // remote commitment will include all local proposed changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed) - val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput, spec) + val fundingKey = localFundingKey(channelKeys) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, remoteCommitParams, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, spec) val htlcSigs = htlcTxs.sortBy(_.input.outPoint.index).map(_.localSig(commitKeys)) // NB: IN/OUT htlcs are inverted because this is the remote commit @@ -664,7 +660,7 @@ case class Commitment(fundingTxIndex: Long, val tlvs = Set( if (batchSize > 1) Some(CommitSigTlv.BatchTlv(batchSize)) else None ).flatten[CommitSigTlv] - val commitSig = params.commitmentFormat match { + val commitSig = commitmentFormat match { case _: SegwitV0CommitmentFormat => val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey).sig CommitSig(params.channelId, sig, htlcSigs.toList, TlvStream(tlvs)) @@ -684,9 +680,9 @@ case class Commitment(fundingTxIndex: Long, // we will reply to this sig with our old revocation hash preimage (at index) and our next revocation hash (at index + 1) // and will increment our index val localCommitIndex = localCommit.index + 1 - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val spec = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) - LocalCommit.fromCommitSig(params, commitKeys, fundingTxId, fundingKey, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec).map { localCommit1 => + LocalCommit.fromCommitSig(params, localCommitParams, commitKeys, fundingTxId, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commit, localCommitIndex, spec, commitmentFormat).map { localCommit1 => log.info(s"built local commit number=$localCommitIndex toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.commitTxFeerate} txid=${localCommit1.txId} fundingTxId=$fundingTxId", spec.htlcs.collect(DirectedHtlc.incoming).map(_.id).mkString(","), spec.htlcs.collect(DirectedHtlc.outgoing).map(_.id).mkString(",")) copy(localCommit = localCommit1) } @@ -694,9 +690,9 @@ case class Commitment(fundingTxIndex: Long, /** Return a fully signed commit tx, that can be published as-is. */ def fullySignedLocalCommitTx(params: ChannelParams, channelKeys: ChannelKeys): Transaction = { - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val commitKeys = localKeys(params, channelKeys) - val (unsignedCommitTx, _) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, localCommit.input, localCommit.spec) + val (unsignedCommitTx, _) = Commitment.makeLocalTxs(params, localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, localCommit.spec) localCommit.remoteSig match { case remoteSig: ChannelSpendSignature.IndividualSignature => val localSig = unsignedCommitTx.sign(fundingKey, remoteFundingPubKey) @@ -707,14 +703,14 @@ case class Commitment(fundingTxIndex: Long, /** Return the HTLC transactions for our local commit and the corresponding remote signatures. */ def htlcTxs(params: ChannelParams, channelKeys: ChannelKeys): Seq[(UnsignedHtlcTx, ByteVector64)] = { - val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val fundingKey = localFundingKey(channelKeys) val commitKeys = localKeys(params, channelKeys) htlcTxs(params, fundingKey, commitKeys) } /** Return the HTLC transactions for our local commit and the corresponding remote signatures. */ def htlcTxs(params: ChannelParams, fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys): Seq[(UnsignedHtlcTx, ByteVector64)] = { - val (_, htlcTxs) = Commitment.makeLocalTxs(params, params.localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, localCommit.input, localCommit.spec) + val (_, htlcTxs) = Commitment.makeLocalTxs(params, localCommitParams, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(fundingKey), commitmentFormat, localCommit.spec) htlcTxs.sortBy(_.input.outPoint.index).zip(localCommit.htlcRemoteSigs) } @@ -728,10 +724,11 @@ object Commitment { localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, 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, channelParams.commitmentFormat) + 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 htlcTxs = makeHtlcTxs(commitTx.tx, outputs, channelParams.commitmentFormat) + val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat) (commitTx, htlcTxs) } @@ -742,10 +739,11 @@ object Commitment { localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, 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, channelParams.commitmentFormat) + 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 htlcTxs = makeHtlcTxs(commitTx.tx, outputs, channelParams.commitmentFormat) + val htlcTxs = makeHtlcTxs(commitTx.tx, outputs, commitmentFormat) (commitTx, htlcTxs) } } @@ -758,32 +756,31 @@ case class AnnouncedCommitment(commitment: Commitment, announcement: ChannelAnno } /** Subset of Commitments when we want to work with a single, specific commitment. */ -case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChanges, - fundingTxIndex: Long, - firstRemoteCommitIndex: Long, - remoteFundingPubKey: PublicKey, - localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, - localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { +case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChanges, commitment: Commitment) { val channelId: ByteVector32 = channelParams.channelId - val shortChannelId_opt: Option[RealShortChannelId] = localFundingStatus match { - case f: LocalFundingStatus.ConfirmedFundingTx => Some(f.shortChannelId) - case _ => None - } + val shortChannelId_opt: Option[RealShortChannelId] = commitment.shortChannelId_opt + val fundingTxIndex: Long = commitment.fundingTxIndex + val fundingInput: OutPoint = commitment.fundingInput + val fundingTxId: TxId = commitment.fundingTxId + val remoteFundingPubKey: PublicKey = commitment.remoteFundingPubKey + val localFundingStatus: LocalFundingStatus = commitment.localFundingStatus + val commitTxIds: CommitTxIds = commitment.commitTxIds val localChannelParams: LocalChannelParams = channelParams.localParams - val localCommitParams: CommitParams = channelParams.localCommitParams + val localCommitParams: CommitParams = commitment.localCommitParams + val localCommit: LocalCommit = commitment.localCommit val remoteChannelParams: RemoteChannelParams = channelParams.remoteParams - val remoteCommitParams: CommitParams = channelParams.remoteCommitParams - val commitInput: InputInfo = localCommit.input - val fundingTxId: TxId = commitInput.outPoint.txid - val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId)) - val capacity: Satoshi = commitInput.txOut.amount - val commitmentFormat: CommitmentFormat = channelParams.commitmentFormat - val commitment: Commitment = Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) + val remoteCommitParams: CommitParams = commitment.remoteCommitParams + val remoteCommit: RemoteCommit = commitment.remoteCommit + val nextRemoteCommit_opt: Option[NextRemoteCommit] = commitment.nextRemoteCommit_opt + val commitmentFormat: CommitmentFormat = commitment.commitmentFormat + val capacity: Satoshi = commitment.fundingAmount def localKeys(channelKeys: ChannelKeys): LocalCommitmentKeys = commitment.localKeys(channelParams, channelKeys) def remoteKeys(channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = commitment.remoteKeys(channelParams, channelKeys, remotePerCommitmentPoint) + def commitInput(channelKeys: ChannelKeys): InputInfo = commitment.commitInput(channelKeys) + def localChannelReserve: Satoshi = commitment.localChannelReserve(channelParams) def remoteChannelReserve: Satoshi = commitment.remoteChannelReserve(channelParams) @@ -851,13 +848,14 @@ case class Commitments(channelParams: ChannelParams, // While we have multiple active commitments, we use the most restrictive one. val capacity: Satoshi = active.map(_.capacity).min + val maxHtlcValueInFlight: MilliSatoshi = active.map(_.maxHtlcValueInFlight).min lazy val availableBalanceForSend: MilliSatoshi = active.map(_.availableBalanceForSend(channelParams, changes)).min lazy val availableBalanceForReceive: MilliSatoshi = active.map(_.availableBalanceForReceive(channelParams, changes)).min val all: Seq[Commitment] = active ++ inactive // We always use the last commitment that was created, to make sure we never go back in time. - val latest: FullCommitment = FullCommitment(channelParams, changes, active.head.fundingTxIndex, active.head.firstRemoteCommitIndex, active.head.remoteFundingPubKey, active.head.localFundingStatus, active.head.remoteFundingStatus, active.head.localCommit, active.head.remoteCommit, active.head.nextRemoteCommit_opt) + val latest: FullCommitment = FullCommitment(channelParams, changes, active.head) val lastLocalLocked_opt: Option[Commitment] = active.filter(_.localFundingStatus.isInstanceOf[LocalFundingStatus.Locked]).sortBy(_.fundingTxIndex).lastOption val lastRemoteLocked_opt: Option[Commitment] = active.filter(c => c.remoteFundingStatus == RemoteFundingStatus.Locked).sortBy(_.fundingTxIndex).lastOption @@ -897,7 +895,7 @@ case class Commitments(channelParams: ChannelParams, } // even if remote advertises support for 0 msat htlc, we limit ourselves to values strictly positive, hence the max(1 msat) - val htlcMinimum = channelParams.remoteCommitParams.htlcMinimum.max(1 msat) + val htlcMinimum = active.map(_.remoteCommitParams.htlcMinimum).max.max(1 msat) if (cmd.amount < htlcMinimum) { return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = cmd.amount)) } @@ -939,7 +937,7 @@ case class Commitments(channelParams: ChannelParams, } // we used to not enforce a strictly positive minimum, hence the max(1 msat) - val htlcMinimum = channelParams.localCommitParams.htlcMinimum.max(1 msat) + val htlcMinimum = active.map(_.localCommitParams.htlcMinimum).max.max(1 msat) if (add.amountMsat < htlcMinimum) { return Left(HtlcValueTooSmall(channelId, minimum = htlcMinimum, actual = add.amountMsat)) } @@ -1038,7 +1036,7 @@ case class Commitments(channelParams: ChannelParams, active.map(_.canSendFee(cmd.feeratePerKw, channelParams, changes1, feeConf)) .collectFirst { case Left(f) => Left(f) } .getOrElse { - Metrics.LocalFeeratePerByte.withTag(Tags.CommitmentFormat, channelParams.commitmentFormat.toString).record(FeeratePerByte(cmd.feeratePerKw).feerate.toLong) + Metrics.LocalFeeratePerByte.withTag(Tags.CommitmentFormat, active.head.commitmentFormat.toString).record(FeeratePerByte(cmd.feeratePerKw).feerate.toLong) Right(copy(changes = changes1), fee) } } @@ -1050,14 +1048,14 @@ case class Commitments(channelParams: ChannelParams, } else if (fee.feeratePerKw < FeeratePerKw.MinimumFeeratePerKw) { Left(FeerateTooSmall(channelId, remoteFeeratePerKw = fee.feeratePerKw)) } else { - val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, remoteNodeId, channelParams.commitmentFormat, active.head.capacity) + val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, remoteNodeId, active.head.commitmentFormat, active.head.capacity) log.info("remote feeratePerKw={}, local feeratePerKw={}, ratio={}", fee.feeratePerKw, localFeeratePerKw, fee.feeratePerKw.toLong.toDouble / localFeeratePerKw.toLong) // update_fee replace each other, so we can remove previous ones val changes1 = changes.copy(remoteChanges = changes.remoteChanges.copy(proposed = changes.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee)) active.map(_.canReceiveFee(fee.feeratePerKw, channelParams, changes1, feerates, feeConf)) .collectFirst { case Left(f) => Left(f) } .getOrElse { - Metrics.RemoteFeeratePerByte.withTag(Tags.CommitmentFormat, channelParams.commitmentFormat.toString).record(FeeratePerByte(fee.feeratePerKw).feerate.toLong) + Metrics.RemoteFeeratePerByte.withTag(Tags.CommitmentFormat, active.head.commitmentFormat.toString).record(FeeratePerByte(fee.feeratePerKw).feerate.toLong) Right(copy(changes = changes1)) } } @@ -1067,8 +1065,10 @@ case class Commitments(channelParams: ChannelParams, remoteNextCommitInfo match { case Right(_) if !changes.localHasChanges => Left(CannotSignWithoutChanges(channelId)) case Right(remoteNextPerCommitmentPoint) => - val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint) - val (active1, sigs) = active.map(_.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size)).unzip + val (active1, sigs) = active.map(c => { + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint, c.commitmentFormat) + c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size) + }).unzip val commitments1 = copy( changes = changes.copy( localChanges = changes.localChanges.copy(proposed = Nil, signed = changes.localChanges.proposed), @@ -1092,9 +1092,9 @@ case class Commitments(channelParams: ChannelParams, case _: CommitSig if active.size > 1 => return Left(CommitSigCountMismatch(channelId, active.size, 1)) case commitSig: CommitSig => Seq(commitSig) } - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1) // Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments. val active1 = active.zip(sigs).map { case (commitment, commit) => + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1, commitment.commitmentFormat) commitment.receiveCommit(channelParams, channelKeys, commitKeys, changes, commit) match { case Left(f) => return Left(f) case Right(commitment1) => commitment1 @@ -1159,21 +1159,21 @@ case class Commitments(channelParams: ChannelParams, case _ => true }) val localReduced = DustExposure.reduceForDustExposure(localSpecWithoutNewHtlcs, changes.localChanges.all, changes.remoteChanges.acked) - val localCommitDustExposure = DustExposure.computeExposure(localReduced, channelParams.localCommitParams.dustLimit, channelParams.commitmentFormat) + val localCommitDustExposure = active.map(c => DustExposure.computeExposure(localReduced, c.localCommitParams.dustLimit, c.commitmentFormat)).max val remoteReduced = DustExposure.reduceForDustExposure(remoteSpecWithoutNewHtlcs, changes.remoteChanges.acked, changes.localChanges.all) - val remoteCommitDustExposure = DustExposure.computeExposure(remoteReduced, channelParams.remoteCommitParams.dustLimit, channelParams.commitmentFormat) + val remoteCommitDustExposure = active.map(c => DustExposure.computeExposure(remoteReduced, c.remoteCommitParams.dustLimit, c.commitmentFormat)).max // we sort incoming htlcs by decreasing amount: we want to prioritize higher amounts. val sortedReceivedHtlcs = receivedHtlcs.sortBy(_.amountMsat).reverse DustExposure.filterBeforeForward( maxDustExposure, localReduced, - channelParams.localCommitParams.dustLimit, + active.map(_.localCommitParams.dustLimit).max, localCommitDustExposure, remoteReduced, - channelParams.remoteCommitParams.dustLimit, + active.map(_.remoteCommitParams.dustLimit).max, remoteCommitDustExposure, sortedReceivedHtlcs, - channelParams.commitmentFormat) + active.head.commitmentFormat) } val actions = acceptedHtlcs.map(add => PostRevocationAction.RelayHtlc(add)) ++ rejectedHtlcs.map(add => PostRevocationAction.RejectHtlc(add)) ++ @@ -1212,10 +1212,14 @@ case class Commitments(channelParams: ChannelParams, def validateSeed(channelKeys: ChannelKeys): Boolean = { active.forall { commitment => - val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex).publicKey - val remoteFundingKey = commitment.remoteFundingPubKey - val redeemInfo = Helpers.Funding.makeFundingScript(localFundingKey, remoteFundingKey, channelParams.commitmentFormat) - commitment.commitInput.txOut.publicKeyScript == redeemInfo.pubkeyScript + commitment.localFundingStatus match { + // We ignore unconfirmed transactions for simplicity. + case _: LocalFundingStatus.UnconfirmedFundingTx => true + case tx: LocalFundingStatus.ConfirmedFundingTx => + val localFundingKey = commitment.localFundingKey(channelKeys).publicKey + val redeemInfo = Transactions.makeFundingScript(localFundingKey, commitment.remoteFundingPubKey, commitment.commitmentFormat) + tx.txOut.publicKeyScript == redeemInfo.pubkeyScript + } } } @@ -1353,7 +1357,7 @@ case class Commitments(channelParams: ChannelParams, * @param spendingTx A transaction that may spend a current or former funding tx */ def resolveCommitment(spendingTx: Transaction): Option[Commitment] = { - all.find(c => spendingTx.txIn.map(_.outPoint).contains(c.commitInput.outPoint)) + all.find(c => spendingTx.txIn.map(_.outPoint).contains(c.fundingInput)) } /** Find the corresponding commitment based on its short_channel_id (once funding transaction is confirmed). */ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 2ee5f15280..23e6ba48a6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -30,7 +30,6 @@ import fr.acinq.eclair.db.ChannelsDb import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.DirectedHtlc._ -import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ @@ -134,8 +133,8 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. - val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) + val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat, open.fundingSatoshis) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) // we don't check that the funder's amount for the initial commitment transaction is sufficient for full fee payment // now, but it will be done later when we receive `funding_created` @@ -181,8 +180,8 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. - val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) + val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat, open.fundingAmount) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) for { script_opt <- extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt) @@ -273,7 +272,7 @@ object Helpers { for { script_opt <- extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt) - fundingScript = Funding.makeFundingScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat).pubkeyScript + fundingScript = Transactions.makeFundingScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat).pubkeyScript liquidityPurchase_opt <- LiquidityAds.validateRemoteFunding(open.requestFunding_opt, remoteNodeId, accept.temporaryChannelId, fundingScript, accept.fundingAmount, open.fundingFeerate, isChannelCreation = true, accept.willFund_opt) } yield { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) @@ -368,17 +367,17 @@ object Helpers { def maxHtlcAmount(nodeParams: NodeParams, commitments: Commitments): MilliSatoshi = { if (!commitments.announceChannel) { // The channel is private, let's not change the channel update needlessly. - return commitments.channelParams.maxHtlcValueInFlight + return commitments.maxHtlcValueInFlight } for (balanceThreshold <- nodeParams.channelConf.balanceThresholds) { if (commitments.availableBalanceForSend <= balanceThreshold.available) { // Our maximum HTLC amount must always be greater than htlc_minimum_msat. - val allowedHtlcAmount = Seq(balanceThreshold.maxHtlcAmount.toMilliSatoshi, commitments.channelParams.localCommitParams.htlcMinimum, commitments.channelParams.remoteCommitParams.htlcMinimum).max + val allowedHtlcAmount = Seq(balanceThreshold.maxHtlcAmount.toMilliSatoshi, commitments.latest.localCommitParams.htlcMinimum, commitments.latest.remoteCommitParams.htlcMinimum).max // But it cannot exceed the channel's max_htlc_value_in_flight_msat. - return allowedHtlcAmount.min(commitments.channelParams.maxHtlcValueInFlight) + return allowedHtlcAmount.min(commitments.maxHtlcValueInFlight) } } - commitments.channelParams.maxHtlcValueInFlight + commitments.maxHtlcValueInFlight } def getRelayFees(nodeParams: NodeParams, remoteNodeId: PublicKey, announceChannel: Boolean): RelayFees = { @@ -388,37 +387,26 @@ object Helpers { object Funding { - def makeFundingScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): RedeemInfo = { - commitmentFormat match { - case _: SegwitV0CommitmentFormat => RedeemInfo.P2wsh(Script.write(multiSig2of2(localFundingKey, remoteFundingKey))) - case _: SimpleTaprootChannelCommitmentFormat => RedeemInfo.TaprootKeyPath(Taproot.musig2Aggregate(localFundingKey, remoteFundingKey), None) - } - } - - def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = { - val redeemInfo = makeFundingScript(fundingPubkey1, fundingPubkey2, commitmentFormat) - val fundingTxOut = TxOut(fundingSatoshis, redeemInfo.pubkeyScript) - InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, ByteVector.empty) - } - /** * Creates both sides' first commitment transaction. * * @return (localSpec, localTx, remoteSpec, remoteTx) */ - def makeFirstCommitTxs(params: ChannelParams, + def makeFirstCommitTxs(channelParams: ChannelParams, + localCommitParams: CommitParams, remoteCommitParams: CommitParams, localFundingAmount: Satoshi, remoteFundingAmount: Satoshi, localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, - commitTxFeerate: FeeratePerKw, + commitTxFeerate: FeeratePerKw, commitmentFormat: CommitmentFormat, fundingTxId: TxId, fundingTxOutputIndex: Int, localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, localCommitKeys: LocalCommitmentKeys, remoteCommitKeys: RemoteCommitmentKeys): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = { - makeCommitTxs(params, + makeCommitTxs(channelParams, localCommitParams, remoteCommitParams, fundingAmount = localFundingAmount + remoteFundingAmount, toLocal = localFundingAmount.toMilliSatoshi - localPushAmount + remotePushAmount, toRemote = remoteFundingAmount.toMilliSatoshi + localPushAmount - remotePushAmount, localHtlcs = Set.empty, commitTxFeerate, + commitmentFormat, fundingTxIndex = 0, fundingTxId, fundingTxOutputIndex, localFundingKey, remoteFundingPubKey, @@ -432,11 +420,14 @@ object Helpers { * This creates commitment transactions for both sides at an arbitrary `commitmentIndex` and with (optional) `htlc` * outputs. This function should only be used when commitments are synchronized (local and remote htlcs match). */ - def makeCommitTxs(params: ChannelParams, + def makeCommitTxs(channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, fundingAmount: Satoshi, toLocal: MilliSatoshi, toRemote: MilliSatoshi, localHtlcs: Set[DirectedHtlc], commitTxFeerate: FeeratePerKw, + commitmentFormat: CommitmentFormat, fundingTxIndex: Long, fundingTxId: TxId, fundingTxOutputIndex: Int, localFundingKey: PrivateKey, remoteFundingPubKey: PublicKey, @@ -445,21 +436,21 @@ object Helpers { val localSpec = CommitmentSpec(localHtlcs, commitTxFeerate, toLocal = toLocal, toRemote = toRemote) val remoteSpec = CommitmentSpec(localHtlcs.map(_.opposite), commitTxFeerate, toLocal = toRemote, toRemote = toLocal) - if (!params.localParams.paysCommitTxFees) { + if (!channelParams.localParams.paysCommitTxFees) { // They are responsible for paying the commitment transaction fee: we need to make sure they can afford it! // Note that the reserve may not always be met: we could be using dual funding with a large funding amount on // our side and a small funding amount on their side. But we shouldn't care as long as they can pay the fees for // the commitment transaction. - val fees = commitTxTotalCost(params.remoteCommitParams.dustLimit, remoteSpec, params.commitmentFormat) + val fees = commitTxTotalCost(remoteCommitParams.dustLimit, remoteSpec, commitmentFormat) val missing = fees - toRemote.truncateToSatoshi if (missing > 0.sat) { - return Left(CannotAffordFirstCommitFees(params.channelId, missing = missing, fees = fees)) + return Left(CannotAffordFirstCommitFees(channelParams.channelId, missing = missing, fees = fees)) } } - val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, localFundingKey.publicKey, remoteFundingPubKey, params.commitmentFormat) - val (localCommitTx, _) = Commitment.makeLocalTxs(params, params.localCommitParams, localCommitKeys, localCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, localSpec) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, params.remoteCommitParams, remoteCommitKeys, remoteCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, remoteSpec) + val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, localFundingKey.publicKey, remoteFundingPubKey, commitmentFormat) + val (localCommitTx, _) = Commitment.makeLocalTxs(channelParams, localCommitParams, localCommitKeys, localCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, commitmentFormat, localSpec) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, remoteCommitParams, remoteCommitKeys, remoteCommitmentIndex, localFundingKey, remoteFundingPubKey, commitmentInput, commitmentFormat, remoteSpec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) Right(localSpec, localCommitTx, remoteSpec, remoteCommitTx, sortedHtlcTxs) } @@ -679,9 +670,9 @@ object Helpers { } } - def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: ClosingFeerates)(implicit log: LoggingAdapter): ClosingFees = { + def firstClosingFee(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: ClosingFeerates)(implicit log: LoggingAdapter): ClosingFees = { // this is just to estimate the weight, it depends on size of the pubkey scripts - val dummyClosingTx = ClosingTx.createUnsignedTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, 0 sat, 0 sat, commitment.localCommit.spec) + val dummyClosingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, 0 sat, 0 sat, commitment.localCommit.spec) val dummyPubkey = commitment.remoteFundingPubKey val dummySig = ChannelSpendSignature.IndividualSignature(Transactions.PlaceHolderSig) val closingWeight = dummyClosingTx.aggregateSigs(dummyPubkey, dummyPubkey, dummySig, dummySig).weight() @@ -689,7 +680,7 @@ object Helpers { feerates.computeFees(closingWeight) } - def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = { + def firstClosingFee(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = { val requestedFeerate = onChainFeeConf.getClosingFeerate(feerates) val preferredFeerate = commitment.commitmentFormat match { case DefaultCommitmentFormat => @@ -700,15 +691,15 @@ object Helpers { // NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can // always use CPFP to speed up confirmation if necessary. val closingFeerates = ClosingFeerates(preferredFeerate, preferredFeerate.min(ConfirmationPriority.Slow.getFeerate(feerates)), preferredFeerate * 2) - firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) + firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 def makeFirstClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, closingFeerates_opt: Option[ClosingFeerates])(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { val closingFees = closingFeerates_opt match { - case Some(closingFeerates) => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) - case None => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, feerates, onChainFeeConf) + case Some(closingFeerates) => firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) + case None => firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, feerates, onChainFeeConf) } makeClosingTx(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFees) } @@ -716,7 +707,7 @@ object Helpers { def makeClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingFees: ClosingFees)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { log.debug("making closing tx with closing fee={} and commitments:\n{}", closingFees.preferred, commitment.specs2String) val dustLimit = commitment.localCommitParams.dustLimit.max(commitment.remoteCommitParams.dustLimit) - val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) + val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localChannelParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) val localClosingSig = closingTx.sign(channelKeys.fundingKey(commitment.fundingTxIndex), commitment.remoteFundingPubKey).sig val closingSigned = ClosingSigned(commitment.channelId, closingFees.preferred, localClosingSig, TlvStream(ClosingSignedTlv.FeeRange(closingFees.min, closingFees.max))) log.debug(s"signed closing txid=${closingTx.tx.txid} with closing fee=${closingSigned.feeSatoshis}") @@ -742,8 +733,9 @@ object Helpers { /** We are the closer: we sign closing transactions for which we pay the fees. */ def makeSimpleClosingTx(currentBlockHeight: BlockHeight, channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerate: FeeratePerKw): Either[ChannelException, (ClosingTxs, ClosingComplete)] = { // We must convert the feerate to a fee: we must build dummy transactions to compute their weight. + val commitInput = commitment.commitInput(channelKeys) val closingFee = { - val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) dummyClosingTxs.preferred_opt match { case Some(dummyTx) => val dummyPubkey = commitment.remoteFundingPubKey @@ -754,7 +746,7 @@ object Helpers { } } // Now that we know the fee we're ready to pay, we can create our closing transactions. - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitInput, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) closingTxs.preferred_opt match { case Some(closingTx) if closingTx.fee > 0.sat => () case _ => return Left(CannotGenerateClosingTx(commitment.channelId)) @@ -776,7 +768,7 @@ object Helpers { */ def signSimpleClosingTx(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingComplete: ClosingComplete): Either[ChannelException, (ClosingTx, ClosingSig)] = { val closingFee = SimpleClosingTxFee.PaidByThem(closingComplete.fees) - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput(channelKeys), commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) // If our output isn't dust, they must provide a signature for a transaction that includes it. // Note that we're the closee, so we look for signatures including the closee output. (closingTxs.localAndRemote_opt, closingTxs.localOnly_opt) match { @@ -1087,7 +1079,7 @@ object Helpers { val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) val outputs = makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) - val mainTx_opt = claimMainOutput(commitment.channelParams, commitKeys, commitTx, feerates, onChainFeeConf, finalScriptPubKey) + val mainTx_opt = claimMainOutput(commitKeys, commitTx, commitment.localCommitParams.dustLimit, commitment.commitmentFormat, feerates, onChainFeeConf, finalScriptPubKey) val (incomingHtlcs, htlcSuccessTxs) = claimIncomingHtlcOutputs(commitKeys, commitTx, outputs, commitment, remoteCommit, finalScriptPubKey) val (outgoingHtlcs, htlcTimeoutTxs) = claimOutgoingHtlcOutputs(commitKeys, commitTx, outputs, commitment, remoteCommit, finalScriptPubKey) val anchorOutput_opt = ClaimRemoteAnchorTx.findInput(commitTx, fundingKey, commitKeys, commitment.commitmentFormat).toOption @@ -1116,14 +1108,14 @@ object Helpers { } /** Claim our main output from the remote commitment transaction, if available. */ - def claimMainOutput(params: ChannelParams, commitKeys: RemoteCommitmentKeys, commitTx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = { + def claimMainOutput(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, dustLimit: Satoshi, commitmentFormat: CommitmentFormat, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = { val feerate = onChainFeeConf.getClosingFeerate(feerates) - params.commitmentFormat match { + commitmentFormat match { case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, params.localCommitParams.dustLimit, finalScriptPubKey, feerate, params.commitmentFormat) + ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, params.localCommitParams.dustLimit, finalScriptPubKey, feerate, params.commitmentFormat) + ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) } } } @@ -1278,11 +1270,10 @@ object Helpers { * When a revoked commitment transaction spending the funding tx is detected, we build a set of transactions that * will punish our peer by stealing all their funds. */ - def claimCommitTxOutputs(params: ChannelParams, channelKeys: ChannelKeys, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, db: ChannelsDb, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, SecondStageTransactions) = { - import params._ + def claimCommitTxOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, db: ChannelsDb, dustLimit: Satoshi, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, SecondStageTransactions) = { log.warning("a revoked commit has been published with commitmentNumber={}", commitmentNumber) - val commitKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val feerateMain = onChainFeeConf.getClosingFeerate(feerates) @@ -1292,23 +1283,23 @@ object Helpers { // First we will claim our main output right away. val mainTx_opt = commitmentFormat match { case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, localCommitParams.dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) + ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, localCommitParams.dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) + ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) } } // Then we punish them by stealing their main output. val mainPenaltyTx_opt = withTxGenerationLog("main-penalty") { - MainPenaltyTx.createUnsignedTx(commitKeys, revocationKey, commitTx, localCommitParams.dustLimit, finalScriptPubKey, remoteCommitParams.toSelfDelay, feeratePenalty, commitmentFormat) + MainPenaltyTx.createUnsignedTx(commitKeys, revocationKey, commitTx, dustLimit, finalScriptPubKey, toSelfDelay, feeratePenalty, commitmentFormat) } // We retrieve the historical information needed to rebuild htlc scripts. - val htlcInfos = db.listHtlcInfos(channelId, commitmentNumber) + val htlcInfos = db.listHtlcInfos(channelParams.channelId, commitmentNumber) log.info("got {} htlcs for commitmentNumber={}", htlcInfos.size, commitmentNumber) // And finally we steal the htlc outputs. - val htlcPenaltyTxs = HtlcPenaltyTx.createUnsignedTxs(commitKeys, revocationKey, commitTx, htlcInfos, localCommitParams.dustLimit, finalScriptPubKey, feeratePenalty, commitmentFormat) + val htlcPenaltyTxs = HtlcPenaltyTx.createUnsignedTxs(commitKeys, revocationKey, commitTx, htlcInfos, dustLimit, finalScriptPubKey, feeratePenalty, commitmentFormat) .flatMap(htlcPenaltyTx => withTxGenerationLog("htlc-penalty")(htlcPenaltyTx)) val rvk = RevokedCommitPublished( @@ -1336,18 +1327,18 @@ object Helpers { * NB: when anchor outputs is used, htlc transactions can be aggregated in a single transaction if they share the same * lockTime (thanks to the use of sighash_single | sighash_anyonecanpay), so we may need to claim multiple outputs. */ - def claimHtlcTxOutputs(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecrets: ShaChain, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, ThirdStageTransactions) = { + def claimHtlcTxOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecrets: ShaChain, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, ThirdStageTransactions) = { // We published HTLC-penalty transactions for every HTLC output: this transaction may be ours, or it may be one // of their HTLC transactions that confirmed before our HTLC-penalty transaction. If it is spending an HTLC // output, we assume that it's an HTLC transaction published by our peer and try to create penalty transactions // that spend it, which will automatically be skipped if this was instead one of our HTLC-penalty transactions. val spendsHtlcOutput = htlcTx.txIn.exists(txIn => revokedCommitPublished.htlcOutputs.contains(txIn.outPoint)) if (spendsHtlcOutput) { - getRemotePerCommitmentSecret(params, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { + getRemotePerCommitmentSecret(channelParams, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { case (_, remotePerCommitmentSecret) => - val commitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) - val penaltyTxs = claimHtlcTxOutputs(params, commitmentKeys, revocationKey, htlcTx, feerates, finalScriptPubKey) + val penaltyTxs = claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey) val revokedCommitPublished1 = revokedCommitPublished.copy(htlcDelayedOutputs = revokedCommitPublished.htlcDelayedOutputs ++ penaltyTxs.map(_.input.outPoint)) val txs = ThirdStageTransactions(penaltyTxs) (revokedCommitPublished1, txs) @@ -1357,12 +1348,10 @@ object Helpers { } } - private def claimHtlcTxOutputs(params: ChannelParams, commitmentKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, htlcTx: Transaction, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Seq[ClaimHtlcDelayedOutputPenaltyTx] = { + private def claimHtlcTxOutputs(commitmentKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, htlcTx: Transaction, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Seq[ClaimHtlcDelayedOutputPenaltyTx] = { // We need to use a high fee when spending HTLC txs because after a delay they can also be spent by the counterparty. val feeratePenalty = feerates.fastest - val dustLimit = params.localCommitParams.dustLimit - val toSelfDelay = params.remoteCommitParams.toSelfDelay - ClaimHtlcDelayedOutputPenaltyTx.createUnsignedTxs(commitmentKeys, revocationKey, htlcTx, dustLimit, toSelfDelay, finalScriptPubKey, feeratePenalty, params.commitmentFormat).flatMap(penaltyTx => { + ClaimHtlcDelayedOutputPenaltyTx.createUnsignedTxs(commitmentKeys, revocationKey, htlcTx, dustLimit, toSelfDelay, finalScriptPubKey, feeratePenalty, commitmentFormat).flatMap(penaltyTx => { withTxGenerationLog("htlc-delayed-penalty")(penaltyTx) }) } @@ -1370,11 +1359,11 @@ object Helpers { /** * Claim the outputs of all 2nd-stage HTLC transactions that have been confirmed. */ - def claimHtlcTxsOutputs(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecret: PrivateKey, revokedCommitPublished: RevokedCommitPublished, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { - val commitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) + def claimHtlcTxsOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, revokedCommitPublished: RevokedCommitPublished, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val confirmedHtlcTxs = revokedCommitPublished.htlcOutputs.flatMap(htlcOutput => revokedCommitPublished.irrevocablySpent.get(htlcOutput)) - val penaltyTxs = confirmedHtlcTxs.flatMap(htlcTx => claimHtlcTxOutputs(params, commitmentKeys, revocationKey, htlcTx, feerates, finalScriptPubKey)) + val penaltyTxs = confirmedHtlcTxs.flatMap(htlcTx => claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey)) ThirdStageTransactions(penaltyTxs.toSeq) } 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 86ab8a8cdc..411ece8bf7 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 @@ -396,9 +396,13 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(c: Closing.RevokedClose) => Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.channelParams, channelKeys, closing.commitments.remotePerCommitmentSecrets, c.revokedCommitPublished.commitTx).foreach { case (commitmentNumber, remotePerCommitmentSecret) => - val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, c.revokedCommitPublished.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = closing.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = closing.commitments.latest.commitmentFormat + val dustLimit = closing.commitments.latest.localCommitParams.dustLimit + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, c.revokedCommitPublished.commitTx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) doPublish(c.revokedCommitPublished, secondStageTransactions) - val thirdStageTransactions = Closing.RevokedClose.claimHtlcTxsOutputs(closing.commitments.channelParams, channelKeys, remotePerCommitmentSecret, c.revokedCommitPublished, nodeParams.currentBitcoinCoreFeerates, closing.finalScriptPubKey) + val thirdStageTransactions = Closing.RevokedClose.claimHtlcTxsOutputs(closing.commitments.channelParams, channelKeys, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, c.revokedCommitPublished, dustLimit, nodeParams.currentBitcoinCoreFeerates, closing.finalScriptPubKey) doPublish(c.revokedCommitPublished, thirdStageTransactions) } case None => @@ -423,7 +427,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall closing.revokedCommitPublished.foreach(rvk => { Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.channelParams, channelKeys, closing.commitments.remotePerCommitmentSecrets, rvk.commitTx).foreach { case (commitmentNumber, remotePerCommitmentSecret) => - val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, rvk.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = closing.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = closing.commitments.latest.commitmentFormat + val dustLimit = closing.commitments.latest.localCommitParams.dustLimit + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.channelParams, channelKeys, rvk.commitTx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) doPublish(rvk, secondStageTransactions) } }) @@ -620,11 +628,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String) val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit val nextCommitNumber = nextRemoteCommit.index - // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our - // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, commitments1.channelParams.commitmentFormat) ++ - Transactions.trimReceivedHtlcs(commitments1.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, commitments1.channelParams.commitmentFormat) - trimmedHtlcs.map(_.add).foreach { htlc => + // We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our + // counterparty, so only htlcs above remote's dust_limit matter. + val trimmedOfferedHtlcs = d.commitments.active.flatMap(c => Transactions.trimOfferedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + val trimmedReceivedHtlcs = d.commitments.active.flatMap(c => Transactions.trimReceivedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + (trimmedOfferedHtlcs ++ trimmedReceivedHtlcs).foreach { htlc => log.debug(s"adding paymentHash=${htlc.paymentHash} cltvExpiry=${htlc.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.db.channels.addHtlcInfo(d.channelId, nextCommitNumber, htlc.paymentHash, htlc.cltvExpiry) } @@ -1082,8 +1090,9 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceWithUnconfirmedTx(d.channelId, d.commitments.latest.fundingTxId).getMessage) } else { val parentCommitment = d.commitments.latest.commitment + val commitmentFormat = parentCommitment.commitmentFormat val localFundingPubKey = channelKeys.fundingKey(parentCommitment.fundingTxIndex + 1).publicKey - val fundingScript = Funding.makeFundingScript(localFundingPubKey, msg.fundingPubKey, d.commitments.channelParams.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(localFundingPubKey, msg.fundingPubKey, commitmentFormat).pubkeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = false, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, msg.useFeeCredit_opt) match { case Left(t) => log.warning("rejecting splice request with invalid liquidity ads: {}", t.getMessage) @@ -1103,11 +1112,12 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = spliceAck.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = Nil, + commitmentFormat = commitmentFormat, lockTime = msg.lockTime, - dustLimit = d.commitments.channelParams.localCommitParams.dustLimit.max(d.commitments.channelParams.remoteCommitParams.dustLimit), + dustLimit = parentCommitment.localCommitParams.dustLimit.max(parentCommitment.remoteCommitParams.dustLimit), targetFeerate = msg.feerate, requireConfirmedInputs = RequireConfirmedInputs(forLocal = msg.requireConfirmedInputs, forRemote = spliceAck.requireConfirmedInputs) ) @@ -1116,6 +1126,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = parentCommitment.localCommitParams, + remoteCommitParams = parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment, d.commitments.changes), localPushAmount = spliceAck.pushAmount, remotePushAmount = msg.pushAmount, @@ -1142,20 +1154,22 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case SpliceStatus.SpliceRequested(cmd, spliceInit) => log.info("our peer accepted our splice request and will contribute {} to the funding transaction", msg.fundingContribution) val parentCommitment = d.commitments.latest.commitment + val commitmentFormat = parentCommitment.commitmentFormat val fundingParams = InteractiveTxParams( channelId = d.channelId, isInitiator = true, localContribution = spliceInit.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = cmd.spliceOutputs, + commitmentFormat = commitmentFormat, lockTime = spliceInit.lockTime, - dustLimit = d.commitments.channelParams.localCommitParams.dustLimit.max(d.commitments.channelParams.remoteCommitParams.dustLimit), + dustLimit = parentCommitment.localCommitParams.dustLimit.max(parentCommitment.remoteCommitParams.dustLimit), targetFeerate = spliceInit.feerate, requireConfirmedInputs = RequireConfirmedInputs(forLocal = msg.requireConfirmedInputs, forRemote = spliceInit.requireConfirmedInputs) ) - val fundingScript = Funding.makeFundingScript(spliceInit.fundingPubKey, msg.fundingPubKey, d.commitments.channelParams.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(spliceInit.fundingPubKey, msg.fundingPubKey, commitmentFormat).pubkeyScript LiquidityAds.validateRemoteFunding(spliceInit.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, spliceInit.feerate, isChannelCreation = false, msg.willFund_opt) match { case Left(t) => log.info("rejecting splice attempt: invalid liquidity ads response ({})", t.getMessage) @@ -1167,6 +1181,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = parentCommitment.localCommitParams, + remoteCommitParams = parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment, d.commitments.changes), localPushAmount = cmd.pushAmount, remotePushAmount = msg.pushAmount, @@ -1207,7 +1223,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.info("rejecting rbf attempt: last attempt was less than {} blocks ago", nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks) stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidRbfAttemptTooSoon(d.channelId, rbf.latestFundingTx.createdAt, rbf.latestFundingTx.createdAt + nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks).getMessage) case Right(rbf) => - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = false, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, feeCreditUsed_opt = None) match { case Left(t) => log.warning("rejecting rbf request with invalid liquidity ads: {}", t.getMessage) @@ -1223,9 +1239,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = false, localContribution = fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(rbf.parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, + commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, lockTime = msg.lockTime, dustLimit = rbf.latestFundingTx.fundingParams.dustLimit, targetFeerate = msg.feerate, @@ -1236,6 +1253,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = rbf.parentCommitment.localCommitParams, + remoteCommitParams = rbf.parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = rbf, localPushAmount = 0 msat, remotePushAmount = 0 msat, @@ -1264,7 +1283,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case SpliceStatus.RbfRequested(cmd, txInitRbf) => getSpliceRbfContext(Some(cmd), d) match { case Right(rbf) => - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRemoteFunding(cmd.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, txInitRbf.feerate, isChannelCreation = false, msg.willFund_opt) match { case Left(t) => log.info("rejecting rbf attempt: invalid liquidity ads response ({})", t.getMessage) @@ -1277,9 +1296,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = true, localContribution = txInitRbf.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(rbf.parentCommitment)), + sharedInput_opt = Some(SharedFundingInput(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, + commitmentFormat = rbf.latestFundingTx.fundingParams.commitmentFormat, lockTime = txInitRbf.lockTime, dustLimit = rbf.latestFundingTx.fundingParams.dustLimit, targetFeerate = txInitRbf.feerate, @@ -1290,6 +1310,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall sessionId, nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = rbf.parentCommitment.localCommitParams, + remoteCommitParams = rbf.parentCommitment.remoteCommitParams, channelKeys = channelKeys, purpose = rbf, localPushAmount = 0 msat, remotePushAmount = 0 msat, @@ -1360,7 +1382,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall cmd_opt.foreach(cmd => cmd.replyTo ! RES_SPLICE(fundingTxIndex = signingSession.fundingTxIndex, signingSession.fundingTx.txId, signingSession.fundingParams.fundingAmount, signingSession.localCommit.fold(_.spec, _.spec).toLocal)) remoteCommitSig_opt.foreach(self ! _) liquidityPurchase_opt.collect { - case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, d.commitments.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, signingSession.remoteCommitParams.htlcMinimum, purchase) } val d1 = d.copy(spliceStatus = SpliceStatus.SpliceWaitingForSigs(signingSession)) stay() using d1 storing() sending commitSig @@ -1613,11 +1635,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String) val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit val nextCommitNumber = nextRemoteCommit.index - // we persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our - // counterparty, so only htlcs above remote's dust_limit matter - val trimmedHtlcs = Transactions.trimOfferedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, d.commitments.channelParams.commitmentFormat) ++ - Transactions.trimReceivedHtlcs(d.commitments.channelParams.remoteCommitParams.dustLimit, nextRemoteCommit.spec, d.commitments.channelParams.commitmentFormat) - trimmedHtlcs.map(_.add).foreach { htlc => + // We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our + // counterparty, so only htlcs above remote's dust_limit matter. + val trimmedOfferedHtlcs = d.commitments.active.flatMap(c => Transactions.trimOfferedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + val trimmedReceivedHtlcs = d.commitments.active.flatMap(c => Transactions.trimReceivedHtlcs(c.remoteCommitParams.dustLimit, nextRemoteCommit.spec, c.commitmentFormat)).map(_.add).toSet + (trimmedOfferedHtlcs ++ trimmedReceivedHtlcs).foreach { htlc => log.debug(s"adding paymentHash=${htlc.paymentHash} cltvExpiry=${htlc.cltvExpiry} to htlcs db for commitNumber=$nextCommitNumber") nodeParams.db.channels.addHtlcInfo(d.channelId, nextCommitNumber, htlc.paymentHash, htlc.cltvExpiry) } @@ -1774,7 +1796,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.localChannelParams.paysClosingFees => // if we are not paying the closing fees and they proposed a fee range, we pick a value in that range and they should accept it without further negotiation // we don't care much about the closing fee since they're paying it (not us) and we can use CPFP if we want to speed up confirmation - val localClosingFees = MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) + val localClosingFees = MutualClose.firstClosingFee(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) if (maxFee < localClosingFees.min) { log.warning("their highest closing fee is below our minimum fee: {} < {}", maxFee, localClosingFees.min) stay() sending Warning(d.channelId, s"closing fee range must not be below ${localClosingFees.min}") @@ -1801,7 +1823,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis) val (closingTx, closingSigned) = { // if we are not the channel initiator and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee - val localClosingFees = MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) + val localClosingFees = MutualClose.firstClosingFee(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeeratesForFundingClosing, nodeParams.onChainFeeConf) val nextPreferredFee = MutualClose.nextClosingFee(lastLocalClosingFee_opt.getOrElse(localClosingFees.preferred), remoteClosingFee) MutualClose.makeClosingTx(channelKeys, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, localClosingFees.copy(preferred = nextPreferredFee)) } @@ -2148,14 +2170,18 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall revokedCommitPublished = d.revokedCommitPublished.map(rvk => { // If the tx is one of our peer's HTLC txs, they were able to claim the output before us. // In that case, we immediately publish a penalty transaction spending their HTLC tx to steal their funds. - val (rvk1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, rvk, tx, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = d.commitments.latest.remoteCommitParams.toSelfDelay + val commitmentFormat = d.commitments.latest.commitmentFormat + val dustLimit = d.commitments.latest.localCommitParams.dustLimit + val (rvk1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, toSelfDelay, commitmentFormat, rvk, tx, dustLimit, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey) doPublish(rvk1, penaltyTxs) Closing.updateIrrevocablySpent(rvk1, tx) }) ) // if the local commitment tx just got confirmed, let's send an event telling when we will get the main output refund if (d1.localCommitPublished.exists(_.commitTx.txid == tx.txid)) { - context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.channelParams.localCommitParams.toSelfDelay.toInt)) + context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.latest.localCommitParams.toSelfDelay.toInt)) } // if the local or remote commitment tx just got confirmed, we abandon anchor transactions that were created based // on the other commitment: they will never confirm so we must free their wallet inputs. @@ -2239,7 +2265,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(c: CMD_CLOSE, d: DATA_CLOSING) => handleCommandError(ClosingAlreadyInProgress(d.channelId), c) case Event(c: CMD_BUMP_FORCE_CLOSE_FEE, d: DATA_CLOSING) => - d.commitments.channelParams.commitmentFormat match { + d.commitments.latest.commitmentFormat match { case commitmentFormat: Transactions.AnchorOutputsCommitmentFormat => val commitment = d.commitments.latest val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) @@ -2443,7 +2469,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 => // They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received // their commit_sig or their tx_signatures (depending on who must send tx_signatures first). - val commitSig = d.signingSession.remoteCommit.sign(d.channelParams, channelKeys, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput) + val fundingParams = d.signingSession.fundingParams + val commitSig = d.signingSession.remoteCommit.sign(d.channelParams, d.signingSession.remoteCommitParams, channelKeys, d.signingSession.fundingTxIndex, fundingParams.remoteFundingPubKey, d.signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED) } @@ -2456,7 +2483,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall if (channelReestablish.nextLocalCommitmentNumber == 0) { // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. - val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val fundingParams = signingSession.fundingParams + val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, signingSession.remoteCommitParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig } else { // They have already received our commit_sig, but we were waiting for them to send either commit_sig or @@ -2467,7 +2495,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures // and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == 0) { - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs) } else { goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs @@ -2495,7 +2523,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall d.commitments.latest.localFundingStatus.localSigs_opt match { case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 => log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) goto(WAIT_FOR_DUAL_FUNDING_READY) sending Seq(commitSig, txSigs, channelReady) case Some(txSigs) => log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2556,7 +2584,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId) - val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, channelKeys, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val fundingParams = signingSession.fundingParams + val commitSig = signingSession.remoteCommit.sign(d.commitments.channelParams, signingSession.remoteCommitParams, channelKeys, signingSession.fundingTxIndex, fundingParams.remoteFundingPubKey, signingSession.commitInput(channelKeys), fundingParams.commitmentFormat) sendQueue = sendQueue :+ commitSig } d.spliceStatus @@ -2567,7 +2596,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // tx_signatures and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) { log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.channelParams, d.commitments.latest.remoteCommitParams, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys), d.commitments.latest.commitmentFormat) sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs } else { log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2656,7 +2685,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val shutdownInProgress = d.localShutdown.nonEmpty || d.remoteShutdown.nonEmpty if (d.commitments.localChannelParams.paysCommitTxFees && !shutdownInProgress) { val currentFeeratePerKw = d.commitments.latest.localCommit.spec.commitTxFeerate - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, d.commitments.latest.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, d.commitments.latest.capacity) if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) } @@ -2862,7 +2891,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // slightly before us. In that case, the WatchConfirmed may trigger first, and it would be inefficient to let the // WatchPublished override our funding status: it will make us set a new WatchConfirmed that will instantly // trigger and rewrite the funding status again. - val alreadyConfirmed = d.commitments.active.map(_.localFundingStatus).collect { case f: LocalFundingStatus.ConfirmedFundingTx => f.tx }.exists(_.txid == w.tx.txid) + val alreadyConfirmed = d.commitments.active.exists(c => c.fundingTxId == w.tx.txid && c.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) if (alreadyConfirmed) { stay() } else { @@ -3122,11 +3151,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, commitments.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, commitments.capacity) val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate val shouldUpdateFee = d.commitments.localChannelParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) val shouldClose = !d.commitments.localChannelParams.paysCommitTxFees && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.channelParams.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldUpdateFee) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -3147,11 +3176,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall */ private def handleCurrentFeerateDisconnected(c: CurrentFeerates, d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.channelParams.commitmentFormat, commitments.capacity) + val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat, commitments.capacity) val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate // if the network fees are too high we risk to not be able to confirm our current commitment val shouldClose = networkFeeratePerKw > currentFeeratePerKw && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.channelParams.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && + nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldClose) { if (nodeParams.onChainFeeConf.closeOnOfflineMismatch) { @@ -3340,12 +3369,12 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeeratesForFundingClosing) val fundingContribution = InteractiveTxFunder.computeSpliceContribution( isInitiator = true, - sharedInput = Multisig2of2Input(parentCommitment), + sharedInput = SharedFundingInput(channelKeys, parentCommitment), spliceInAmount = cmd.additionalLocalFunding, spliceOut = cmd.spliceOutputs, targetFeerate = targetFeerate) val commitTxFees = if (d.commitments.localChannelParams.paysCommitTxFees) { - Transactions.commitTxTotalCost(d.commitments.channelParams.remoteCommitParams.dustLimit, parentCommitment.remoteCommit.spec, d.commitments.channelParams.commitmentFormat) + Transactions.commitTxTotalCost(parentCommitment.remoteCommitParams.dustLimit, parentCommitment.remoteCommit.spec, parentCommitment.commitmentFormat) } else { 0.sat } @@ -3375,7 +3404,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We use the same contribution as the previous splice attempt. val fundingContribution = rbf.latestFundingTx.fundingParams.localContribution val commitTxFees = if (d.commitments.localChannelParams.paysCommitTxFees) { - Transactions.commitTxTotalCost(d.commitments.channelParams.remoteCommitParams.dustLimit, rbf.parentCommitment.remoteCommit.spec, d.commitments.channelParams.commitmentFormat) + Transactions.commitTxTotalCost(rbf.parentCommitment.remoteCommitParams.dustLimit, rbf.parentCommitment.remoteCommit.spec, rbf.latestFundingTx.fundingParams.commitmentFormat) } else { 0.sat } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala index e89ae08750..dbc079cada 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala @@ -19,7 +19,6 @@ package fr.acinq.eclair.channel.fsm import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapter} import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel._ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{FullySignedSharedTransaction, InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs} @@ -27,6 +26,7 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.io.Peer.{LiquidityPurchaseSigned, OpenChannelResponse} +import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{ToMilliSatoshiConversion, randomBytes32} @@ -140,19 +140,14 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { when(WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL)(handleExceptions { case Event(open: OpenDualFundedChannel, d: DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL) => val localFundingPubkey = channelKeys.fundingKey(fundingTxIndex = 0).publicKey - val fundingScript = Funding.makeFundingScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat).pubkeyScript + val fundingScript = Transactions.makeFundingScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat).pubkeyScript Helpers.validateParamsDualFundedNonInitiator(nodeParams, d.init.channelType, open, fundingScript, remoteNodeId, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.init.fundingContribution_opt) match { case Left(t) => handleLocalError(t, d, Some(open)) case Right((channelFeatures, remoteShutdownScript, willFund_opt)) => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.commitmentFeerate, Some(open.fundingFeerate))) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = open.dustLimit, - maxHtlcValueInFlightMsat = open.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = None, // channel reserve will be computed based on channel capacity - htlcMinimum = open.htlcMinimum, - toRemoteDelay = open.toSelfDelay, - maxAcceptedHtlcs = open.maxAcceptedHtlcs, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -162,6 +157,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { // We've exchanged open_channel2 and accept_channel2, we now know the final channelId. val channelId = Helpers.computeChannelId(open.revocationBasepoint, channelKeys.revocationBasePoint) val channelParams = ChannelParams(channelId, d.init.channelConfig, channelFeatures, d.init.localChannelParams, remoteChannelParams, open.channelFlags) + val localCommitParams = CommitParams(d.init.proposedCommitParams.localDustLimit, d.init.proposedCommitParams.localHtlcMinimum, d.init.proposedCommitParams.localMaxHtlcValueInFlight, d.init.proposedCommitParams.localMaxAcceptedHtlcs, open.toSelfDelay) + val remoteCommitParams = CommitParams(open.dustLimit, open.htlcMinimum, open.maxHtlcValueInFlightMsat, open.maxAcceptedHtlcs, d.init.proposedCommitParams.toRemoteDelay) val localAmount = d.init.fundingContribution_opt.map(_.fundingAmount).getOrElse(0 sat) val tlvs: Set[AcceptDualFundedChannelTlv] = Set( d.init.localChannelParams.upfrontShutdownScript_opt.map(scriptPubKey => ChannelTlv.UpfrontShutdownScriptTlv(scriptPubKey)), @@ -174,12 +171,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val accept = AcceptDualFundedChannel( temporaryChannelId = open.temporaryChannelId, fundingAmount = localAmount, - dustLimit = d.init.proposedCommitParams.localDustLimit, - maxHtlcValueInFlightMsat = d.init.proposedCommitParams.localMaxHtlcValueInFlight, - htlcMinimum = d.init.proposedCommitParams.localHtlcMinimum, + dustLimit = localCommitParams.dustLimit, + maxHtlcValueInFlightMsat = localCommitParams.maxHtlcValueInFlight, + htlcMinimum = localCommitParams.htlcMinimum, minimumDepth = channelParams.minDepth(nodeParams.channelConf.minDepth).getOrElse(0).toLong, - toSelfDelay = d.init.proposedCommitParams.toRemoteDelay, - maxAcceptedHtlcs = d.init.proposedCommitParams.localMaxAcceptedHtlcs, + toSelfDelay = remoteCommitParams.toSelfDelay, + maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = localFundingPubkey, revocationBasepoint = channelKeys.revocationBasePoint, paymentBasepoint = d.init.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), @@ -200,6 +197,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { sharedInput_opt = None, remoteFundingPubKey = open.fundingPubkey, localOutputs = Nil, + commitmentFormat = d.init.channelType.commitmentFormat, lockTime = open.lockTime, dustLimit = open.dustLimit.max(accept.dustLimit), targetFeerate = open.fundingFeerate, @@ -209,12 +207,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val txBuilder = context.spawnAnonymous(InteractiveTxBuilder( randomBytes32(), nodeParams, fundingParams, - channelParams, channelKeys, purpose, + channelParams, localCommitParams, remoteCommitParams, channelKeys, purpose, localPushAmount = accept.pushAmount, remotePushAmount = open.pushAmount, willFund_opt.map(_.purchase), wallet)) txBuilder ! InteractiveTxBuilder.Start(self) - goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, open.secondPerCommitmentPoint, accept.pushAmount, open.pushAmount, txBuilder, deferred = None, replyTo_opt = None) sending accept + goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, localCommitParams, remoteCommitParams, open.secondPerCommitmentPoint, accept.pushAmount, open.pushAmount, txBuilder, deferred = None, replyTo_opt = None) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -238,12 +236,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, accept.temporaryChannelId, channelId)) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = accept.dustLimit, - maxHtlcValueInFlightMsat = accept.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = None, // channel reserve will be computed based on channel capacity - htlcMinimum = accept.htlcMinimum, - toRemoteDelay = accept.toSelfDelay, - maxAcceptedHtlcs = accept.maxAcceptedHtlcs, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, @@ -252,6 +245,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) // We start the interactive-tx funding protocol. val channelParams = ChannelParams(channelId, d.init.channelConfig, channelFeatures, d.init.localChannelParams, remoteChannelParams, d.lastSent.channelFlags) + val localCommitParams = CommitParams(d.init.proposedCommitParams.localDustLimit, d.init.proposedCommitParams.localHtlcMinimum, d.init.proposedCommitParams.localMaxHtlcValueInFlight, d.init.proposedCommitParams.localMaxAcceptedHtlcs, accept.toSelfDelay) + val remoteCommitParams = CommitParams(accept.dustLimit, accept.htlcMinimum, accept.maxHtlcValueInFlightMsat, accept.maxAcceptedHtlcs, d.init.proposedCommitParams.toRemoteDelay) val localAmount = d.lastSent.fundingAmount val remoteAmount = accept.fundingAmount val fundingParams = InteractiveTxParams( @@ -262,6 +257,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { sharedInput_opt = None, remoteFundingPubKey = accept.fundingPubkey, localOutputs = Nil, + commitmentFormat = d.init.channelType.commitmentFormat, lockTime = d.lastSent.lockTime, dustLimit = d.lastSent.dustLimit.max(accept.dustLimit), targetFeerate = d.lastSent.fundingFeerate, @@ -271,12 +267,12 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { val txBuilder = context.spawnAnonymous(InteractiveTxBuilder( randomBytes32(), nodeParams, fundingParams, - channelParams, channelKeys, purpose, + channelParams, localCommitParams, remoteCommitParams, channelKeys, purpose, localPushAmount = d.lastSent.pushAmount, remotePushAmount = accept.pushAmount, liquidityPurchase_opt = liquidityPurchase_opt, wallet)) txBuilder ! InteractiveTxBuilder.Start(self) - goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, accept.secondPerCommitmentPoint, d.lastSent.pushAmount, accept.pushAmount, txBuilder, deferred = None, replyTo_opt = Some(d.init.replyTo)) + goto(WAIT_FOR_DUAL_FUNDING_CREATED) using DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId, channelParams, localCommitParams, remoteCommitParams, accept.secondPerCommitmentPoint, d.lastSent.pushAmount, accept.pushAmount, txBuilder, deferred = None, replyTo_opt = Some(d.init.replyTo)) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL) => @@ -330,9 +326,9 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { d.deferred.foreach(self ! _) d.replyTo_opt.foreach(_ ! OpenChannelResponse.Created(d.channelId, status.fundingTx.txId, status.fundingTx.tx.localFees.truncateToSatoshi)) liquidityPurchase_opt.collect { - case purchase if !status.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, status.fundingTx.txId, status.fundingTxIndex, d.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !status.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, status.fundingTx.txId, status.fundingTxIndex, d.remoteCommitParams.htlcMinimum, purchase) } - val d1 = DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(d.channelParams, d.secondRemotePerCommitmentPoint, d.localPushAmount, d.remotePushAmount, status, None) + val d1 = DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(d.channelParams, d.secondRemotePerCommitmentPoint, d.localPushAmount, d.remotePushAmount, status) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) using d1 storing() sending commitSig case f: InteractiveTxBuilder.Failed => d.replyTo_opt.foreach(_ ! OpenChannelResponse.Rejected(f.cause.getMessage)) @@ -542,7 +538,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { log.info("rejecting rbf attempt: last attempt was less than {} blocks ago", nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks) stay() using d.copy(status = DualFundingStatus.RbfAborted) sending TxAbort(d.channelId, InvalidRbfAttemptTooSoon(d.channelId, d.latestFundingTx.createdAt, d.latestFundingTx.createdAt + nodeParams.channelConf.remoteRbfLimits.attemptDeltaBlocks).getMessage) } else { - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = true, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, None) match { case Left(t) => log.warning("rejecting rbf attempt: invalid liquidity ads request ({})", t.getMessage) @@ -564,6 +560,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { randomBytes32(), nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = d.commitments.active.head.localCommitParams, + remoteCommitParams = d.commitments.active.head.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.FundingTxRbf(d.commitments.active.head, previousTransactions = d.allFundingTxs.map(_.sharedTx), feeBudget_opt = None), localPushAmount = d.localPushAmount, remotePushAmount = d.remotePushAmount, @@ -600,7 +598,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { lockTime = cmd.lockTime, targetFeerate = cmd.targetFeerate, ) - val fundingScript = d.commitments.latest.commitInput.txOut.publicKeyScript + val fundingScript = d.commitments.latest.commitInput(channelKeys).txOut.publicKeyScript LiquidityAds.validateRemoteFunding(cmd.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, cmd.targetFeerate, isChannelCreation = true, msg.willFund_opt) match { case Left(t) => log.warning("rejecting rbf attempt: invalid liquidity ads response ({})", t.getMessage) @@ -612,6 +610,8 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { randomBytes32(), nodeParams, fundingParams, channelParams = d.commitments.channelParams, + localCommitParams = d.commitments.active.head.localCommitParams, + remoteCommitParams = d.commitments.active.head.remoteCommitParams, channelKeys = channelKeys, purpose = InteractiveTxBuilder.FundingTxRbf(d.commitments.active.head, previousTransactions = d.allFundingTxs.map(_.sharedTx), feeBudget_opt = Some(cmd.fundingFeeBudget)), localPushAmount = d.localPushAmount, remotePushAmount = d.remotePushAmount, @@ -695,7 +695,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { cmd_opt.foreach(cmd => cmd.replyTo ! RES_BUMP_FUNDING_FEE(rbfIndex = d.previousFundingTxs.length, signingSession.fundingTx.txId, signingSession.fundingTx.tx.localFees.truncateToSatoshi)) remoteCommitSig_opt.foreach(self ! _) liquidityPurchase_opt.collect { - case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, d.commitments.channelParams.remoteCommitParams.htlcMinimum, purchase) + case purchase if !signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseSigned(d.channelId, signingSession.fundingTx.txId, signingSession.fundingTxIndex, signingSession.remoteCommitParams.htlcMinimum, purchase) } val d1 = d.copy(status = DualFundingStatus.RbfWaitingForSigs(signingSession)) stay() using d1 storing() sending commitSig diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index 4332b503d4..23d0b5b74a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -110,12 +110,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.feeratePerKw, None)) val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = open.dustLimitSatoshis, - maxHtlcValueInFlightMsat = open.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = Some(open.channelReserveSatoshis), // our peer requires us to always have at least that much satoshis in our balance - htlcMinimum = open.htlcMinimumMsat, - toRemoteDelay = open.toSelfDelay, - maxAcceptedHtlcs = open.maxAcceptedHtlcs, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -124,17 +119,19 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) val fundingPubkey = channelKeys.fundingKey(fundingTxIndex = 0).publicKey val channelParams = ChannelParams(d.initFundee.temporaryChannelId, d.initFundee.channelConfig, channelFeatures, d.initFundee.localChannelParams, remoteChannelParams, open.channelFlags) + val localCommitParams = CommitParams(d.initFundee.proposedCommitParams.localDustLimit, d.initFundee.proposedCommitParams.localHtlcMinimum, d.initFundee.proposedCommitParams.localMaxHtlcValueInFlight, d.initFundee.proposedCommitParams.localMaxAcceptedHtlcs, open.toSelfDelay) + val remoteCommitParams = CommitParams(open.dustLimitSatoshis, open.htlcMinimumMsat, open.maxHtlcValueInFlightMsat, open.maxAcceptedHtlcs, d.initFundee.proposedCommitParams.toRemoteDelay) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. val localShutdownScript = d.initFundee.localChannelParams.upfrontShutdownScript_opt.getOrElse(ByteVector.empty) val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, - dustLimitSatoshis = d.initFundee.proposedCommitParams.localDustLimit, - maxHtlcValueInFlightMsat = d.initFundee.proposedCommitParams.localMaxHtlcValueInFlight, + dustLimitSatoshis = localCommitParams.dustLimit, + maxHtlcValueInFlightMsat = localCommitParams.maxHtlcValueInFlight, channelReserveSatoshis = d.initFundee.localChannelParams.initialRequestedChannelReserve_opt.get, minimumDepth = channelParams.minDepth(nodeParams.channelConf.minDepth).getOrElse(0).toLong, - htlcMinimumMsat = d.initFundee.proposedCommitParams.localHtlcMinimum, - toSelfDelay = d.initFundee.proposedCommitParams.toRemoteDelay, - maxAcceptedHtlcs = d.initFundee.proposedCommitParams.localMaxAcceptedHtlcs, + htlcMinimumMsat = localCommitParams.htlcMinimum, + toSelfDelay = remoteCommitParams.toSelfDelay, + maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = fundingPubkey, revocationBasepoint = channelKeys.revocationBasePoint, paymentBasepoint = d.initFundee.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), @@ -145,7 +142,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript), ChannelTlv.ChannelTypeTlv(d.initFundee.channelType) )) - goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept + goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(channelParams, d.initFundee.channelType, localCommitParams, remoteCommitParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -156,32 +153,29 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { }) when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { - case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(init, open)) => - Helpers.validateParamsSingleFundedFunder(nodeParams, init.channelType, init.localChannelParams.initFeatures, init.remoteInit.features, open, accept) match { + case Event(accept: AcceptChannel, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => + Helpers.validateParamsSingleFundedFunder(nodeParams, d.initFunder.channelType, d.initFunder.localChannelParams.initFeatures, d.initFunder.remoteInit.features, d.lastSent, accept) match { case Left(t) => d.initFunder.replyTo ! OpenChannelResponse.Rejected(t.getMessage) handleLocalError(t, d, Some(accept)) case Right((channelFeatures, remoteShutdownScript)) => val remoteChannelParams = RemoteChannelParams( nodeId = remoteNodeId, - dustLimit = accept.dustLimitSatoshis, - maxHtlcValueInFlightMsat = accept.maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = Some(accept.channelReserveSatoshis), // our peer requires us to always have at least that much satoshis in our balance - htlcMinimum = accept.htlcMinimumMsat, - toRemoteDelay = accept.toSelfDelay, - maxAcceptedHtlcs = accept.maxAcceptedHtlcs, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, htlcBasepoint = accept.htlcBasepoint, - initFeatures = init.remoteInit.features, + initFeatures = d.initFunder.remoteInit.features, upfrontShutdownScript_opt = remoteShutdownScript) log.info("remote will use fundingMinDepth={}", accept.minimumDepth) val localFundingKey = channelKeys.fundingKey(fundingTxIndex = 0) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, accept.fundingPubkey))) - wallet.makeFundingTx(fundingPubkeyScript, init.fundingAmount, init.fundingTxFeerate, init.fundingTxFeeBudget_opt).pipeTo(self) - val channelParams = ChannelParams(init.temporaryChannelId, init.channelConfig, channelFeatures, init.localChannelParams, remoteChannelParams, open.channelFlags) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, init.fundingAmount, init.pushAmount_opt.getOrElse(0 msat), init.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) + wallet.makeFundingTx(fundingPubkeyScript, d.initFunder.fundingAmount, d.initFunder.fundingTxFeerate, d.initFunder.fundingTxFeeBudget_opt).pipeTo(self) + val channelParams = ChannelParams(d.initFunder.temporaryChannelId, d.initFunder.channelConfig, channelFeatures, d.initFunder.localChannelParams, remoteChannelParams, d.lastSent.channelFlags) + val localCommitParams = CommitParams(d.initFunder.proposedCommitParams.localDustLimit, d.initFunder.proposedCommitParams.localHtlcMinimum, d.initFunder.proposedCommitParams.localMaxHtlcValueInFlight, d.initFunder.proposedCommitParams.localMaxAcceptedHtlcs, accept.toSelfDelay) + val remoteCommitParams = CommitParams(accept.dustLimitSatoshis, accept.htlcMinimumMsat, accept.maxHtlcValueInFlightMsat, accept.maxAcceptedHtlcs, d.initFunder.proposedCommitParams.toRemoteDelay) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams, d.initFunder.channelType, localCommitParams, remoteCommitParams, d.initFunder.fundingAmount, d.initFunder.pushAmount_opt.getOrElse(0 msat), d.initFunder.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -206,16 +200,17 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val temporaryChannelId = d.channelParams.channelId // let's create the first commitment tx that spends the yet uncommitted funding tx val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) - Funding.makeFirstCommitTxs(d.channelParams, localFundingAmount = d.fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = d.pushAmount, remotePushAmount = 0 msat, d.commitTxFeerate, fundingTx.txid, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) + Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = d.fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = d.pushAmount, remotePushAmount = 0 msat, d.commitTxFeerate, d.commitmentFormat, fundingTx.txid, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = d.channelParams.commitmentFormat match { + val localSigOfRemoteTx = d.commitmentFormat match { case _: SegwitV0CommitmentFormat => remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey).sig case _: SimpleTaprootChannelCommitmentFormat => ??? } + val remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -229,7 +224,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { txPublisher ! SetChannelId(remoteNodeId, channelId) context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) // NB: we don't send a ChannelSignatureSent for the first commit - goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint), fundingCreated, d.replyTo) sending fundingCreated + goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelParams1, d.channelType, d.localCommitParams, d.remoteCommitParams, d.remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, d.replyTo) sending fundingCreated } case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => @@ -258,17 +253,17 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { case Event(FundingCreated(_, fundingTxId, fundingTxOutputIndex, remoteSig, _), d: DATA_WAIT_FOR_FUNDING_CREATED) => val temporaryChannelId = d.channelParams.channelId val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - Funding.makeFirstCommitTxs(d.channelParams, localFundingAmount = 0 sat, remoteFundingAmount = d.fundingAmount, localPushAmount = 0 msat, remotePushAmount = d.pushAmount, d.commitTxFeerate, fundingTxId, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { + Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = 0 sat, remoteFundingAmount = d.fundingAmount, localPushAmount = 0 msat, remotePushAmount = d.pushAmount, d.commitTxFeerate, d.commitmentFormat, fundingTxId, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => // check remote signature validity localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, ChannelSpendSignature.IndividualSignature(remoteSig)) match { case false => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, fundingTxId, commitmentNumber = 0, localCommitTx.tx), d, None) case true => - val localSigOfRemoteTx = d.channelParams.commitmentFormat match { + val localSigOfRemoteTx = d.commitmentFormat match { case _: SegwitV0CommitmentFormat => remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey).sig case _: SimpleTaprootChannelCommitmentFormat => ??? } @@ -280,10 +275,15 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = localCommitTx.input.outPoint, + fundingAmount = localCommitTx.input.txOut.amount, remoteFundingPubKey = d.remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(None), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, localCommitTx.input, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + commitmentFormat = d.commitmentFormat, + localCommitParams = d.localCommitParams, + localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + remoteCommitParams = d.remoteCommitParams, remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint), nextRemoteCommit_opt = None) val commitments = Commitments( @@ -325,10 +325,15 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = d.localCommitTx.input.outPoint, + fundingAmount = d.localCommitTx.input.txOut.amount, remoteFundingPubKey = d.remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(Some(d.fundingTx)), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, d.localCommitTx.input, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + commitmentFormat = d.commitmentFormat, + localCommitParams = d.localCommitParams, + localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), + remoteCommitParams = d.remoteCommitParams, remoteCommit = d.remoteCommit, nextRemoteCommit_opt = None ) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala index 007bf006ab..531e5b7e3d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala @@ -44,7 +44,7 @@ trait CommonFundingHandlers extends CommonHandlers { */ def watchFundingSpent(commitment: Commitment, additionalKnownSpendingTxs: Set[TxId], delay_opt: Option[FiniteDuration]): Unit = { val knownSpendingTxs = commitment.commitTxIds.txIds ++ additionalKnownSpendingTxs - val watch = WatchFundingSpent(self, commitment.commitInput.outPoint.txid, commitment.commitInput.outPoint.index.toInt, knownSpendingTxs) + val watch = WatchFundingSpent(self, commitment.fundingInput.txid, commitment.fundingInput.index.toInt, knownSpendingTxs) delay_opt match { case Some(delay) => context.system.scheduler.scheduleOnce(delay, blockchain.toClassic, watch) case None => blockchain ! watch @@ -84,8 +84,8 @@ trait CommonFundingHandlers extends CommonHandlers { context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, w.tx)) d.commitments.all.find(_.fundingTxId == w.tx.txid) match { case Some(c) => - val scid = RealShortChannelId(w.blockHeight, w.txIndex, c.commitInput.outPoint.index.toInt) - val fundingStatus = ConfirmedFundingTx(w.tx, scid, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) + val scid = RealShortChannelId(w.blockHeight, w.txIndex, c.fundingInput.index.toInt) + val fundingStatus = ConfirmedFundingTx(w.tx.txOut(c.fundingInput.index.toInt), scid, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) // When a splice transaction confirms, it double-spends all the commitment transactions that only applied to the // previous funding transaction. Our peer cannot publish the corresponding revoked commitments anymore, so we can // clean-up the htlc data that we were storing for the matching penalty transactions. @@ -149,7 +149,7 @@ trait CommonFundingHandlers extends CommonHandlers { remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint) ) peer ! ChannelReadyForPayments(self, remoteNodeId, commitments.channelId, fundingTxIndex = 0) - DATA_NORMAL(commitments1, aliases1, None, initialChannelUpdate, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments1, aliases1, None, initialChannelUpdate, SpliceStatus.NoSplice, None, None, None) } def delayEarlyAnnouncementSigs(remoteAnnSigs: AnnouncementSignatures): Unit = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala index cafade4e17..261e1cae3e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala @@ -224,7 +224,7 @@ trait ErrorHandlers extends CommonHandlers { /** Publish 2nd-stage transactions for our local commitment. */ def doPublish(lcp: LocalCommitPublished, txs: Closing.LocalClose.SecondStageTransactions, commitment: FullCommitment): Unit = { - val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.commitInput.outPoint, "commit-tx", Closing.commitTxFee(commitment.commitInput, lcp.commitTx, commitment.localChannelParams.paysCommitTxFees), None) + val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.fundingInput, "commit-tx", Closing.commitTxFee(commitment.commitInput(channelKeys), lcp.commitTx, commitment.localChannelParams.paysCommitTxFees), None) val publishAnchorTx_opt = txs.anchorTx_opt match { case Some(anchorTx) if !lcp.isConfirmed => val confirmationTarget = Closing.confirmationTarget(commitment.localCommit, commitment.localCommitParams.dustLimit, commitment.commitmentFormat, nodeParams.onChainFeeConf) @@ -265,7 +265,7 @@ trait ErrorHandlers extends CommonHandlers { log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == commitments.remoteCommit.txId, "txid mismatch") val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput, commitTx, d.commitments.localChannelParams.paysCommitTxFees), "remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput(channelKeys), commitTx, d.commitments.localChannelParams.paysCommitTxFees), "remote-commit")) val (remoteCommitPublished, closingTxs) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitments, commitments.remoteCommit, commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished)) @@ -284,7 +284,7 @@ trait ErrorHandlers extends CommonHandlers { require(commitTx.txid == remoteCommit.txId, "txid mismatch") val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput, commitTx, d.commitments.localChannelParams.paysCommitTxFees), "next-remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput(channelKeys), commitTx, d.commitments.localChannelParams.paysCommitTxFees), "next-remote-commit")) val (remoteCommitPublished, closingTxs) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, remoteCommit, commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished)) @@ -332,9 +332,13 @@ trait ErrorHandlers extends CommonHandlers { val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) Closing.RevokedClose.getRemotePerCommitmentSecret(d.commitments.channelParams, channelKeys, d.commitments.remotePerCommitmentSecrets, tx) match { case Some((commitmentNumber, remotePerCommitmentSecret)) => - val (revokedCommitPublished, closingTxs) = Closing.RevokedClose.claimCommitTxOutputs(d.commitments.channelParams, channelKeys, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + // TODO: once we allow changing the commitment format or to_self_delay during a splice, those values may be incorrect. + val toSelfDelay = commitment.remoteCommitParams.toSelfDelay + val commitmentFormat = commitment.commitmentFormat + val dustLimit = commitment.localCommitParams.dustLimit + val (revokedCommitPublished, closingTxs) = Closing.RevokedClose.claimCommitTxOutputs(d.commitments.channelParams, channelKeys, tx, commitmentNumber, remotePerCommitmentSecret, toSelfDelay, commitmentFormat, nodeParams.db.channels, dustLimit, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) log.warning("txid={} was a revoked commitment, publishing the penalty tx", tx.txid) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput, tx, d.commitments.localChannelParams.paysCommitTxFees), "revoked-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput(channelKeys), tx, d.commitments.localChannelParams.paysCommitTxFees), "revoked-commit")) val exc = FundingTxSpent(d.channelId, tx.txid) val error = Error(d.channelId, exc.getMessage) val nextData = d match { @@ -348,10 +352,10 @@ trait ErrorHandlers extends CommonHandlers { case None => d match { case d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT => log.warning("they published a future commit (because we asked them to) in txid={}", tx.txid) - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(d.commitments.latest.commitInput, tx, d.commitments.latest.localChannelParams.paysCommitTxFees), "future-remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(d.commitments.latest.commitInput(channelKeys), tx, d.commitments.localChannelParams.paysCommitTxFees), "future-remote-commit")) val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint val commitKeys = d.commitments.latest.remoteKeys(channelKeys, remotePerCommitmentPoint) - val mainTx_opt = Closing.RemoteClose.claimMainOutput(d.commitments.channelParams, commitKeys, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + val mainTx_opt = Closing.RemoteClose.claimMainOutput(commitKeys, tx, d.commitments.latest.localCommitParams.dustLimit, d.commitments.latest.commitmentFormat, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) mainTx_opt.foreach(tx => log.warning("publishing our recovery transaction: tx={}", tx.toString)) val remoteCommitPublished = RemoteCommitPublished( commitTx = tx, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala index a52d189dff..ec33147a65 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Output.Local import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Purpose import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} -import fr.acinq.eclair.transactions.Transactions.{InputInfo, SegwitV0CommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo, SegwitV0CommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, ToMilliSatoshiConversion, UInt64} @@ -101,15 +101,8 @@ object InteractiveTxBuilder { case class RequireConfirmedInputs(forLocal: Boolean, forRemote: Boolean) /** An input that is already shared between participants (e.g. the current funding output when doing a splice). */ - sealed trait SharedFundingInput { - // @formatter:off - def info: InputInfo - def weight: Int - // @formatter:on - } - - case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) extends SharedFundingInput { - override val weight: Int = 388 + case class SharedFundingInput(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey, commitmentFormat: CommitmentFormat) { + val weight: Int = commitmentFormat.fundingInputWeight def sign(channelKeys: ChannelKeys, tx: Transaction, spentUtxos: Map[OutPoint, TxOut]): ChannelSpendSignature.IndividualSignature = { val localFundingKey = channelKeys.fundingKey(fundingTxIndex) @@ -117,11 +110,12 @@ object InteractiveTxBuilder { } } - object Multisig2of2Input { - def apply(commitment: Commitment): Multisig2of2Input = Multisig2of2Input( - info = commitment.commitInput, + object SharedFundingInput { + def apply(channelKeys: ChannelKeys, commitment: Commitment): SharedFundingInput = SharedFundingInput( + info = commitment.commitInput(channelKeys), fundingTxIndex = commitment.fundingTxIndex, - remoteFundingPubkey = commitment.remoteFundingPubKey + remoteFundingPubkey = commitment.remoteFundingPubKey, + commitmentFormat = commitment.commitmentFormat, ) } @@ -145,6 +139,7 @@ object InteractiveTxBuilder { sharedInput_opt: Option[SharedFundingInput], remoteFundingPubKey: PublicKey, localOutputs: List[TxOut], + commitmentFormat: CommitmentFormat, lockTime: Long, dustLimit: Satoshi, targetFeerate: FeeratePerKw, @@ -388,6 +383,8 @@ object InteractiveTxBuilder { nodeParams: NodeParams, fundingParams: InteractiveTxParams, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, channelKeys: ChannelKeys, purpose: Purpose, localPushAmount: MilliSatoshi, @@ -425,7 +422,7 @@ object InteractiveTxBuilder { replyTo ! LocalFailure(InvalidLiquidityAdsPaymentType(channelParams.channelId, liquidityPurchase_opt.get.paymentDetails.paymentType, Set(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.FromChannelBalanceForFutureHtlc))) Behaviors.stopped } else { - val actor = new InteractiveTxBuilder(replyTo, sessionId, nodeParams, channelParams, channelKeys, fundingParams, purpose, localPushAmount, remotePushAmount, liquidityPurchase_opt, wallet, stash, context) + val actor = new InteractiveTxBuilder(replyTo, sessionId, nodeParams, channelParams, localCommitParams, remoteCommitParams, channelKeys, fundingParams, purpose, localPushAmount, remotePushAmount, liquidityPurchase_opt, wallet, stash, context) actor.start() } case Abort => Behaviors.stopped @@ -444,6 +441,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon sessionId: ByteVector32, nodeParams: NodeParams, channelParams: ChannelParams, + localCommitParams: CommitParams, + remoteCommitParams: CommitParams, channelKeys: ChannelKeys, fundingParams: InteractiveTxBuilder.InteractiveTxParams, purpose: Purpose, @@ -739,7 +738,7 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon // operation, remote post-splice reserve may actually be worse than before, but that's not their fault. } else { // If remote removes funds from the channel, it must meet reserve requirements post-splice - val remoteReserve = channelParams.remoteChannelReserveForCapacity(fundingParams.fundingAmount, isSplice = true) + val remoteReserve = (fundingParams.fundingAmount / 100).max(fundingParams.dustLimit) if (sharedOutput.remoteAmount < remoteReserve) { log.warn("invalid interactive tx: peer takes too much funds out and falls below the channel reserve ({} < {})", sharedOutput.remoteAmount, remoteReserve) return Left(InvalidCompleteInteractiveTx(fundingParams.channelId)) @@ -833,14 +832,15 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon val fundingTx = completeTx.buildUnsignedTx() val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingPubkeyScript) val liquidityFee = fundingParams.liquidityFees(liquidityPurchase_opt) - val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex) - val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint) - Funding.makeCommitTxs(channelParams, + val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex, fundingParams.commitmentFormat) + val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint, fundingParams.commitmentFormat) + Funding.makeCommitTxs(channelParams, localCommitParams, remoteCommitParams, fundingAmount = fundingParams.fundingAmount, toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount - liquidityFee, toRemote = completeTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount + liquidityFee, localHtlcs = purpose.localHtlcs, purpose.commitTxFeerate, + fundingParams.commitmentFormat, fundingTxIndex = purpose.fundingTxIndex, fundingTx.txid, fundingOutputIndex, localFundingKey, fundingParams.remoteFundingPubKey, @@ -851,16 +851,16 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon unlockAndStop(completeTx) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx, sortedHtlcTxs)) => require(fundingTx.txOut(fundingOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, "pubkey script mismatch!") - channelParams.commitmentFormat match { + fundingParams.commitmentFormat match { case _: SegwitV0CommitmentFormat => val localSigOfRemoteTx = remoteCommitTx.sign(localFundingKey, fundingParams.remoteFundingPubKey).sig val htlcSignatures = sortedHtlcTxs.map(_.localSig(remoteCommitmentKeys)).toList val localCommitSig = CommitSig(fundingParams.channelId, localSigOfRemoteTx, htlcSignatures) - val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx.tx.txid, localCommitTx.input) + val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx.tx.txid) val remoteCommit = RemoteCommit(purpose.remoteCommitIndex, remoteSpec, remoteCommitTx.tx.txid, purpose.remotePerCommitmentPoint) signFundingTx(completeTx, localCommitSig, localCommit, remoteCommit) - case _: SimpleTaprootChannelCommitmentFormat => ??? - + case _: SimpleTaprootChannelCommitmentFormat => + ??? } } } @@ -897,7 +897,9 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon fundingParams, purpose.fundingTxIndex, signedTx, + localCommitParams, Left(localCommit), + remoteCommitParams, remoteCommit, liquidityPurchase_opt.map(_.basicInfo(isBuyer = fundingParams.isInitiator)) ) @@ -920,9 +922,10 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon import fr.acinq.bitcoin.scalacompat.KotlinUtils._ val tx = unsignedTx.buildUnsignedTx() - val sharedSig_opt = fundingParams.sharedInput_opt.collect { - case i: Multisig2of2Input => i.sign(channelKeys, tx, unsignedTx.inputDetails).sig - } + val sharedSig_opt = fundingParams.sharedInput_opt.map(i => i.commitmentFormat match { + case _: SegwitV0CommitmentFormat => i.sign(channelKeys, tx, unsignedTx.inputDetails).sig + case _: SimpleTaprootChannelCommitmentFormat => ??? + }) if (unsignedTx.localInputs.isEmpty) { context.self ! SignTransactionResult(PartiallySignedSharedTransaction(unsignedTx, TxSignatures(fundingParams.channelId, tx, Nil, sharedSig_opt))) } else { @@ -1020,7 +1023,7 @@ object InteractiveTxSigningSession { // +-------+ +-------+ /** A local commitment for which we haven't received our peer's signatures. */ - case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo) + case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId) private def shouldSignFirst(isInitiator: Boolean, channelParams: ChannelParams, tx: SharedTransaction): Boolean = { val sharedAmountIn = tx.sharedInput_opt.map(_.txOut.amount).getOrElse(0 sat) @@ -1046,18 +1049,19 @@ object InteractiveTxSigningSession { log.info("invalid tx_signatures: witness count mismatch (expected={}, got={})", partiallySignedTx.tx.remoteInputs.length, remoteSigs.witnesses.length) return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) } - val sharedSigs_opt = fundingParams.sharedInput_opt match { - case Some(sharedInput: Multisig2of2Input) => - (partiallySignedTx.localSigs.previousFundingTxSig_opt, remoteSigs.previousFundingTxSig_opt) match { + val sharedSigs_opt = fundingParams.sharedInput_opt.map(sharedInput => { + sharedInput.commitmentFormat match { + case _: SegwitV0CommitmentFormat => (partiallySignedTx.localSigs.previousFundingTxSig_opt, remoteSigs.previousFundingTxSig_opt) match { case (Some(localSig), Some(remoteSig)) => val localFundingPubkey = channelKeys.fundingKey(sharedInput.fundingTxIndex).publicKey - Some(Scripts.witness2of2(localSig, remoteSig, localFundingPubkey, sharedInput.remoteFundingPubkey)) + Scripts.witness2of2(localSig, remoteSig, localFundingPubkey, sharedInput.remoteFundingPubkey) case _ => log.info("invalid tx_signatures: missing shared input signatures") return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) } - case None => None - } + case _: SimpleTaprootChannelCommitmentFormat => ??? + } + }) val txWithSigs = FullySignedSharedTransaction(partiallySignedTx.tx, partiallySignedTx.localSigs, remoteSigs, sharedSigs_opt) if (remoteSigs.txId != txWithSigs.signedTx.txid) { log.info("invalid tx_signatures: txId mismatch (expected={}, got={})", txWithSigs.signedTx.txid, remoteSigs.txId) @@ -1092,10 +1096,11 @@ object InteractiveTxSigningSession { case class WaitingForSigs(fundingParams: InteractiveTxParams, fundingTxIndex: Long, fundingTx: PartiallySignedSharedTransaction, + localCommitParams: CommitParams, localCommit: Either[UnsignedLocalCommit, LocalCommit], + remoteCommitParams: CommitParams, remoteCommit: RemoteCommit, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession { - val commitInput: InputInfo = localCommit.fold(_.input, _.input) val localCommitIndex: Long = localCommit.fold(_.index, _.index) // This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not. val nextLocalCommitmentNumber: Long = localCommit match { @@ -1103,15 +1108,26 @@ object InteractiveTxSigningSession { case Right(commit) => commit.index + 1 } + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + + def commitInput(fundingKey: PrivateKey): InputInfo = { + val fundingScript = Transactions.makeFundingScript(fundingKey.publicKey, fundingParams.remoteFundingPubKey, fundingParams.commitmentFormat).pubkeyScript + val fundingOutput = OutPoint(fundingTx.txId, fundingTx.tx.buildUnsignedTx().txOut.indexWhere(txOut => txOut.amount == fundingParams.fundingAmount && txOut.publicKeyScript == fundingScript)) + InputInfo(fundingOutput, TxOut(fundingParams.fundingAmount, fundingScript)) + } + + def commitInput(channelKeys: ChannelKeys): InputInfo = commitInput(localFundingKey(channelKeys)) + def receiveCommitSig(channelParams: ChannelParams, channelKeys: ChannelKeys, remoteCommitSig: CommitSig, currentBlockHeight: BlockHeight)(implicit log: LoggingAdapter): Either[ChannelException, InteractiveTxSigningSession] = { localCommit match { case Left(unsignedLocalCommit) => - val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex) - LocalCommit.fromCommitSig(channelParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, commitInput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec).map { signedLocalCommit => + val fundingKey = localFundingKey(channelKeys) + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex, fundingParams.commitmentFormat) + val fundingOutput = commitInput(fundingKey) + LocalCommit.fromCommitSig(channelParams, localCommitParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, fundingOutput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, fundingParams.commitmentFormat).map { signedLocalCommit => if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) { val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, currentBlockHeight, fundingParams, liquidityPurchase_opt) - val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingOutput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, fundingParams.commitmentFormat, localCommitParams, signedLocalCommit, remoteCommitParams, remoteCommit, None) SendingSigs(fundingStatus, commitment, fundingTx.localSigs) } else { this.copy(localCommit = Right(signedLocalCommit)) @@ -1135,8 +1151,9 @@ object InteractiveTxSigningSession { Left(f) case Right(fullySignedTx) => log.info("interactive-tx fully signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", fullySignedTx.tx.localInputs.length, fullySignedTx.tx.remoteInputs.length, fullySignedTx.tx.localOutputs.length, fullySignedTx.tx.remoteOutputs.length) + val fundingOutput = commitInput(channelKeys) val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fullySignedTx, currentBlockHeight, fundingParams, liquidityPurchase_opt) - val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingOutput.outPoint, fundingParams.fundingAmount, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, fundingParams.commitmentFormat, localCommitParams, signedLocalCommit, remoteCommitParams, remoteCommit, None) Right(SendingSigs(fundingStatus, commitment, fullySignedTx.localSigs)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala index 564a425592..540650ce38 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.crypto.keymanager import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.Features import fr.acinq.eclair.channel.ChannelParams -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} /** * Created by t-bast on 10/04/2025. @@ -67,14 +67,14 @@ case class LocalCommitmentKeys(ourDelayedPaymentKey: PrivateKey, } object LocalCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long): LocalCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long, commitmentFormat: CommitmentFormat): LocalCommitmentKeys = { val localPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex) LocalCommitmentKeys( ourDelayedPaymentKey = channelKeys.delayedPaymentKey(localPerCommitmentPoint), - theirPaymentPublicKey = params.commitmentFormat match { - case DefaultCommitmentFormat if params.channelFeatures.hasFeature(Features.StaticRemoteKey) => params.remoteParams.paymentBasepoint + theirPaymentPublicKey = commitmentFormat match { + case DefaultCommitmentFormat if params.localParams.walletStaticPaymentBasepoint.nonEmpty => params.remoteParams.paymentBasepoint case DefaultCommitmentFormat => ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.paymentBasepoint, localPerCommitmentPoint) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint + case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint }, ourPaymentBasePoint = params.localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), ourHtlcKey = channelKeys.htlcKey(localPerCommitmentPoint), @@ -116,11 +116,11 @@ case class RemoteCommitmentKeys(ourPaymentKey: Either[PublicKey, PrivateKey], } object RemoteCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey, commitmentFormat: CommitmentFormat): RemoteCommitmentKeys = { RemoteCommitmentKeys( ourPaymentKey = params.localParams.walletStaticPaymentBasepoint match { case Some(walletPublicKey) => Left(walletPublicKey) - case None => params.commitmentFormat match { + case None => commitmentFormat match { // Note that if we're using option_static_remotekey, a walletStaticPaymentBasepoint will be provided. case DefaultCommitmentFormat => Right(channelKeys.paymentKey(remotePerCommitmentPoint)) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Right(channelKeys.paymentBaseSecret) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala index d670ce61df..7a481db36c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala @@ -77,16 +77,11 @@ object OpenChannelInterceptor { } } - def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): LocalChannelParams = { + def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { LocalChannelParams( nodeParams.nodeId, nodeParams.channelKeyManager.newFundingKeyPath(isChannelOpener), - dustLimit = nodeParams.channelConf.dustLimit, - maxHtlcValueInFlightMsat = nodeParams.channelConf.maxHtlcValueInFlight(fundingAmount, unlimitedMaxHtlcValueInFlight), - initialRequestedChannelReserve_opt = if (dualFunded) None else Some((fundingAmount * nodeParams.channelConf.reserveToFundingRatio).max(nodeParams.channelConf.dustLimit)), // BOLT #2: make sure that our reserve is above our dust limit - htlcMinimum = nodeParams.channelConf.htlcMinimum, - toRemoteDelay = nodeParams.channelConf.toRemoteDelay, // we choose their delay - maxAcceptedHtlcs = nodeParams.channelConf.maxAcceptedHtlcs, + initialRequestedChannelReserve_opt = if (dualFunded) None else Some((fundingAmount * nodeParams.channelConf.reserveToFundingRatio).max(nodeParams.channelConf.dustLimit)), // BOLT #2: make sure that our reserve is above our dust limit, isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, upfrontShutdownScript_opt = upfrontShutdownScript_opt, @@ -128,7 +123,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript) // If we're purchasing liquidity, we expect our peer to contribute at least the amount we're purchasing, otherwise we'll cancel the funding attempt. val expectedFundingAmount = request.open.fundingAmount + request.open.requestFunding_opt.map(_.requestedAmount).getOrElse(0 sat) - val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount, request.open.disableMaxHtlcValueInFlight) + val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount) peer ! Peer.SpawnChannelInitiator(request.replyTo, request.open, ChannelConfig.standard, channelType, localParams) waitForRequest() } @@ -161,8 +156,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = dualFunded, - fundingAmount = request.fundingAmount, - disableMaxHtlcValueInFlight = false + fundingAmount = request.fundingAmount ) checkRateLimits(request, channelType, localParams) case Left(ex) => @@ -196,10 +190,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], request.open.fold(_ => None, _.requestFunding_opt) match { case Some(requestFunding) if Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.OnTheFlyFunding) && localParams.paysCommitTxFees => val addFunding = LiquidityAds.AddFunding(requestFunding.requestedAmount, nodeParams.liquidityAdsConfig.rates_opt) - // Now that we know how much we'll contribute to the funding transaction, we update the maxHtlcValueInFlight. - val maxHtlcValueInFlight = Seq(localParams.maxHtlcValueInFlightMsat, nodeParams.channelConf.maxHtlcValueInFlight(request.fundingAmount + addFunding.fundingAmount, unlimited = false)).max - val localParams1 = localParams.copy(maxHtlcValueInFlightMsat = maxHtlcValueInFlight) - val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams1, request.peerConnection.toClassic) + val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams, request.peerConnection.toClassic) checkNoExistingChannel(request, accept) case _ => // We don't honor liquidity ads for new channels: node operators should use plugin for that. @@ -302,7 +293,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], } } - private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, disableMaxHtlcValueInFlight: Boolean): LocalChannelParams = { + private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { makeChannelParams( nodeParams, initFeatures, // Note that if our bitcoin node is configured to use taproot, this will generate a taproot script. @@ -314,8 +305,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, dualFunded = dualFunded, - fundingAmount, - disableMaxHtlcValueInFlight + fundingAmount ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 30e343a3ea..36eaa37666 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -602,8 +602,8 @@ object OriginSerializer extends MinimalSerializer({ }) // @formatter:off -case class CommitmentJson(fundingTxIndex: Long, fundingTx: InputInfo, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit]) -object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.commitInput, c.localFundingStatus, c.remoteFundingStatus, c.localCommit, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit))) +case class CommitmentJson(fundingTxIndex: Long, fundingInput: OutPoint, fundingAmount: Satoshi, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, commitmentFormat: String, localCommitParams: CommitParams, localCommit: LocalCommit, remoteCommitParams: CommitParams, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit]) +object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.fundingInput, c.fundingAmount, c.localFundingStatus, c.remoteFundingStatus, c.commitmentFormat.toString, c.localCommitParams, c.localCommit, c.remoteCommitParams, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit))) // @formatter:on // @formatter:off diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala index 14b8226a3e..08549f8d82 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala @@ -301,7 +301,7 @@ object OnTheFlyFunding { private def relay(data: DATA_NORMAL): Behavior[Command] = { context.log.debug("relaying {} on-the-fly HTLCs that have been funded", cmd.proposed.size) - val htlcMinimum = data.commitments.channelParams.remoteCommitParams.htlcMinimum + val htlcMinimum = data.commitments.latest.remoteCommitParams.htlcMinimum val cmdAdapter = context.messageAdapter[CommandResponse[CMD_ADD_HTLC]](WrappedCommandResponse) val htlcSettledAdapter = context.messageAdapter[RES_ADD_SETTLED[Origin.Hot, HtlcResult]](WrappedHtlcSettled) cmd.proposed.foldLeft(cmd.status.remainingFees) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala index aee51b7b59..3f4067a413 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.reputation import fr.acinq.bitcoin.scalacompat.ByteVector32 -import fr.acinq.eclair.channel.{ChannelJammingException, ChannelParams, Commitments, IncomingConfidenceTooLow, OutgoingConfidenceTooLow, TooManySmallHtlcs} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.wire.protocol.UpdateAddHtlc import fr.acinq.eclair.{MilliSatoshi, TimestampMilli} @@ -109,8 +109,8 @@ case class Reputation(pastScores: Map[Int, PastScore], pending: Map[HtlcId, Pend } object Reputation { - val endorsementLevels = 8 - val maxEndorsement = endorsementLevels - 1 + private val endorsementLevels = 8 + val maxEndorsement: Int = endorsementLevels - 1 case class Config(enabled: Boolean, halfLife: FiniteDuration, maxRelayDuration: FiniteDuration, pendingMultiplier: Double) @@ -120,29 +120,29 @@ object Reputation { * @param incomingConfidence Confidence that the outgoing HTLC will succeed given the reputation of the incoming peer */ case class Score(incomingConfidence: Double, outgoingConfidence: Double) { - val endorsement = toEndorsement(incomingConfidence) + val endorsement: Int = toEndorsement(incomingConfidence) - def checkOutgoingChannelOccupancy(outgoingHtlcs: Seq[UpdateAddHtlc], params: ChannelParams): Either[ChannelJammingException, Unit] = { - val maxAcceptedHtlcs = Seq(params.localCommitParams.maxAcceptedHtlcs, params.remoteCommitParams.maxAcceptedHtlcs).min + def checkOutgoingChannelOccupancy(channelId: ByteVector32, commitment: Commitment, outgoingHtlcs: Seq[UpdateAddHtlc]): Either[ChannelJammingException, Unit] = { + val maxAcceptedHtlcs = Seq(commitment.localCommitParams.maxAcceptedHtlcs, commitment.remoteCommitParams.maxAcceptedHtlcs).min for ((amountMsat, i) <- outgoingHtlcs.map(_.amountMsat).sorted.zipWithIndex) { // We want to allow some small HTLCs but still keep slots for larger ones. // We never want to reject HTLCs of size above `maxHtlcAmount / maxAcceptedHtlcs` as too small because we want to allow filling all the slots with HTLCs of equal sizes. // We use exponentially spaced thresholds in between. - if (amountMsat.toLong < 1 || amountMsat.toLong.toDouble < math.pow(params.maxHtlcValueInFlight.toLong.toDouble / maxAcceptedHtlcs, i / maxAcceptedHtlcs)) { - return Left(TooManySmallHtlcs(params.channelId, number = i + 1, below = amountMsat)) + if (amountMsat.toLong < 1 || amountMsat.toLong.toDouble < math.pow(commitment.maxHtlcValueInFlight.toLong.toDouble / maxAcceptedHtlcs, i / maxAcceptedHtlcs)) { + return Left(TooManySmallHtlcs(channelId, number = i + 1, below = amountMsat)) } } val htlcValueInFlight = outgoingHtlcs.map(_.amountMsat).sum val slotsOccupancy = outgoingHtlcs.size.toDouble / maxAcceptedHtlcs - val valueOccupancy = htlcValueInFlight.toLong.toDouble / params.maxHtlcValueInFlight.toLong.toDouble + val valueOccupancy = htlcValueInFlight.toLong.toDouble / commitment.maxHtlcValueInFlight.toLong.toDouble val occupancy = slotsOccupancy max valueOccupancy // Because there are only 8 endorsement levels, the highest endorsement corresponds to a confidence between 87.5% and 100%. // So even for well-behaved peers setting the highest endorsement we still expect a confidence of less than 93.75%. // To compensate for that we add a tolerance of 10% that's also useful for nodes without history. if (incomingConfidence + 0.1 < occupancy) { - return Left(IncomingConfidenceTooLow(params.channelId, incomingConfidence, occupancy)) + return Left(IncomingConfidenceTooLow(channelId, incomingConfidence, occupancy)) } Right(()) @@ -157,7 +157,7 @@ object Reputation { } case object Score { - val max = Score(1.0, 1.0) + val max: Score = Score(1.0, 1.0) def fromEndorsement(endorsement: Int): Score = Score((endorsement + 0.5) / 8, 1.0) } @@ -167,9 +167,9 @@ object Reputation { def incomingOccupancy(commitments: Commitments): Double = { commitments.active.map(commitment => { val incomingHtlcs = commitment.localCommit.spec.htlcs.collect(DirectedHtlc.incoming) - val slotsOccupancy = incomingHtlcs.size.toDouble / (commitments.channelParams.localCommitParams.maxAcceptedHtlcs min commitments.channelParams.remoteCommitParams.maxAcceptedHtlcs) + val slotsOccupancy = commitments.active.map(c => incomingHtlcs.size.toDouble / (c.localCommitParams.maxAcceptedHtlcs min c.remoteCommitParams.maxAcceptedHtlcs)).max val htlcValueInFlight = incomingHtlcs.toSeq.map(_.amountMsat).sum - val valueOccupancy = htlcValueInFlight.toLong.toDouble / commitments.channelParams.maxHtlcValueInFlight.toLong.toDouble + val valueOccupancy = commitments.active.map(c => htlcValueInFlight.toLong.toDouble / c.maxHtlcValueInFlight.toLong.toDouble).max slotsOccupancy max valueOccupancy }).max } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index cb6843053e..e9e7f5b3ba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -58,6 +58,8 @@ object Transactions { sealed trait CommitmentFormat { // @formatter:off + /** Weight of a fully signed channel output, when spent by a [[ChannelSpendTransaction]]. */ + def fundingInputWeight: Int /** Weight of a fully signed [[CommitTx]] transaction without any HTLCs. */ def commitWeight: Int /** Weight of a fully signed [[ClaimLocalAnchorTx]] or [[ClaimRemoteAnchorTx]] input. */ @@ -93,7 +95,9 @@ object Transactions { // @formatter:on } - sealed trait SegwitV0CommitmentFormat extends CommitmentFormat + sealed trait SegwitV0CommitmentFormat extends CommitmentFormat { + override val fundingInputWeight = 384 + } /** * Commitment format as defined in the v1.0 specification (https://github.com/lightningnetwork/lightning-rfc/tree/v1.0). @@ -115,6 +119,8 @@ object Transactions { override val htlcOfferedPenaltyWeight = 572 override val htlcReceivedPenaltyWeight = 577 override val claimHtlcPenaltyWeight = 484 + + override def toString: String = "legacy" } /** @@ -149,19 +155,24 @@ object Transactions { * Don't use this commitment format unless you know what you're doing! * See https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html for details. */ - case object UnsafeLegacyAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat + case object UnsafeLegacyAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat { + override def toString: String = "unsafe_anchor_outputs" + } /** * This commitment format removes the fees from the pre-signed 2nd-stage htlc transactions to fix the fee inflating * attack against [[UnsafeLegacyAnchorOutputsCommitmentFormat]]. */ - case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat + case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat { + override def toString: String = "anchor_outputs" + } sealed trait TaprootCommitmentFormat extends CommitmentFormat sealed trait SimpleTaprootChannelCommitmentFormat extends TaprootCommitmentFormat { // weights for taproot transactions are deterministic since signatures are encoded as 64 bytes and // not in variable length DER format (around 72 bytes) + override val fundingInputWeight = 230 override val commitWeight = 960 override val anchorInputWeight = 230 override val htlcOutputWeight = 172 @@ -180,13 +191,15 @@ object Transactions { override val claimHtlcPenaltyWeight = 396 } - case object LegacySimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat + case object LegacySimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat { + override def toString: String = "unsafe_simple_taproot" + } - case object ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat + case object ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat extends SimpleTaprootChannelCommitmentFormat { + override def toString: String = "simple_taproot" + } - // TODO: we're currently keeping the now unused redeemScript to avoid a painful codec update. When creating v5 codecs - // (for taproot channels), don't forget to remove this field from the InputInfo class! - case class InputInfo(outPoint: OutPoint, txOut: TxOut, unusedRedeemScript: ByteVector) + case class InputInfo(outPoint: OutPoint, txOut: TxOut) // @formatter:off /** This trait contains redeem information necessary to spend different types of segwit inputs. */ @@ -598,7 +611,7 @@ object Transactions { outputIndex: Int, commitmentFormat: CommitmentFormat): UnsignedHtlcSuccessTx = { val htlc = output.htlc.add - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, @@ -652,7 +665,7 @@ object Transactions { outputIndex: Int, commitmentFormat: CommitmentFormat): UnsignedHtlcTimeoutTx = { val htlc = output.htlc.add - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, @@ -699,7 +712,7 @@ object Transactions { findPubKeyScriptIndex(htlcTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.htlcDelayedWeight) val tx = Transaction( version = 2, @@ -762,7 +775,7 @@ object Transactions { def findInput(commitTx: Transaction, outputs: Seq[CommitmentOutput], htlc: UpdateAddHtlc): Option[InputInfo] = { outputs.zipWithIndex.collectFirst { case (OutHtlc(outgoingHtlc, _, _), outputIndex) if outgoingHtlc.add.id == htlc.id => - InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) } } @@ -822,7 +835,7 @@ object Transactions { def findInput(commitTx: Transaction, outputs: Seq[CommitmentOutput], htlc: UpdateAddHtlc): Option[InputInfo] = { outputs.zipWithIndex.collectFirst { case (InHtlc(incomingHtlc, _, _), outputIndex) if incomingHtlc.add.id == htlc.id => - InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) } } @@ -903,7 +916,7 @@ object Transactions { def findInput(commitTx: Transaction, fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { val pubKeyScript = redeemInfo(fundingKey.publicKey, commitKeys.publicKeys, commitmentFormat).pubkeyScript - findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex))) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimLocalAnchorTx] = { @@ -940,7 +953,7 @@ object Transactions { def findInput(commitTx: Transaction, fundingKey: PrivateKey, commitKeys: RemoteCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { val pubKeyScript = redeemInfo(fundingKey.publicKey, commitKeys.publicKeys, commitmentFormat).pubkeyScript - findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex))) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: RemoteCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimRemoteAnchorTx] = { @@ -973,7 +986,7 @@ object Transactions { commitKeys.ourPaymentKey match { case Left(_) => Left(OutputAlreadyInWallet) case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) val tx = Transaction( version = 2, @@ -1026,7 +1039,7 @@ object Transactions { commitKeys.ourPaymentKey match { case Left(_) => Left(OutputAlreadyInWallet) case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) val tx = Transaction( version = 2, @@ -1075,7 +1088,7 @@ object Transactions { findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toLocalDelayedWeight) val tx = Transaction( version = 2, @@ -1123,7 +1136,7 @@ object Transactions { findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.mainPenaltyWeight) val tx = Transaction( version = 2, @@ -1200,7 +1213,7 @@ object Transactions { localFinalScriptPubKey: ByteVector, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcPenaltyTx] = { - val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex)) val amount = input.txOut.amount - weight2fee(feerate, redeemDetails.weight) val tx = Transaction( version = 2, @@ -1252,7 +1265,7 @@ object Transactions { // Note that we check *all* outputs of the tx, because it could spend a batch of HTLC outputs from the commit tx. htlcTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if txOut.publicKeyScript == redeemInfo.pubkeyScript => - val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), ByteVector.empty) + val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex)) val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.claimHtlcPenaltyWeight) val tx = Transaction( version = 2, @@ -1502,6 +1515,19 @@ object Transactions { } } + def makeFundingScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): RedeemInfo = { + commitmentFormat match { + case _: SegwitV0CommitmentFormat => RedeemInfo.P2wsh(Script.write(multiSig2of2(localFundingKey, remoteFundingKey))) + case _: SimpleTaprootChannelCommitmentFormat => RedeemInfo.TaprootKeyPath(Taproot.musig2Aggregate(localFundingKey, remoteFundingKey), None) + } + } + + def makeFundingInputInfo(fundingTxId: TxId, fundingOutputIndex: Int, fundingAmount: Satoshi, localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = { + val redeemInfo = makeFundingScript(localFundingKey, remoteFundingKey, commitmentFormat) + val fundingTxOut = TxOut(fundingAmount, redeemInfo.pubkeyScript) + InputInfo(OutPoint(fundingTxId, fundingOutputIndex), fundingTxOut) + } + // @formatter:off /** We always create multiple versions of each closing transaction, where fees are either paid by us or by our peer. */ sealed trait SimpleClosingTxFee diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala index 779490fdab..b8f7b207aa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecs.scala @@ -22,6 +22,7 @@ import fr.acinq.eclair.wire.internal.channel.version1.ChannelCodecs1 import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2 import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3 import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4 +import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5 import grizzled.slf4j.Logging import scodec.Codec import scodec.codecs.{byte, discriminated} @@ -67,7 +68,8 @@ object ChannelCodecs extends Logging { * More info here: https://github.com/scodec/scodec/issues/122 */ val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(byte) - .typecase(4, ChannelCodecs4.channelDataCodec) + .typecase(5, ChannelCodecs5.channelDataCodec) + .typecase(4, ChannelCodecs4.channelDataCodec.decodeOnly) .typecase(3, ChannelCodecs3.channelDataCodec.decodeOnly) .typecase(2, ChannelCodecs2.channelDataCodec.decodeOnly) .typecase(1, ChannelCodecs1.channelDataCodec.decodeOnly) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 8c2a81a619..1ecc72f104 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -57,7 +57,7 @@ private[channel] object ChannelCodecs0 { // field and don't support additional features which is why all bits are set to 0. ) - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -71,7 +71,7 @@ private[channel] object ChannelCodecs0 { ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).map { case nodeId :: channelPath :: dustLimit :: maxHtlcValueInFlightMsat :: channelReserve :: htlcMinimum :: toSelfDelay :: maxAcceptedHtlcs :: isInitiator :: upfrontShutdownScript_opt :: walletStaticPaymentBasepoint :: features :: HNil => - LocalChannelParams(nodeId, channelPath, dustLimit, maxHtlcValueInFlightMsat, channelReserve, htlcMinimum, toSelfDelay, maxAcceptedHtlcs, isInitiator, isInitiator, upfrontShutdownScript_opt, walletStaticPaymentBasepoint, features) + ChannelTypes0.LocalParams(nodeId, channelPath, dustLimit, maxHtlcValueInFlightMsat, channelReserve, htlcMinimum, toSelfDelay, maxAcceptedHtlcs, isInitiator, isInitiator, upfrontShutdownScript_opt, walletStaticPaymentBasepoint, features) }.decodeOnly val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( @@ -129,7 +129,7 @@ private[channel] object ChannelCodecs0 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | varsizebinarydata)).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0)) @@ -420,7 +420,7 @@ private[channel] object ChannelCodecs0 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_10_Codec: Codec[DATA_NORMAL] = ( @@ -434,7 +434,7 @@ private[channel] object ChannelCodecs0 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_04_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala index 1121805230..568cafb65e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.wire.internal.channel.version0 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxId, TxOut} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, DeterministicWallet, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxId, TxOut} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec @@ -95,7 +95,7 @@ private[channel] object ChannelTypes0 { * the raw transaction. It provides more information for auditing but is not used for business logic, so we can safely * put dummy values in the migration. */ - def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), ByteVector.empty), tx, None) + def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil)), tx, None) case class HtlcTxAndSigs(txinfo: UnsignedHtlcTx, localSig: ByteVector64, remoteSig: ByteVector64) @@ -104,11 +104,11 @@ private[channel] object ChannelTypes0 { // Before version3, we stored fully signed local transactions (commit tx and htlc txs). It meant that someone gaining // access to the database could publish revoked commit txs, so we changed that to only store remote signatures. case class LocalCommit(index: Long, spec: CommitmentSpec, publishableTxs: PublishableTxs) { - def migrate(remoteFundingPubKey: PublicKey): channel.LocalCommit = { + def migrate(remoteFundingPubKey: PublicKey): (channel.LocalCommit, InputInfo) = { val remoteSig = extractRemoteSig(publishableTxs.commitTx, remoteFundingPubKey) val unsignedCommitTx = publishableTxs.commitTx.copy(tx = removeWitnesses(publishableTxs.commitTx.tx)) val htlcRemoteSigs = publishableTxs.htlcTxsAndSigs.map(_.remoteSig) - channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, unsignedCommitTx.input, remoteSig, htlcRemoteSigs) + (channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, remoteSig, htlcRemoteSigs), unsignedCommitTx.input) } private def extractRemoteSig(commitTx: CommitTx, remoteFundingPubKey: PublicKey): ChannelSpendSignature.IndividualSignature = { @@ -161,6 +161,31 @@ private[channel] object ChannelTypes0 { val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS } + case class LocalParams(nodeId: PublicKey, + fundingKeyPath: DeterministicWallet.KeyPath, + dustLimit: Satoshi, + maxHtlcValueInFlightMsat: UInt64, + initialRequestedChannelReserve_opt: Option[Satoshi], + htlcMinimum: MilliSatoshi, + toSelfDelay: CltvExpiryDelta, + maxAcceptedHtlcs: Int, + isChannelOpener: Boolean, + paysCommitTxFees: Boolean, + upfrontShutdownScript_opt: Option[ByteVector], + walletStaticPaymentBasepoint: Option[PublicKey], + initFeatures: Features[InitFeature]) { + def migrate(): channel.LocalChannelParams = channel.LocalChannelParams( + nodeId = nodeId, + fundingKeyPath = fundingKeyPath, + initialRequestedChannelReserve_opt = initialRequestedChannelReserve_opt, + isChannelOpener = isChannelOpener, + paysCommitTxFees = paysCommitTxFees, + upfrontShutdownScript_opt = upfrontShutdownScript_opt, + walletStaticPaymentBasepoint = walletStaticPaymentBasepoint, + initFeatures = initFeatures, + ) + } + case class RemoteParams(nodeId: PublicKey, dustLimit: Satoshi, maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi @@ -177,12 +202,7 @@ private[channel] object ChannelTypes0 { upfrontShutdownScript_opt: Option[ByteVector]) { def migrate(): channel.RemoteChannelParams = channel.RemoteChannelParams( nodeId = nodeId, - dustLimit = dustLimit, - maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat, initialRequestedChannelReserve_opt = requestedChannelReserve_opt, - htlcMinimum = htlcMinimum, - toRemoteDelay = toRemoteDelay, - maxAcceptedHtlcs = maxAcceptedHtlcs, revocationBasepoint = revocationBasepoint, paymentBasepoint = paymentBasepoint, delayedPaymentBasepoint = delayedPaymentBasepoint, @@ -195,7 +215,7 @@ private[channel] object ChannelTypes0 { case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) case class Commitments(channelVersion: ChannelVersion, - localParams: LocalChannelParams, remoteParams: RemoteParams, + localParams: LocalParams, remoteParams: RemoteParams, channelFlags: ChannelFlags, localCommit: LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, @@ -210,25 +230,34 @@ private[channel] object ChannelTypes0 { } else { ChannelConfig() } - val channelFeatures = if (channelVersion.hasAnchorOutputs) { - ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) - } else if (channelVersion.hasStaticRemotekey) { - ChannelFeatures(Features.StaticRemoteKey) + val commitmentFormat = if (channelVersion.hasAnchorOutputs) { + UnsafeLegacyAnchorOutputsCommitmentFormat } else { - ChannelFeatures() + DefaultCommitmentFormat } + val (localCommit1, commitInput) = localCommit.migrate(remoteParams.fundingPubKey) + val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) + val remoteCommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) val commitment = Commitment( fundingTxIndex = 0, firstRemoteCommitIndex = 0, + fundingInput = commitInput.outPoint, + fundingAmount = commitInput.txOut.amount, remoteFundingPubKey = remoteParams.fundingPubKey, // We set an empty funding tx, even if it may be confirmed already (and the channel fully operational). We could // have set a specific Unknown status, but it would have forced us to keep it forever. We will retrieve the // funding tx when the channel is instantiated, and update the status (possibly immediately if it was confirmed). - LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, - localCommit.migrate(remoteParams.fundingPubKey), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) + localFundingStatus = LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), + remoteFundingStatus = RemoteFundingStatus.Locked, + commitmentFormat = commitmentFormat, + localCommitParams = localCommitParams, + localCommit = localCommit1, + remoteCommitParams = remoteCommitParams, + remoteCommit = remoteCommit, + nextRemoteCommit_opt = remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), + ChannelParams(channelId, channelConfig, ChannelFeatures(), localParams.migrate(), remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), Seq(commitment), inactive = Nil, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index c25ef198ae..5dd5a8e200 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -44,7 +44,7 @@ private[channel] object ChannelCodecs1 { val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -56,7 +56,7 @@ private[channel] object ChannelCodecs1 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams].decodeOnly + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams].decodeOnly val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: @@ -101,7 +101,7 @@ private[channel] object ChannelCodecs1 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly private val missingHtlcExpiry: Codec[CltvExpiry] = provide(CltvExpiry(0)) @@ -277,7 +277,7 @@ private[channel] object ChannelCodecs1 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_23_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index 77621252f8..bae787cd20 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -53,7 +53,7 @@ private[channel] object ChannelCodecs2 { val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] - def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalChannelParams] = ( + def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: @@ -65,7 +65,7 @@ private[channel] object ChannelCodecs2 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: @@ -105,7 +105,7 @@ private[channel] object ChannelCodecs2 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly val outputInfoCodec: Codec[Long] = ( @@ -307,7 +307,7 @@ private[channel] object ChannelCodecs2 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index e7138b4750..9259953997 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.wire.internal.channel.version2.ChannelTypes2 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage -import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature} +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, InitFeature, MilliSatoshiLong} import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.codecs._ import scodec.{Attempt, Codec, Err} @@ -61,30 +61,30 @@ private[channel] object ChannelCodecs3 { }) /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ - val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( - (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated - (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + val channelFeaturesCodec: Codec[ChannelTypes3.ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelTypes3.ChannelFeatures(Features(b).activated.keySet.collect { case f: InitFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelTypes3.ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[LocalChannelParams] = ( + def localParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | lengthDelimited(bytes).map(Option(_)).decodeOnly) :: ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( + def remoteParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: @@ -118,7 +118,7 @@ private[channel] object ChannelCodecs3 { ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).map { - case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut, ByteVector.empty) + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) }.decodeOnly val outputInfoCodec: Codec[Long] = ( @@ -373,7 +373,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | provide(Option.empty[CloseStatus]))).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_NORMAL_07_Codec: Codec[DATA_NORMAL] = ( @@ -387,7 +387,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | closeStatusCompatCodec)).map { case commitments :: shortChannelId :: _ :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: HNil => val aliases = ShortIdAliases(localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None) - DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, SpliceStatus.NoSplice, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_NORMAL_09_Codec: Codec[DATA_NORMAL] = ( @@ -400,7 +400,7 @@ private[channel] object ChannelCodecs3 { ("closeStatus" | closeStatusCompatCodec) :: ("spliceStatus" | provide[SpliceStatus](SpliceStatus.NoSplice))).map { case commitments :: shortIds :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => - DATA_NORMAL(commitments, shortIds, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus, spliceStatus) + DATA_NORMAL(commitments, shortIds, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) }.decodeOnly val DATA_SHUTDOWN_03_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala index 3170b33aca..5c8ee95590 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala @@ -18,17 +18,33 @@ package fr.acinq.eclair.wire.internal.channel.version3 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64} -import fr.acinq.eclair.channel import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec -import fr.acinq.eclair.transactions.Transactions.{CommitTx, UnsignedHtlcTx} +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommitSig +import fr.acinq.eclair.{Features, InitFeature, PermanentChannelFeature, channel} private[channel] object ChannelTypes3 { + // We previously stored channel type features inside our channel features. + case class ChannelFeatures(features: Set[InitFeature]) { + val paysDirectlyToWallet: Boolean = features.contains(Features.StaticRemoteKey) && !features.contains(Features.AnchorOutputs) && !features.contains(Features.AnchorOutputsZeroFeeHtlcTx) + + /** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */ + val commitmentFormat: CommitmentFormat = if (features.contains(Features.AnchorOutputs)) { + UnsafeLegacyAnchorOutputsCommitmentFormat + } else if (features.contains(Features.AnchorOutputsZeroFeeHtlcTx)) { + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + } else { + DefaultCommitmentFormat + } + + def migrate(): channel.ChannelFeatures = channel.ChannelFeatures(features.collect { case f: PermanentChannelFeature => f }) + } + case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) case class HtlcTxAndRemoteSig(htlcTx: UnsignedHtlcTx, remoteSig: ByteVector64) @@ -38,18 +54,21 @@ private[channel] object ChannelTypes3 { // Before version 4, we stored the unsigned commit tx and htlc txs in our local commit. // We changed that to only store the remote signatures and re-compute transactions on-the-fly when force-closing. case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig: CommitTxAndRemoteSig, htlcTxsAndRemoteSigs: List[HtlcTxAndRemoteSig]) { - def migrate(): channel.LocalCommit = channel.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.commitTx.input, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + def migrate(): (channel.LocalCommit, InputInfo) = { + val localCommit = channel.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + (localCommit, commitTxAndRemoteSig.commitTx.input) + } } case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, commitTx: CommitTx, htlcTxs: List[UnsignedHtlcTx]) { - def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, commitTx.tx.txid, commitTx.input) + def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, commitTx.tx.txid) } // Before version4, we didn't support multiple active commitments, which were later introduced by dual funding and splicing. case class Commitments(channelId: ByteVector32, channelConfig: ChannelConfig, channelFeatures: ChannelFeatures, - localParams: LocalChannelParams, remoteParams: ChannelTypes0.RemoteParams, + localParams: ChannelTypes0.LocalParams, remoteParams: ChannelTypes0.RemoteParams, channelFlags: ChannelFlags, localCommit: ChannelTypes3.LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, @@ -59,15 +78,30 @@ private[channel] object ChannelTypes3 { localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, remotePerCommitmentSecrets: ShaChain) { - def migrate(): channel.Commitments = channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), - CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), - Seq(Commitment(fundingTxIndex = 0, firstRemoteCommitIndex = 0, remoteFundingPubKey = remoteParams.fundingPubKey, localFundingStatus, remoteFundingStatus, localCommit.migrate(), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), - inactive = Nil, - remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), - remotePerCommitmentSecrets, - originChannels - ) + def migrate(): channel.Commitments = { + val (localCommit1, commitInput) = localCommit.migrate() + val localCommitParams = CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toRemoteDelay) + val remoteCommitParams = CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) + val commitment = Commitment( + fundingTxIndex = 0, firstRemoteCommitIndex = 0, + commitInput.outPoint, commitInput.txOut.amount, + remoteParams.fundingPubKey, + localFundingStatus, remoteFundingStatus, + channelFeatures.commitmentFormat, + localCommitParams, localCommit1, + remoteCommitParams, remoteCommit, + remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) + ) + channel.Commitments( + ChannelParams(channelId, channelConfig, channelFeatures.migrate(), localParams.migrate(), remoteParams.migrate(), channelFlags), + CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), + active = Seq(commitment), + inactive = Nil, + remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), + remotePerCommitmentSecrets, + originChannels + ) + } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala index d9ec9d267e..75e9de1043 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -3,22 +3,21 @@ package fr.acinq.eclair.wire.internal.channel.version4 import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, SatoshiLong, ScriptWitness, Transaction, TxOut} -import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget} -import fr.acinq.eclair.channel.LocalFundingStatus._ +import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{FullySignedSharedTransaction, PartiallySignedSharedTransaction} -import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit -import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc} +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.internal.channel.version2.ChannelTypes2 import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature, RealShortChannelId, channel} +import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, InitFeature, MilliSatoshiLong, RealShortChannelId, channel} import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -48,17 +47,17 @@ private[channel] object ChannelCodecs4 { }) /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ - val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( - (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated - (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + val channelFeaturesCodec: Codec[ChannelTypes3.ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelTypes3.ChannelFeatures(Features(b).activated.keySet.collect { case f: InitFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelTypes3.ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention ) - def localParamsCodec(channelFeatures: ChannelFeatures): Codec[LocalChannelParams] = ( + def localParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes0.LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | keyPathCodec) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: @@ -66,13 +65,13 @@ private[channel] object ChannelCodecs4 { ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | optional(bool8, lengthDelimited(bytes))) :: ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: - ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + ("features" | combinedFeaturesCodec)).as[ChannelTypes0.LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[RemoteChannelParams] = ( + def remoteParamsCodec(channelFeatures: ChannelTypes3.ChannelFeatures): Codec[ChannelTypes4.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: - ("channelReserve" | conditional(!channelFeatures.hasFeature(Features.DualFunding), satoshi)) :: + ("channelReserve" | conditional(!channelFeatures.features.contains(Features.DualFunding), satoshi)) :: ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: @@ -81,7 +80,7 @@ private[channel] object ChannelCodecs4 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[RemoteChannelParams] + ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[ChannelTypes4.RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) @@ -113,11 +112,12 @@ private[channel] object ChannelCodecs4 { val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) - // all v4-encoded channels use segwit inputs, support for taproot inputs will be added later in v5 codecs val inputInfoCodec: Codec[InputInfo] = ( ("outPoint" | outPointCodec) :: ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo] + ("redeemScript" | lengthDelimited(bytes))).map { + case outpoint :: txOut :: _ :: HNil => InputInfo(outpoint, txOut) + }.decodeOnly val outputInfoCodec: Codec[Long] = ( ("index" | uint32) :: @@ -277,17 +277,17 @@ private[channel] object ChannelCodecs4 { val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec) - private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( + private val multisig2of2InputCodec: Codec[ChannelTypes4.Multisig2of2Input] = ( ("info" | inputInfoCodec) :: ("fundingTxIndex" | uint32) :: - ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] + ("remoteFundingPubkey" | publicKey)).as[ChannelTypes4.Multisig2of2Input] - private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) + private val sharedFundingInputCodec: Codec[ChannelTypes4.Multisig2of2Input] = discriminated[ChannelTypes4.Multisig2of2Input].by(uint16) .typecase(0x01, multisig2of2InputCodec) private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] - private val fundingParamsCodec: Codec[InteractiveTxBuilder.InteractiveTxParams] = ( + private val fundingParamsCodec: Codec[ChannelTypes4.InteractiveTxParams] = ( ("channelId" | bytes32) :: ("isInitiator" | bool8) :: ("localContribution" | satoshiSigned) :: @@ -298,7 +298,7 @@ private[channel] object ChannelCodecs4 { ("lockTime" | uint32) :: ("dustLimit" | satoshi) :: ("targetFeerate" | feeratePerKw) :: - ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[InteractiveTxBuilder.InteractiveTxParams] + ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[ChannelTypes4.InteractiveTxParams] // This codec was used by a first prototype version of splicing that only worked without HTLCs. private val sharedInteractiveTxInputWithoutHtlcsCodec: Codec[InteractiveTxBuilder.Input.Shared] = ( @@ -423,26 +423,26 @@ private[channel] object ChannelCodecs4 { ("amount" | satoshi) :: ("fees" | liquidityFeesCodec)).as[LiquidityAds.PurchaseBasicInfo] - private val dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec: Codec[DualFundedUnconfirmedFundingTx] = ( + private val dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec: Codec[ChannelTypes4.DualFundedUnconfirmedFundingTx] = ( ("sharedTx" | signedSharedTransactionCodec) :: ("createdAt" | blockHeight) :: ("fundingParams" | fundingParamsCodec) :: - ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[DualFundedUnconfirmedFundingTx].xmap( + ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[ChannelTypes4.DualFundedUnconfirmedFundingTx].xmap( dfu => fillSharedInputScript(dfu), dfu => dfu ) - private val dualFundedUnconfirmedFundingTxCodec: Codec[DualFundedUnconfirmedFundingTx] = ( + private val dualFundedUnconfirmedFundingTxCodec: Codec[ChannelTypes4.DualFundedUnconfirmedFundingTx] = ( ("sharedTx" | signedSharedTransactionCodec) :: ("createdAt" | blockHeight) :: ("fundingParams" | fundingParamsCodec) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[DualFundedUnconfirmedFundingTx].xmap( + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.DualFundedUnconfirmedFundingTx].xmap( dfu => fillSharedInputScript(dfu), dfu => dfu ) // When decoding interactive-tx from older codecs, we fill the shared input publicKeyScript if necessary. - private def fillSharedInputScript(dfu: DualFundedUnconfirmedFundingTx): DualFundedUnconfirmedFundingTx = { + private def fillSharedInputScript(dfu: ChannelTypes4.DualFundedUnconfirmedFundingTx): ChannelTypes4.DualFundedUnconfirmedFundingTx = { (dfu.sharedTx.tx.sharedInput_opt, dfu.fundingParams.sharedInput_opt) match { case (Some(sharedTxInput), Some(sharedFundingParamsInput)) if sharedTxInput.publicKeyScript.isEmpty => val sharedTxInput1 = sharedTxInput.copy(publicKeyScript = sharedFundingParamsInput.info.txOut.publicKeyScript) @@ -456,30 +456,30 @@ private[channel] object ChannelCodecs4 { } } - val fundingTxStatusCodec: Codec[LocalFundingStatus] = discriminated[LocalFundingStatus].by(uint8) - .typecase(0x0a, (txCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ConfirmedFundingTx]) - .typecase(0x01, optional(bool8, txCodec).as[SingleFundedUnconfirmedFundingTx]) + val fundingTxStatusCodec: Codec[ChannelTypes4.LocalFundingStatus] = discriminated[ChannelTypes4.LocalFundingStatus].by(uint8) + .typecase(0x0a, (txCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ConfirmedFundingTx]) + .typecase(0x01, optional(bool8, txCodec).as[ChannelTypes4.SingleFundedUnconfirmedFundingTx]) .typecase(0x07, dualFundedUnconfirmedFundingTxCodec) - .typecase(0x08, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ZeroconfPublishedFundingTx]) - .typecase(0x09, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ConfirmedFundingTx]) + .typecase(0x08, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x09, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[ChannelTypes4.ConfirmedFundingTx]) .typecase(0x02, dualFundedUnconfirmedFundingTxWithoutLiquidityPurchaseCodec) - .typecase(0x05, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ZeroconfPublishedFundingTx]) - .typecase(0x06, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ConfirmedFundingTx]) - .typecase(0x03, (txCodec :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ZeroconfPublishedFundingTx]) - .typecase(0x04, (txCodec :: provide(RealShortChannelId(0)) :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ConfirmedFundingTx]) + .typecase(0x05, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x06, (txCodec :: provide(RealShortChannelId(0)) :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ConfirmedFundingTx]) + .typecase(0x03, (txCodec :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ZeroconfPublishedFundingTx]) + .typecase(0x04, (txCodec :: provide(RealShortChannelId(0)) :: provide(Option.empty[TxSignatures]) :: provide(Option.empty[LiquidityAds.PurchaseBasicInfo])).as[ChannelTypes4.ConfirmedFundingTx]) val remoteFundingStatusCodec: Codec[RemoteFundingStatus] = discriminated[RemoteFundingStatus].by(uint8) .typecase(0x01, provide(RemoteFundingStatus.NotLocked)) .typecase(0x02, provide(RemoteFundingStatus.Locked)) - val paramsCodec: Codec[ChannelParams] = ( + val paramsCodec: Codec[ChannelTypes4.ChannelParams] = ( ("channelId" | bytes32) :: ("channelConfig" | channelConfigCodec) :: (("channelFeatures" | channelFeaturesCodec) >>:~ { channelFeatures => ("localParams" | localParamsCodec(channelFeatures)) :: ("remoteParams" | remoteParamsCodec(channelFeatures)) :: ("channelFlags" | channelflags) - })).as[ChannelParams] + })).as[ChannelTypes4.ChannelParams] val waitForRevCodec: Codec[WaitForRev] = ("sentAfterLocalCommitIndex" | uint64overflow).as[WaitForRev] @@ -489,19 +489,22 @@ private[channel] object ChannelCodecs4 { ("localNextHtlcId" | uint64overflow) :: ("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges] - private def localCommitWithTxsCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + private def localCommitWithTxsCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[ChannelTypes4.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTxAndRemoteSig" | commitTxAndRemoteSigCodec) :: - ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[ChannelTypes3.LocalCommit].decodeOnly.map[LocalCommit](_.migrate()).decodeOnly + ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).map { + case index :: spec :: commitTxAndRemoteSig :: htlcTxsAndRemoteSigs :: HNil => + ChannelTypes4.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.commitTx.input, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + }.decodeOnly - private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[ChannelTypes4.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("txId" | txId) :: ("input" | inputInfoCodec) :: ("remoteSig" | channelSpendSignatureCodec) :: - ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[LocalCommit] + ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[ChannelTypes4.LocalCommit] private def remoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -513,7 +516,7 @@ private[channel] object ChannelCodecs4 { ("sig" | lengthDelimited(commitSigCodec)) :: ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit] - private def commitmentCodecWithoutFirstRemoteCommitIndex(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodecWithoutFirstRemoteCommitIndex(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | provide(0L)) :: ("fundingPubKey" | publicKey) :: @@ -521,9 +524,9 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitWithTxsCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] - private def commitmentCodecWithLocalTxs(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodecWithLocalTxs(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | uint64overflow) :: ("fundingPubKey" | publicKey) :: @@ -531,9 +534,9 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitWithTxsCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] - private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[ChannelTypes4.Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | uint64overflow) :: ("fundingPubKey" | publicKey) :: @@ -541,7 +544,7 @@ private[channel] object ChannelCodecs4 { ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: - ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[ChannelTypes4.Commitment] /** * When multiple commitments are active, htlcs are shared between all of these commitments. @@ -549,49 +552,26 @@ private[channel] object ChannelCodecs4 { * The resulting htlc set size is thus between 1,4 MB and 64 MB, which can be pretty large. * To avoid writing that htlc set multiple times to disk, we encode it separately. */ - case class EncodedCommitments(channelParams: ChannelParams, + case class EncodedCommitments(channelParams: ChannelTypes4.ChannelParams, changes: CommitmentChanges, // The direction we use is from our local point of view. htlcs: Set[DirectedHtlc], - active: List[Commitment], - inactive: List[Commitment], + active: List[ChannelTypes4.Commitment], + inactive: List[ChannelTypes4.Commitment], remoteNextCommitInfo: Either[WaitForRev, PublicKey], remotePerCommitmentSecrets: ShaChain, originChannels: Map[Long, Origin], remoteChannelData_opt: Option[ByteVector]) { def toCommitments: Commitments = { Commitments( - channelParams = channelParams, + channelParams = channelParams.migrate(), changes = changes, - active = active, - inactive = inactive, - remoteNextCommitInfo, - remotePerCommitmentSecrets, - originChannels, - remoteChannelData_opt - ) - } - } - - object EncodedCommitments { - def apply(commitments: Commitments): EncodedCommitments = { - // The direction we use is from our local point of view: we use sets, which deduplicates htlcs that are in both - // local and remote commitments. - // All active commitments have the same htlc set, but each inactive commitment may have a distinct htlc set - val commitmentsSet = (commitments.active.head +: commitments.inactive).toSet - val htlcs = commitmentsSet.flatMap(_.localCommit.spec.htlcs) ++ - commitmentsSet.flatMap(_.remoteCommit.spec.htlcs.map(_.opposite)) ++ - commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.commit.spec.htlcs.map(_.opposite))) - EncodedCommitments( - channelParams = commitments.channelParams, - changes = commitments.changes, - htlcs = htlcs, - active = commitments.active.toList, - inactive = commitments.inactive.toList, - remoteNextCommitInfo = commitments.remoteNextCommitInfo, - remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets, - originChannels = commitments.originChannels, - remoteChannelData_opt = commitments.remoteChannelData_opt + active = active.map(_.migrate(channelParams)), + inactive = inactive.map(_.migrate(channelParams)), + remoteNextCommitInfo = remoteNextCommitInfo, + remotePerCommitmentSecrets = remotePerCommitmentSecrets, + originChannels = originChannels, + remoteChannelData_opt = remoteChannelData_opt ) } } @@ -606,10 +586,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val commitmentsCodecWithLocalTxs: Codec[Commitments] = ( ("params" | paramsCodec) :: @@ -621,10 +598,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val commitmentsCodec: Codec[Commitments] = ( ("params" | paramsCodec) :: @@ -636,10 +610,7 @@ private[channel] object ChannelCodecs4 { ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( - encoded => encoded.toCommitments, - commitments => EncodedCommitments(commitments) - ) + })).as[EncodedCommitments].map(_.toCommitments).decodeOnly val versionedCommitmentsCodec: Codec[Commitments] = discriminated[Commitments].by(uint8) .typecase(0x02, commitmentsCodec) @@ -724,57 +695,59 @@ private[channel] object ChannelCodecs4 { // We don't bother removing the duplication across HTLCs: this is a short-lived state during which the channel // cannot be used for payments. - private val (interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec, interactiveTxWaitingForSigsWithTxsCodec, interactiveTxWaitingForSigsCodec): (Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs]) = { - val unsignedLocalCommitWithTxsCodec: Codec[UnsignedLocalCommit] = ( + private val (interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec, interactiveTxWaitingForSigsWithTxsCodec, interactiveTxWaitingForSigsCodec): (Codec[ChannelTypes4.WaitingForSigs], Codec[ChannelTypes4.WaitingForSigs], Codec[ChannelTypes4.WaitingForSigs]) = { + val unsignedLocalCommitWithTxsCodec: Codec[ChannelTypes4.UnsignedLocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTx" | commitTxCodec) :: - ("htlcTxs" | listOfN(uint16, htlcTxCodec))).as[ChannelTypes3.UnsignedLocalCommit].decodeOnly.map[UnsignedLocalCommit](_.migrate()).decodeOnly + ("htlcTxs" | listOfN(uint16, htlcTxCodec))).map { + case index :: spec :: commitTx :: _ :: HNil => ChannelTypes4.UnsignedLocalCommit(index, spec, commitTx.tx.txid, commitTx.input) + }.decodeOnly - val unsignedLocalCommitCodec: Codec[UnsignedLocalCommit] = ( + val unsignedLocalCommitCodec: Codec[ChannelTypes4.UnsignedLocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("txId" | txId) :: - ("input" | inputInfoCodec)).as[UnsignedLocalCommit] + ("input" | inputInfoCodec)).as[ChannelTypes4.UnsignedLocalCommit] - val waitingForSigsWithoutLiquidityPurchaseCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsWithoutLiquidityPurchaseCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitWithTxsCodec, localCommitWithTxsCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))).as[ChannelTypes4.WaitingForSigs] - val waitingForSigsWithTxsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsWithTxsCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitWithTxsCodec, localCommitWithTxsCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.WaitingForSigs] - val waitingForSigsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + val waitingForSigsCodec: Codec[ChannelTypes4.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: - ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[ChannelTypes4.WaitingForSigs] (waitingForSigsWithoutLiquidityPurchaseCodec, waitingForSigsWithTxsCodec, waitingForSigsCodec) } - val dualFundingStatusCodec: Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) + def dualFundingStatusCodec(commitments: Commitments): Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) .\(0x01) { case status: DualFundingStatus if !status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs] => DualFundingStatus.WaitingForConfirmations }(provide(DualFundingStatus.WaitingForConfirmations)) - .\(0x04) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[DualFundingStatus.RbfWaitingForSigs]) - .\(0x03) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.as[DualFundingStatus.RbfWaitingForSigs]) - .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x04) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x03) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) + .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.map(_.migrate(commitments)).decodeOnly.as[DualFundingStatus.RbfWaitingForSigs]) - val spliceStatusCodec: Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) + def spliceStatusCodec(commitments: Commitments): Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) .\(0x01) { case status: SpliceStatus if !status.isInstanceOf[SpliceStatus.SpliceWaitingForSigs] => SpliceStatus.NoSplice }(provide(SpliceStatus.NoSplice)) - .\(0x04) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) - .\(0x03) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) - .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x04) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x03) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithTxsCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec.map(_.migrate(commitments)).decodeOnly.as[channel.SpliceStatus.SpliceWaitingForSigs]) private val shortids: Codec[ChannelTypes4.ShortIds] = ( ("real_opt" | optional(bool8, realshortchannelid)) :: @@ -812,7 +785,10 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_13_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( ("channelParams" | paramsCodec) :: @@ -820,7 +796,10 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsWithTxsCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_1c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( ("channelParams" | paramsCodec) :: @@ -828,25 +807,30 @@ private[channel] object ChannelCodecs4 { ("localPushAmount" | millisatoshi) :: ("remotePushAmount" | millisatoshi) :: ("status" | interactiveTxWaitingForSigsCodec) :: - ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).map { + case channelParams :: secondRemotePerCommitmentPoint :: localPushAmount :: remotePushAmount :: status :: _ :: HNil => + DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams.migrate(), secondRemotePerCommitmentPoint, localPushAmount, remotePushAmount, status.migrate(channelParams)) + }.decodeOnly val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_02_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: + ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) >>:~ { commitments => ("localPushAmount" | millisatoshi) :: - ("remotePushAmount" | millisatoshi) :: - ("waitingSince" | blockHeight) :: - ("lastChecked" | blockHeight) :: - ("status" | dualFundingStatusCodec) :: - ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec(commitments)) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) + }).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_0c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("localPushAmount" | millisatoshi) :: - ("remotePushAmount" | millisatoshi) :: - ("waitingSince" | blockHeight) :: - ("lastChecked" | blockHeight) :: - ("status" | dualFundingStatusCodec) :: - ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec(commitments)) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) + }).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val DATA_WAIT_FOR_DUAL_FUNDING_READY_03_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: @@ -861,45 +845,55 @@ private[channel] object ChannelCodecs4 { ("aliases" | aliases)).as[DATA_WAIT_FOR_DUAL_FUNDING_READY] val DATA_NORMAL_04_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: + ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) >>:~ { commitments => ("shortids" | shortids) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly val DATA_NORMAL_0e_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("shortids" | shortids) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closingFeerates" | optional(bool8, closingFeeratesCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).as[ChannelTypes4.DATA_NORMAL_0e].map(_.migrate()).decodeOnly val DATA_NORMAL_14_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("aliases" | aliases) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - // If there are closing fees defined we consider ourselves to be the closing initiator. - ("closingFeerates" | optional(bool8, closingFeeratesCodec).map[Option[CloseStatus]](feerates_opt => Some(CloseStatus.Initiator(feerates_opt))).decodeOnly) :: - ("spliceStatus" | spliceStatusCodec)).as[DATA_NORMAL] + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + // If there are closing fees defined we consider ourselves to be the closing initiator. + ("closingFeerates" | optional(bool8, closingFeeratesCodec).map[Option[CloseStatus]](feerates_opt => Some(CloseStatus.Initiator(feerates_opt))).decodeOnly) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).map { + case commitments :: aliases :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) + }.decodeOnly val DATA_NORMAL_18_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | versionedCommitmentsCodec) :: + ("commitments" | versionedCommitmentsCodec) >>:~ { commitments => ("aliases" | aliases) :: - ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: - ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closeStatus" | optional(bool8, closeStatusCodec)) :: - ("spliceStatus" | spliceStatusCodec)).as[DATA_NORMAL] + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closeStatus" | optional(bool8, closeStatusCodec)) :: + ("spliceStatus" | spliceStatusCodec(commitments)) + }).map { + case commitments :: aliases :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closeStatus :: spliceStatus :: HNil => + DATA_NORMAL(commitments, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus) + }.decodeOnly val DATA_SHUTDOWN_05_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: @@ -966,7 +960,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_11_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -978,7 +973,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_1a_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -990,7 +986,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_1a))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_1a)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_CLOSING_1b_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -1002,7 +999,8 @@ private[channel] object ChannelCodecs4 { ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec)) :: + ("maxClosingFeerate" | provide(Option.empty[FeeratePerKw]))).as[DATA_CLOSING] val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_08_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( ("commitments" | commitmentsCodecWithoutFirstRemoteCommitIndex) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala index 55973eef7a..c69621e24b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelTypes4.scala @@ -16,68 +16,246 @@ package fr.acinq.eclair.wire.internal.channel.version4 -import fr.acinq.eclair.channel.LocalFundingStatus.ConfirmedFundingTx -import fr.acinq.eclair.channel._ -import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, Shutdown} -import fr.acinq.eclair.{Alias, RealShortChannelId} +import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transaction, TxId, TxOut} +import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.SignedSharedTransaction +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo} +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 +import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 +import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, LiquidityAds, Shutdown, TxSignatures} +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, RealShortChannelId, UInt64, channel} +import scodec.bits.ByteVector private[channel] object ChannelTypes4 { // We moved the real scid inside each commitment object when adding DATA_NORMAL_14_Codec. case class ShortIds(real_opt: Option[RealShortChannelId], localAlias: Alias, remoteAlias_opt: Option[Alias]) + // We split remote params into separate channel params and commitment params when moving to channel codecs v5. + case class RemoteParams(nodeId: PublicKey, + dustLimit: Satoshi, + maxHtlcValueInFlightMsat: UInt64, + initialRequestedChannelReserve_opt: Option[Satoshi], + htlcMinimum: MilliSatoshi, + toSelfDelay: CltvExpiryDelta, + maxAcceptedHtlcs: Int, + revocationBasepoint: PublicKey, + paymentBasepoint: PublicKey, + delayedPaymentBasepoint: PublicKey, + htlcBasepoint: PublicKey, + initFeatures: Features[InitFeature], + upfrontShutdownScript_opt: Option[ByteVector]) { + def migrate(): channel.RemoteChannelParams = channel.RemoteChannelParams( + nodeId = nodeId, + initialRequestedChannelReserve_opt = initialRequestedChannelReserve_opt, + revocationBasepoint = revocationBasepoint, + paymentBasepoint = paymentBasepoint, + delayedPaymentBasepoint = delayedPaymentBasepoint, + htlcBasepoint = htlcBasepoint, + initFeatures = initFeatures, + upfrontShutdownScript_opt = upfrontShutdownScript_opt, + ) + } + + case class ChannelParams(channelId: ByteVector32, + channelConfig: channel.ChannelConfig, + channelFeatures: ChannelTypes3.ChannelFeatures, + localParams: ChannelTypes0.LocalParams, remoteParams: RemoteParams, + channelFlags: channel.ChannelFlags) { + def migrate(): channel.ChannelParams = channel.ChannelParams(channelId, channelConfig, channelFeatures.migrate(), localParams.migrate(), remoteParams.migrate(), channelFlags) + + def localCommitParams(): channel.CommitParams = channel.CommitParams(localParams.dustLimit, localParams.htlcMinimum, localParams.maxHtlcValueInFlightMsat, localParams.maxAcceptedHtlcs, remoteParams.toSelfDelay) + + def remoteCommitParams(): channel.CommitParams = channel.CommitParams(remoteParams.dustLimit, remoteParams.htlcMinimum, remoteParams.maxHtlcValueInFlightMsat, remoteParams.maxAcceptedHtlcs, localParams.toSelfDelay) + } + + case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) { + def migrate(commitmentFormat: CommitmentFormat): InteractiveTxBuilder.SharedFundingInput = InteractiveTxBuilder.SharedFundingInput(info, fundingTxIndex, remoteFundingPubkey, commitmentFormat) + } + + // We added the commitment format when moving to channel codecs v5. + case class InteractiveTxParams(channelId: ByteVector32, + isInitiator: Boolean, + localContribution: Satoshi, + remoteContribution: Satoshi, + sharedInput_opt: Option[Multisig2of2Input], + remoteFundingPubKey: PublicKey, + localOutputs: List[TxOut], + lockTime: Long, + dustLimit: Satoshi, + targetFeerate: FeeratePerKw, + requireConfirmedInputs: InteractiveTxBuilder.RequireConfirmedInputs) { + def migrate(commitmentFormat: CommitmentFormat): InteractiveTxBuilder.InteractiveTxParams = InteractiveTxBuilder.InteractiveTxParams( + channelId = channelId, + isInitiator = isInitiator, + localContribution = localContribution, + remoteContribution = remoteContribution, + sharedInput_opt = sharedInput_opt.map(_.migrate(commitmentFormat)), + remoteFundingPubKey = remoteFundingPubKey, + localOutputs = localOutputs, + commitmentFormat = commitmentFormat, + lockTime = lockTime, + dustLimit = dustLimit, + targetFeerate = targetFeerate, + requireConfirmedInputs = requireConfirmedInputs, + ) + } + + // We removed the signed transaction when confirmed to save space when moving to channel codecs v5. + sealed trait LocalFundingStatus { + def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus + } + + case class SingleFundedUnconfirmedFundingTx(signedTx_opt: Option[Transaction]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx = channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx(signedTx_opt) + } + + case class DualFundedUnconfirmedFundingTx(sharedTx: SignedSharedTransaction, createdAt: BlockHeight, fundingParams: InteractiveTxParams, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx = channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx(sharedTx, createdAt, fundingParams.migrate(commitmentFormat), liquidityPurchase_opt) + } + + case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.ZeroconfPublishedFundingTx = channel.LocalFundingStatus.ZeroconfPublishedFundingTx(tx, localSigs_opt, liquidityPurchase_opt) + } + + case class ConfirmedFundingTx(tx: Transaction, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus { + override def migrate(commitmentFormat: CommitmentFormat): channel.LocalFundingStatus.ConfirmedFundingTx = { + val txOut = tx.txOut(shortChannelId.outputIndex) + channel.LocalFundingStatus.ConfirmedFundingTx(txOut, shortChannelId, localSigs_opt, liquidityPurchase_opt) + } + } + + // We move the input to the Commitment class instead of the LocalCommit when moving to channel codecs v5. + case class LocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo, remoteSig: channel.ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) { + def migrate(): channel.LocalCommit = channel.LocalCommit(index, spec, txId, remoteSig, htlcRemoteSigs) + } + + case class Commitment(fundingTxIndex: Long, + firstRemoteCommitIndex: Long, + remoteFundingPubKey: PublicKey, + localFundingStatus: LocalFundingStatus, remoteFundingStatus: channel.RemoteFundingStatus, + localCommit: LocalCommit, remoteCommit: channel.RemoteCommit, nextRemoteCommit_opt: Option[channel.NextRemoteCommit]) { + def migrate(params: ChannelParams): channel.Commitment = channel.Commitment( + fundingTxIndex = fundingTxIndex, + firstRemoteCommitIndex = firstRemoteCommitIndex, + fundingInput = localCommit.input.outPoint, + fundingAmount = localCommit.input.txOut.amount, + remoteFundingPubKey = remoteFundingPubKey, + localFundingStatus = localFundingStatus.migrate(params.channelFeatures.commitmentFormat), + remoteFundingStatus = remoteFundingStatus, + commitmentFormat = params.channelFeatures.commitmentFormat, + localCommitParams = params.localCommitParams(), + localCommit = localCommit.migrate(), + remoteCommitParams = params.remoteCommitParams(), + remoteCommit = remoteCommit, + nextRemoteCommit_opt = nextRemoteCommit_opt + ) + } + + case class Commitments(params: ChannelParams, + changes: channel.CommitmentChanges, + active: Seq[Commitment], + inactive: Seq[Commitment] = Nil, + remoteNextCommitInfo: Either[channel.WaitForRev, PublicKey], + remotePerCommitmentSecrets: ShaChain, + originChannels: Map[Long, channel.Origin], + remoteChannelData_opt: Option[ByteVector]) + + case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, txId: TxId, input: InputInfo) { + def migrate(): InteractiveTxSigningSession.UnsignedLocalCommit = InteractiveTxSigningSession.UnsignedLocalCommit(index, spec, txId) + } + + case class WaitingForSigs(fundingParams: InteractiveTxParams, + fundingTxIndex: Long, + fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction, + localCommit: Either[UnsignedLocalCommit, LocalCommit], + remoteCommit: channel.RemoteCommit, + liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) { + def migrate(params: ChannelParams): InteractiveTxSigningSession.WaitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + fundingParams = fundingParams.migrate(params.channelFeatures.commitmentFormat), + fundingTxIndex = fundingTxIndex, + fundingTx = fundingTx, + localCommitParams = params.localCommitParams(), + localCommit = localCommit match { + case Left(unsigned) => Left(unsigned.migrate()) + case Right(signed) => Right(signed.migrate()) + }, + remoteCommitParams = params.remoteCommitParams(), + remoteCommit = remoteCommit, + liquidityPurchase_opt = liquidityPurchase_opt, + ) + + def migrate(commitments: channel.Commitments): InteractiveTxSigningSession.WaitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + fundingParams = fundingParams.migrate(commitments.latest.commitmentFormat), + fundingTxIndex = fundingTxIndex, + fundingTx = fundingTx, + localCommitParams = commitments.latest.localCommitParams, + localCommit = localCommit match { + case Left(unsigned) => Left(unsigned.migrate()) + case Right(signed) => Right(signed.migrate()) + }, + remoteCommitParams = commitments.latest.remoteCommitParams, + remoteCommit = remoteCommit, + liquidityPurchase_opt = liquidityPurchase_opt, + ) + } + // We moved the channel_announcement inside each commitment object when adding DATA_NORMAL_14_Codec. - case class DATA_NORMAL_0e(commitments: Commitments, + case class DATA_NORMAL_0e(commitments: channel.Commitments, shortIds: ShortIds, channelAnnouncement: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], - closingFeerates: Option[ClosingFeerates], - spliceStatus: SpliceStatus) { - def migrate(): DATA_NORMAL = { + closingFeerates: Option[channel.ClosingFeerates], + spliceStatus: channel.SpliceStatus) { + def migrate(): channel.DATA_NORMAL = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) val closeStatus_opt = if (localShutdown.nonEmpty) { - Some(CloseStatus.Initiator(closingFeerates)) + Some(channel.CloseStatus.Initiator(closingFeerates)) } else if (remoteShutdown.nonEmpty) { - Some(CloseStatus.NonInitiator(closingFeerates)) + Some(channel.CloseStatus.NonInitiator(closingFeerates)) } else None - DATA_NORMAL(commitments1, aliases, channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closeStatus_opt, spliceStatus) + channel.DATA_NORMAL(commitments1, aliases, channelAnnouncement, channelUpdate, spliceStatus, localShutdown, remoteShutdown, closeStatus_opt) } } - case class DATA_WAIT_FOR_CHANNEL_READY_0b(commitments: Commitments, shortIds: ShortIds) { - def migrate(): DATA_WAIT_FOR_CHANNEL_READY = { + case class DATA_WAIT_FOR_CHANNEL_READY_0b(commitments: channel.Commitments, shortIds: ShortIds) { + def migrate(): channel.DATA_WAIT_FOR_CHANNEL_READY = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) - DATA_WAIT_FOR_CHANNEL_READY(commitments1, aliases) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + channel.DATA_WAIT_FOR_CHANNEL_READY(commitments1, aliases) } } - case class DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(commitments: Commitments, shortIds: ShortIds) { - def migrate(): DATA_WAIT_FOR_DUAL_FUNDING_READY = { + case class DATA_WAIT_FOR_DUAL_FUNDING_READY_0d(commitments: channel.Commitments, shortIds: ShortIds) { + def migrate(): channel.DATA_WAIT_FOR_DUAL_FUNDING_READY = { val commitments1 = commitments.copy( active = commitments.active.map(c => setScidIfMatches(c, shortIds)), inactive = commitments.inactive.map(c => setScidIfMatches(c, shortIds)), ) - val aliases = ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) - DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, aliases) + val aliases = channel.ShortIdAliases(shortIds.localAlias, shortIds.remoteAlias_opt) + channel.DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, aliases) } } - private def setScidIfMatches(c: Commitment, shortIds: ShortIds): Commitment = { + private def setScidIfMatches(c: channel.Commitment, shortIds: ShortIds): channel.Commitment = { c.localFundingStatus match { // We didn't support splicing on public channels in this version: the scid (if available) is for the initial // funding transaction. For private channels we don't care about the real scid, it will be set correctly after // the next splice. - case f: ConfirmedFundingTx if c.fundingTxIndex == 0 => + case f: channel.LocalFundingStatus.ConfirmedFundingTx if c.fundingTxIndex == 0 => val scid = shortIds.real_opt.getOrElse(f.shortChannelId) c.copy(localFundingStatus = f.copy(shortChannelId = scid)) case _ => c diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala new file mode 100644 index 0000000000..6c7f7fa62b --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala @@ -0,0 +1,511 @@ +/* + * Copyright 2025 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.Transactions.{ClosingTx, ClosingTxs, InputInfo} +import fr.acinq.eclair.transactions._ +import fr.acinq.eclair.wire.protocol.CommonCodecs._ +import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ +import fr.acinq.eclair.wire.protocol.{LiquidityAds, UpdateAddHtlc, UpdateMessage} +import fr.acinq.eclair.{FeatureSupport, Features, PermanentChannelFeature} +import scodec.bits.{BitVector, ByteVector} +import scodec.codecs._ +import scodec.{Attempt, Codec} + +/** + * Created by t-bast on 18/06/2025. + */ + +private[channel] object ChannelCodecs5 { + + private[version5] object Codecs { + private val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] + private val outPointCodec: Codec[OutPoint] = lengthDelimited(bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d))) + private val txOutCodec: Codec[TxOut] = lengthDelimited(bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d))) + private val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) + private val scriptWitnessCodec: Codec[ScriptWitness] = listOfN(uint16, lengthDelimited(bytes)).xmap(s => ScriptWitness(s.toSeq), w => w.stack.toList) + + private val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo] + + private val channelSpendSignatureCodec: Codec[ChannelSpendSignature] = discriminated[ChannelSpendSignature].by(uint8) + .typecase(0x01, bytes64.as[ChannelSpendSignature.IndividualSignature]) + .typecase(0x02, (("partialSig" | bytes32) :: ("nonce" | publicNonce)).as[ChannelSpendSignature.PartialSignatureWithNonce]) + + private def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) + + private def mapCodec[K, V](keyCodec: Codec[K], valueCodec: Codec[V]): Codec[Map[K, V]] = listOfN(uint16, keyCodec ~ valueCodec).xmap(_.toMap, _.toList) + + private val channelConfigCodec: Codec[ChannelConfig] = lengthDelimited(bytes).xmap(b => { + val activated: Set[ChannelConfigOption] = b.bits.toIndexedSeq.reverse.zipWithIndex.collect { + case (true, 0) => ChannelConfig.FundingPubKeyBasedChannelKeyPath + }.toSet + ChannelConfig(activated) + }, cfg => { + val indices = cfg.options.map(_.supportBit) + if (indices.isEmpty) { + ByteVector.empty + } else { + // NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits. + var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits + indices.foreach(i => buffer = buffer.set(i)) + buffer.reverse.bytes + } + }) + + /** We use the same encoding as init features, even if we don't need the distinction between mandatory and optional */ + private val channelFeaturesCodec: Codec[ChannelFeatures] = lengthDelimited(bytes).xmap( + (b: ByteVector) => ChannelFeatures(Features(b).activated.keySet.collect { case f: PermanentChannelFeature => f }), // we make no difference between mandatory/optional, both are considered activated + (cf: ChannelFeatures) => Features(cf.features.map(f => f -> FeatureSupport.Mandatory).toMap).toByteVector // we encode features as mandatory, by convention + ) + + private val commitmentFormatCodec: Codec[Transactions.CommitmentFormat] = discriminated[Transactions.CommitmentFormat].by(uint8) + .typecase(0x00, provide(Transactions.DefaultCommitmentFormat)) + .typecase(0x01, provide(Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + .typecase(0x02, provide(Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) + .typecase(0x03, provide(Transactions.LegacySimpleTaprootChannelCommitmentFormat)) + .typecase(0x04, provide(Transactions.ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)) + + private val localChannelParamsCodec: Codec[LocalChannelParams] = ( + ("nodeId" | publicKey) :: + ("channelPath" | keyPathCodec) :: + ("channelReserve" | optional(bool8, satoshi)) :: + // We pad to keep codecs byte-aligned. + ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: + ("upfrontShutdownScript_opt" | optional(bool8, lengthDelimited(bytes))) :: + ("walletStaticPaymentBasepoint" | optional(bool8, publicKey)) :: + ("features" | combinedFeaturesCodec)).as[LocalChannelParams] + + val remoteChannelParamsCodec: Codec[RemoteChannelParams] = ( + ("nodeId" | publicKey) :: + ("channelReserve" | optional(bool8, satoshi)) :: + ("revocationBasepoint" | publicKey) :: + ("paymentBasepoint" | publicKey) :: + ("delayedPaymentBasepoint" | publicKey) :: + ("htlcBasepoint" | publicKey) :: + ("features" | combinedFeaturesCodec) :: + ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[RemoteChannelParams] + + private val channelParamsCodec: Codec[ChannelParams] = ( + ("channelId" | bytes32) :: + ("channelConfig" | channelConfigCodec) :: + ("channelFeatures" | channelFeaturesCodec) :: + ("localParams" | localChannelParamsCodec) :: + ("remoteParams" | remoteChannelParamsCodec) :: + ("channelFlags" | channelflags)).as[ChannelParams] + + private val commitParamsCodec: Codec[CommitParams] = ( + ("dustLimit" | satoshi) :: + ("htlcMinimum" | millisatoshi) :: + ("maxHtlcValueInFlight" | uint64) :: + ("maxAcceptedHtlcs" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta)).as[CommitParams] + + private val interactiveTxSharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = ( + ("info" | inputInfoCodec) :: + ("fundingTxIndex" | uint32) :: + ("remoteFundingPubkey" | publicKey) :: + ("commitmentFormat" | commitmentFormatCodec)).as[InteractiveTxBuilder.SharedFundingInput] + + private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) + .typecase(0x01, interactiveTxSharedFundingInputCodec) + + private val requireConfirmedInputsCodec: Codec[InteractiveTxBuilder.RequireConfirmedInputs] = (("forLocal" | bool8) :: ("forRemote" | bool8)).as[InteractiveTxBuilder.RequireConfirmedInputs] + + private val fundingParamsCodec: Codec[InteractiveTxBuilder.InteractiveTxParams] = ( + ("channelId" | bytes32) :: + ("isInitiator" | bool8) :: + ("localContribution" | satoshiSigned) :: + ("remoteContribution" | satoshiSigned) :: + ("sharedInput_opt" | optional(bool8, sharedFundingInputCodec)) :: + ("remoteFundingPubKey" | publicKey) :: + ("localOutputs" | listOfN(uint16, txOutCodec)) :: + ("commitmentFormat" | commitmentFormatCodec) :: + ("lockTime" | uint32) :: + ("dustLimit" | satoshi) :: + ("targetFeerate" | feeratePerKw) :: + ("requireConfirmedInputs" | requireConfirmedInputsCodec)).as[InteractiveTxBuilder.InteractiveTxParams] + + private val liquidityFeesCodec: Codec[LiquidityAds.Fees] = (("miningFees" | satoshi) :: ("serviceFees" | satoshi)).as[LiquidityAds.Fees] + private val liquidityPurchaseCodec: Codec[LiquidityAds.PurchaseBasicInfo] = (("isBuyer" | bool8) :: ("amount" | satoshi) :: ("fees" | liquidityFeesCodec)).as[LiquidityAds.PurchaseBasicInfo] + + private val sharedInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Shared] = ( + ("serialId" | uint64) :: + ("outPoint" | outPointCodec) :: + ("publicKeyScript" | lengthDelimited(bytes)) :: + ("sequence" | uint32) :: + ("localAmount" | millisatoshi) :: + ("remoteAmount" | millisatoshi) :: + ("htlcAmount" | millisatoshi)).as[InteractiveTxBuilder.Input.Shared] + + private val sharedInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Shared] = ( + ("serialId" | uint64) :: + ("scriptPubKey" | lengthDelimited(bytes)) :: + ("localAmount" | millisatoshi) :: + ("remoteAmount" | millisatoshi) :: + ("htlcAmount" | millisatoshi)).as[InteractiveTxBuilder.Output.Shared] + + private val localOnlyInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Local] = ( + ("serialId" | uint64) :: + ("previousTx" | txCodec) :: + ("previousTxOutput" | uint32) :: + ("sequence" | uint32)).as[InteractiveTxBuilder.Input.Local] + + private val localInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Local] = discriminated[InteractiveTxBuilder.Input.Local].by(byte) + .typecase(0x01, localOnlyInteractiveTxInputCodec) + + private val remoteOnlyInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Remote] = ( + ("serialId" | uint64) :: + ("outPoint" | outPointCodec) :: + ("txOut" | txOutCodec) :: + ("sequence" | uint32)).as[InteractiveTxBuilder.Input.Remote] + + private val remoteInteractiveTxInputCodec: Codec[InteractiveTxBuilder.Input.Remote] = discriminated[InteractiveTxBuilder.Input.Remote].by(byte) + .typecase(0x01, remoteOnlyInteractiveTxInputCodec) + + private val localInteractiveTxChangeOutputCodec: Codec[InteractiveTxBuilder.Output.Local.Change] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Local.Change] + + private val localInteractiveTxNonChangeOutputCodec: Codec[InteractiveTxBuilder.Output.Local.NonChange] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Local.NonChange] + + private val localInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Local] = discriminated[InteractiveTxBuilder.Output.Local].by(byte) + .typecase(0x01, localInteractiveTxChangeOutputCodec) + .typecase(0x02, localInteractiveTxNonChangeOutputCodec) + + private val remoteStandardInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Remote] = ( + ("serialId" | uint64) :: + ("amount" | satoshi) :: + ("scriptPubKey" | lengthDelimited(bytes))).as[InteractiveTxBuilder.Output.Remote] + + private val remoteInteractiveTxOutputCodec: Codec[InteractiveTxBuilder.Output.Remote] = discriminated[InteractiveTxBuilder.Output.Remote].by(byte) + .typecase(0x01, remoteStandardInteractiveTxOutputCodec) + + private val sharedTransactionCodec: Codec[InteractiveTxBuilder.SharedTransaction] = ( + ("sharedInput" | optional(bool8, sharedInteractiveTxInputCodec)) :: + ("sharedOutput" | sharedInteractiveTxOutputCodec) :: + ("localInputs" | listOfN(uint16, localInteractiveTxInputCodec)) :: + ("remoteInputs" | listOfN(uint16, remoteInteractiveTxInputCodec)) :: + ("localOutputs" | listOfN(uint16, localInteractiveTxOutputCodec)) :: + ("remoteOutputs" | listOfN(uint16, remoteInteractiveTxOutputCodec)) :: + ("lockTime" | uint32)).as[InteractiveTxBuilder.SharedTransaction] + + private val partiallySignedSharedTransactionCodec: Codec[InteractiveTxBuilder.PartiallySignedSharedTransaction] = ( + ("sharedTx" | sharedTransactionCodec) :: + ("localSigs" | lengthDelimited(txSignaturesCodec))).as[InteractiveTxBuilder.PartiallySignedSharedTransaction] + + private val fullySignedSharedTransactionCodec: Codec[InteractiveTxBuilder.FullySignedSharedTransaction] = ( + ("sharedTx" | sharedTransactionCodec) :: + ("localSigs" | lengthDelimited(txSignaturesCodec)) :: + ("remoteSigs" | lengthDelimited(txSignaturesCodec)) :: + ("sharedSigs_opt" | optional(bool8, scriptWitnessCodec))).as[InteractiveTxBuilder.FullySignedSharedTransaction] + + private val signedSharedTransactionCodec: Codec[InteractiveTxBuilder.SignedSharedTransaction] = discriminated[InteractiveTxBuilder.SignedSharedTransaction].by(uint16) + .typecase(0x01, partiallySignedSharedTransactionCodec) + .typecase(0x02, fullySignedSharedTransactionCodec) + + private val localFundingStatusCodec: Codec[LocalFundingStatus] = discriminated[LocalFundingStatus].by(uint8) + .typecase(0x04, (txOutCodec :: realshortchannelid :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.ConfirmedFundingTx]) + .typecase(0x03, (txCodec :: optional(bool8, lengthDelimited(txSignaturesCodec)) :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.ZeroconfPublishedFundingTx]) + .typecase(0x02, (signedSharedTransactionCodec :: blockHeight :: fundingParamsCodec :: optional(bool8, liquidityPurchaseCodec)).as[LocalFundingStatus.DualFundedUnconfirmedFundingTx]) + .typecase(0x01, optional(bool8, txCodec).as[LocalFundingStatus.SingleFundedUnconfirmedFundingTx]) + + private val remoteFundingStatusCodec: Codec[RemoteFundingStatus] = discriminated[RemoteFundingStatus].by(uint8) + .typecase(0x01, provide(RemoteFundingStatus.NotLocked)) + .typecase(0x02, provide(RemoteFundingStatus.Locked)) + + private val htlcCodec: Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(uint8) + .typecase(0x01, lengthDelimited(updateAddHtlcCodec).as[IncomingHtlc]) + .typecase(0x02, lengthDelimited(updateAddHtlcCodec).as[OutgoingHtlc]) + + private def minimalHtlcCodec(htlcs: Set[UpdateAddHtlc]): Codec[UpdateAddHtlc] = uint64overflow.xmap[UpdateAddHtlc](id => htlcs.find(_.id == id).get, _.id) + + private def minimalDirectedHtlcCodec(htlcs: Set[DirectedHtlc]): Codec[DirectedHtlc] = discriminated[DirectedHtlc].by(uint8) + .typecase(0x01, minimalHtlcCodec(htlcs.collect(DirectedHtlc.incoming)).as[IncomingHtlc]) + .typecase(0x02, minimalHtlcCodec(htlcs.collect(DirectedHtlc.outgoing)).as[OutgoingHtlc]) + + private def baseCommitmentSpecCodec(directedHtlcCodec: Codec[DirectedHtlc]): Codec[CommitmentSpec] = ( + ("htlcs" | setCodec(directedHtlcCodec)) :: + ("feeratePerKw" | feeratePerKw) :: + ("toLocal" | millisatoshi) :: + ("toRemote" | millisatoshi)).as[CommitmentSpec] + + /** HTLCs are stored separately to avoid duplicating data. */ + private def minimalCommitmentSpecCodec(htlcs: Set[DirectedHtlc]): Codec[CommitmentSpec] = baseCommitmentSpecCodec(minimalDirectedHtlcCodec(htlcs)) + + /** HTLCs are stored in full, the codec is stateless but creates duplication between local/remote commitment, and across commitments. */ + private val commitmentSpecCodec: Codec[CommitmentSpec] = baseCommitmentSpecCodec(htlcCodec) + + // Note that we use the default commitmentSpec codec that fully encodes HTLCs. This creates some duplication, but + // it's fine because this is a short-lived state during which the channel cannot be used for payments. + private val unsignedLocalCommitCodec: Codec[InteractiveTxSigningSession.UnsignedLocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txId" | txId)).as[InteractiveTxSigningSession.UnsignedLocalCommit] + + private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txId" | txId) :: + ("remoteSig" | channelSpendSignatureCodec) :: + ("htlcRemoteSigs" | listOfN(uint16, bytes64))).as[LocalCommit] + + private def remoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[RemoteCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txid" | txId) :: + ("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit] + + private def nextRemoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[NextRemoteCommit] = ( + ("sig" | lengthDelimited(commitSigCodec)) :: + ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit] + + private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + ("fundingTxIndex" | uint32) :: + ("firstRemoteCommitIndex" | uint64overflow) :: + ("fundingInput" | outPointCodec) :: + ("fundingAmount" | satoshi) :: + ("remoteFundingPubKey" | publicKey) :: + ("fundingTxStatus" | localFundingStatusCodec) :: + ("remoteFundingStatus" | remoteFundingStatusCodec) :: + ("commitmentFormat" | commitmentFormatCodec) :: + ("localCommitParams" | commitParamsCodec) :: + ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: + ("remoteCommitParams" | commitParamsCodec) :: + ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] + + private val waitForRevCodec: Codec[WaitForRev] = ("sentAfterLocalCommitIndex" | uint64overflow).as[WaitForRev] + + private val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g)) + + private val localChangesCodec: Codec[LocalChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec))).as[LocalChanges] + + private val remoteChangesCodec: Codec[RemoteChanges] = ( + ("proposed" | listOfN(uint16, updateMessageCodec)) :: + ("acked" | listOfN(uint16, updateMessageCodec)) :: + ("signed" | listOfN(uint16, updateMessageCodec))).as[RemoteChanges] + + private val changesCodec: Codec[CommitmentChanges] = ( + ("localChanges" | localChangesCodec) :: + ("remoteChanges" | remoteChangesCodec) :: + ("localNextHtlcId" | uint64overflow) :: + ("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges] + + private val upstreamChannelCodec: Codec[Upstream.Cold.Channel] = ( + ("originChannelId" | bytes32) :: + ("originHtlcId" | int64) :: + ("amountIn" | millisatoshi)).as[Upstream.Cold.Channel] + + private val coldUpstreamCodec: Codec[Upstream.Cold] = discriminated[Upstream.Cold].by(uint16) + // NB: order matters! + .typecase(0x03, upstreamChannelCodec) + .typecase(0x02, listOfN(uint16, upstreamChannelCodec).as[Upstream.Cold.Trampoline]) + .typecase(0x01, ("id" | uuid).as[Upstream.Local]) + + private val originCodec: Codec[Origin] = coldUpstreamCodec.xmap[Origin]( + upstream => Origin.Cold(upstream), + { + case Origin.Hot(_, upstream) => Upstream.Cold(upstream) + case Origin.Cold(upstream) => upstream + } + ) + + private val originsMapCodec: Codec[Map[Long, Origin]] = mapCodec(int64, originCodec) + + private val commitmentsCodec: Codec[ChannelTypes5.EncodedCommitments] = ( + ("params" | channelParamsCodec) :: + ("changes" | changesCodec) :: + (("htlcs" | setCodec(htlcCodec)) >>:~ { htlcs => + ("active" | listOfN(uint16, commitmentCodec(htlcs))) :: + ("inactive" | listOfN(uint16, commitmentCodec(htlcs))) :: + ("remoteNextCommitInfo" | either(bool8, waitForRevCodec, publicKey)) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("originChannels" | originsMapCodec) :: + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) + })).as[ChannelTypes5.EncodedCommitments] + + private val versionedCommitmentsCodec: Codec[Commitments] = discriminated[Commitments].by(uint8) + .typecase(0x01, commitmentsCodec.xmap(_.toCommitments, c => ChannelTypes5.EncodedCommitments.fromCommitments(c))) + + // Note that we use the default commitmentSpec codec that fully encodes HTLCs. This creates some duplication, but + // it's fine because this is a short-lived state during which the channel cannot be used for payments. + private val interactiveTxWaitingForSigsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( + ("fundingParams" | fundingParamsCodec) :: + ("fundingTxIndex" | uint32) :: + ("fundingTx" | partiallySignedSharedTransactionCodec) :: + ("localCommitParams" | commitParamsCodec) :: + ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: + ("remoteCommitParams" | commitParamsCodec) :: + ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: + ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] + + val dualFundingStatusCodec: Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) + .\(0x01) { case status: DualFundingStatus if !status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs] => DualFundingStatus.WaitingForConfirmations }(provide(DualFundingStatus.WaitingForConfirmations)) + .\(0x02) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[DualFundingStatus.RbfWaitingForSigs]) + + private val spliceStatusCodec: Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) + .\(0x01) { case status: SpliceStatus if !status.isInstanceOf[SpliceStatus.SpliceWaitingForSigs] => SpliceStatus.NoSplice }(provide(SpliceStatus.NoSplice)) + .\(0x02) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[SpliceStatus.SpliceWaitingForSigs]) + + private val closingFeeratesCodec: Codec[ClosingFeerates] = (("preferred" | feeratePerKw) :: ("min" | feeratePerKw) :: ("max" | feeratePerKw)).as[ClosingFeerates] + + private val closeStatusCodec: Codec[CloseStatus] = discriminated[CloseStatus].by(uint8) + .typecase(0x01, optional(bool8, closingFeeratesCodec).as[CloseStatus.Initiator]) + .typecase(0x02, optional(bool8, closingFeeratesCodec).as[CloseStatus.NonInitiator]) + + private val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, uint32))).as[ClosingTx] + + private val closingTxsCodec: Codec[ClosingTxs] = ( + ("localAndRemote_opt" | optional(bool8, closingTxCodec)) :: + ("localOnly_opt" | optional(bool8, closingTxCodec)) :: + ("remoteOnly_opt" | optional(bool8, closingTxCodec))).as[ClosingTxs] + + private val closingTxProposedCodec: Codec[ClosingTxProposed] = (("unsignedTx" | closingTxCodec) :: ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed] + + private val spentMapCodec: Codec[Map[OutPoint, Transaction]] = mapCodec(outPointCodec, txCodec) + + private val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("incomingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("outgoingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("htlcDelayedOutputs" | setCodec(outPointCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[LocalCommitPublished] + + private val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("incomingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("outgoingHtlcs" | mapCodec(outPointCodec, uint64overflow)) :: + ("irrevocablySpent" | spentMapCodec)).as[RemoteCommitPublished] + + private val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("remoteOutput_opt" | optional(bool8, outPointCodec)) :: + ("htlcOutputs" | setCodec(outPointCodec)) :: + ("htlcDelayedOutputs" | setCodec(outPointCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[RevokedCommitPublished] + + val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("waitingSince" | blockHeight) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: + ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] + + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases)).as[DATA_WAIT_FOR_CHANNEL_READY] + + val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( + ("channelParams" | channelParamsCodec) :: + ("secondRemotePerCommitmentPoint" | publicKey) :: + ("localPushAmount" | millisatoshi) :: + ("remotePushAmount" | millisatoshi) :: + ("status" | interactiveTxWaitingForSigsCodec)).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + + val DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localPushAmount" | millisatoshi) :: + ("remotePushAmount" | millisatoshi) :: + ("waitingSince" | blockHeight) :: + ("lastChecked" | blockHeight) :: + ("status" | dualFundingStatusCodec) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec)))).as[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] + + val DATA_WAIT_FOR_DUAL_FUNDING_READY_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_READY] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases)).as[DATA_WAIT_FOR_DUAL_FUNDING_READY] + + val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("aliases" | aliases) :: + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("spliceStatus" | spliceStatusCodec) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closeStatus" | optional(bool8, closeStatusCodec))).as[DATA_NORMAL] + + val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec)) :: + ("closeStatus" | closeStatusCodec)).as[DATA_SHUTDOWN] + + val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("localShutdown" | lengthDelimited(shutdownCodec)) :: + ("remoteShutdown" | lengthDelimited(shutdownCodec)) :: + ("closingTxProposed" | listOfN(uint16, listOfN(uint16, lengthDelimited(closingTxProposedCodec)))) :: + ("bestUnpublishedClosingTx_opt" | optional(bool8, closingTxCodec))).as[DATA_NEGOTIATING] + + val DATA_NEGOTIATING_SIMPLE_Codec: Codec[DATA_NEGOTIATING_SIMPLE] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("lastClosingFeerate" | feeratePerKw) :: + ("localScriptPubKey" | varsizebinarydata) :: + ("remoteScriptPubKey" | varsizebinarydata) :: + ("proposedClosingTxs" | listOfN(uint16, closingTxsCodec)) :: + ("publishedClosingTxs" | listOfN(uint16, closingTxCodec))).as[DATA_NEGOTIATING_SIMPLE] + + val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("waitingSince" | blockHeight) :: + ("finalScriptPubKey" | lengthDelimited(bytes)) :: + ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: + ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec)) :: + ("maxClosingFeerate" | optional(bool8, feeratePerKw))).as[DATA_CLOSING] + + val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] + } + + // Order matters! + val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) + .typecase(0x0b, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) + .typecase(0x0a, Codecs.DATA_CLOSING_Codec) + .typecase(0x09, Codecs.DATA_NEGOTIATING_SIMPLE_Codec) + .typecase(0x08, Codecs.DATA_NEGOTIATING_Codec) + .typecase(0x07, Codecs.DATA_SHUTDOWN_Codec) + .typecase(0x06, Codecs.DATA_NORMAL_Codec) + .typecase(0x05, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_READY_Codec) + .typecase(0x04, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED_Codec) + .typecase(0x03, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_Codec) + .typecase(0x02, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) + .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) + +} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala new file mode 100644 index 0000000000..eb61d3bb95 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala @@ -0,0 +1,84 @@ +/* + * Copyright 2025 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.transactions.DirectedHtlc +import scodec.bits.ByteVector + +/** + * Created by t-bast on 18/06/2025. + */ + +private[channel] object ChannelTypes5 { + + /** + * When multiple commitments are active, htlcs are shared between all of these commitments. + * There may be up to 2 * 483 = 966 htlcs, and every htlc uses at least 1452 bytes and at most 65536 bytes. + * The resulting htlc set size is thus between 1,4 MB and 64 MB, which can be pretty large. + * To avoid writing that htlc set multiple times to disk, we encode it separately. + */ + case class EncodedCommitments(channelParams: ChannelParams, + changes: CommitmentChanges, + // The direction we use is from our local point of view. + htlcs: Set[DirectedHtlc], + active: List[Commitment], + inactive: List[Commitment], + remoteNextCommitInfo: Either[WaitForRev, PublicKey], + remotePerCommitmentSecrets: ShaChain, + originChannels: Map[Long, Origin], + remoteChannelData_opt: Option[ByteVector]) { + def toCommitments: Commitments = { + Commitments( + channelParams = channelParams, + changes = changes, + active = active, + inactive = inactive, + remoteNextCommitInfo = remoteNextCommitInfo, + remotePerCommitmentSecrets = remotePerCommitmentSecrets, + originChannels = originChannels, + remoteChannelData_opt = remoteChannelData_opt + ) + } + } + + object EncodedCommitments { + def fromCommitments(commitments: Commitments): EncodedCommitments = { + // The direction we use is from our local point of view: we use sets, which deduplicates htlcs that are in both + // local and remote commitments. All active commitments have the same htlc set, but each inactive commitment may + // have a distinct htlc set. + val commitmentsSet = commitments.active.head +: commitments.inactive + val htlcs = commitmentsSet.flatMap(_.localCommit.spec.htlcs).toSet ++ + commitmentsSet.flatMap(_.remoteCommit.spec.htlcs.map(_.opposite)).toSet ++ + commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.commit.spec.htlcs.map(_.opposite))).toSet + EncodedCommitments( + channelParams = commitments.channelParams, + changes = commitments.changes, + htlcs = htlcs, + active = commitments.active.toList, + inactive = commitments.inactive.toList, + remoteNextCommitInfo = commitments.remoteNextCommitInfo, + remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets, + originChannels = commitments.originChannels, + remoteChannelData_opt = commitments.remoteChannelData_opt + ) + } + } + +} diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json index 1f34a0cdee..7d6a120a96 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 1457788542, 1007597768, 1455922339, 479707306 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 5000000000, "initialRequestedChannelReserve_opt" : 167772, - "htlcMinimum" : 1, - "toRemoteDelay" : 720, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "upfrontShutdownScript_opt" : "a9144805d016e47885dc7c852710cdd8cd0d576f57ec87", @@ -28,12 +23,7 @@ }, "remoteParams" : { "nodeId" : "034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 16609443000, "initialRequestedChannelReserve_opt" : 167772, - "htlcMinimum" : 1000, - "toRemoteDelay" : 2016, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1", "paymentBasepoint" : "034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1", "delayedPaymentBasepoint" : "0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467", @@ -67,16 +57,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", - "amountSatoshis" : 16777215 - }, + "fundingInput" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", + "fundingAmount" : 16777215, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 5000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 2016 + }, "localCommit" : { "index" : 7675, "spec" : { @@ -86,15 +82,18 @@ "toRemote" : 16572475271 }, "txId" : "e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4", - "input" : { - "outPoint" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", - "amountSatoshis" : 16777215 - }, "remoteSig" : { "sig" : "4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 16609443000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 720 + }, "remoteCommit" : { "index" : 7779, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json index e699ba38e5..ae804b4955 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 3561221353, 3653515793, 2711311691, 2863050005 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "a91445e990148599176534ec9b75df92ace9263f7d3487", @@ -28,12 +23,7 @@ }, "remoteParams" : { "nodeId" : "0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 14850000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 1802, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c", "paymentBasepoint" : "03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd", "delayedPaymentBasepoint" : "03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397", @@ -67,16 +57,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", - "amountSatoshis" : 15000000 - }, + "fundingInput" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", + "fundingAmount" : 15000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 1802 + }, "localCommit" : { "index" : 20024, "spec" : { @@ -86,15 +82,18 @@ "toRemote" : 13656683380 }, "txId" : "65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43", - "input" : { - "outPoint" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", - "amountSatoshis" : 15000000 - }, "remoteSig" : { "sig" : "bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 14850000000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 20024, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json index a594bbd9c4..1bf956287e 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder/data.json @@ -4,16 +4,11 @@ "channelParams" : { "channelId" : "5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134", "fundingKeyPath" : [ 2353764507, 3184449568, 2809819526, 3258060413, 392846475, 1545000620, 720603293, 1808318336, 2147483649 ], - "dustLimit" : 546, - "maxHtlcValueInFlightMsat" : 20000000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 720, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "00148061b7fbd2d84ed1884177ea785faecb2080b103", @@ -35,12 +30,7 @@ }, "remoteParams" : { "nodeId" : "027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8", - "dustLimit" : 573, - "maxHtlcValueInFlightMsat" : 14850000000, "initialRequestedChannelReserve_opt" : 150000, - "htlcMinimum" : 1, - "toRemoteDelay" : 1802, - "maxAcceptedHtlcs" : 483, "revocationBasepoint" : "0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75", "paymentBasepoint" : "03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd", "delayedPaymentBasepoint" : "03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8", @@ -81,16 +71,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", - "amountSatoshis" : 15000000 - }, + "fundingInput" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", + "fundingAmount" : 15000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 546, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 20000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 1802 + }, "localCommit" : { "index" : 4, "spec" : { @@ -100,15 +96,18 @@ "toRemote" : 0 }, "txId" : "fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f", - "input" : { - "outPoint" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", - "amountSatoshis" : 15000000 - }, "remoteSig" : { "sig" : "871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 573, + "htlcMinimum" : 1, + "maxHtlcValueInFlight" : 14850000000, + "maxAcceptedHtlcs" : 483, + "toSelfDelay" : 720 + }, "remoteCommit" : { "index" : 4, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json index 0cb947422d..4df96939c6 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 4092535092, 4227137620, 3959690417, 2298849496, 2106263857, 1090614243, 1495530077, 1280982866, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014fec406ef7a0258cb503fe1f1803787d971eeb4d1", @@ -31,12 +26,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 18446744073709551615, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "02f7c3bf47cdc640304eda4c761a26dfebfee561de15ba106f3d9982d3ef3fbe10", "paymentBasepoint" : "02be361b7bf1bdfb283cff3f83bf16f6f8fb67d3f480b541e76518939f667ab834", "delayedPaymentBasepoint" : "03fcaec5443dd423f160c9b77a48b2585b186f2d850147f57210d3f8a8c8d754a7", @@ -74,10 +64,8 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "unconfirmed", "txid" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9" @@ -85,6 +73,14 @@ "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -94,15 +90,18 @@ "toRemote" : 200000000 }, "txId" : "c6fe6bc0a5a9c149a03a907d2351714aa27fc98a485e981343cea08a1904ee26", - "input" : { - "outPoint" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "1ea9cffd2af82f6c14251bd59ffa3e876178c7b263f16d12790c33fc093eaed053dd9e0d49f79558aeb3c2fe7fe84f95a91eecbea2679fb65916ad9632df07b4" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 18446744073709551615, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index ad75510f4e..93350edf5f 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -8,12 +8,7 @@ "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 303987973, 3198768511, 3783619274, 2277156978, 1699864653, 63358126, 3265052696, 516813756, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014c59265957886e166f37c863dca15b49aa42d75b4", @@ -32,12 +27,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "033b164d14b54f08a951f9e40fb84e637021f376c9ae2907975f9ff0795431205d", "paymentBasepoint" : "03759da3f6bdcc2f595e1a98eb4803729743ab8608e28788cfe96726b5328c214c", "delayedPaymentBasepoint" : "03abc3ae99fac104e94f79cafcd70247cc336cdc21a53d4c3a4c321b54e20902e7", @@ -76,16 +66,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -95,15 +91,18 @@ "toRemote" : 200000000 }, "txId" : "a591186503570022767b96fc4e02448e2c2d95f9e83f06c526f810886b02dedd", - "input" : { - "outPoint" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "8ccca40bbf6c859ceb2d5288153801426e19f8b119c2d1d4ba6d82511c7816aa0eaa8e41eafb179818d1ba7b0cfdce2da5fa002921df00a1fb4e1458d0b9042c" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index 039730f794..f79f5ff3bb 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -4,15 +4,10 @@ "channelParams" : { "channelId" : "f5cafea10bc83c2fe9de16d04bb73c1ddaed2ce9d600dd91301a7d995f7b9134", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 3109590638, 2571039769, 2759029525, 192289746, 3879532998, 1343053922, 3645251601, 1767821717, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "upfrontShutdownScript_opt" : "0014fe2baa428e1f6b4b4134d444b3de42a86cffbabc", @@ -35,11 +30,6 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "03626342f0af6e87ab41715bd6d1db7d6eee5e95a2a835680b42d9b357003c1c6a", "paymentBasepoint" : "02ed6013749b4e3a820e7c003d3d3c7c5f16dd0d25ce72d3de99e544d4011c0a67", "delayedPaymentBasepoint" : "038a15a3dcd087135509b8e91d95c4fb788be11735f5c1a9ac24283d3cecbc76d2", @@ -82,16 +72,22 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "unconfirmed" }, "remoteFunding" : { "status" : "locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -101,15 +97,18 @@ "toRemote" : 500000000 }, "txId" : "b543f86a2a9a06f80b7339f9aaa169a779a7754d3523c28845d10dad9b9d7bc9", - "input" : { - "outPoint" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "5d65b42b0a8f05ca2793360a6b34bd5c1b9d960cf62c300c0e493f9cd83d2ca41f46a663d4fc11891ef268c0200b8fea8fb454d9055d35c2d6f5ba0b14c78b2a" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json index 4eed16c4f0..a7ba47483c 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/fundee/data.json @@ -4,16 +4,11 @@ "channelParams" : { "channelId" : "7d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 3266606892, 3454308995, 379409286, 2058386039, 150235166, 1337553882, 292124276, 1286028724, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "02e1a7010650cd5cd6fbf7505f5f213b36e5cc0d127064c74619c83dfa7434ce25", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "031723f6ffc8e552694f09d067844851f14357fb2a294fd94fd0b596ee808d2a17", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 800000000 }, "txId" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", - "input" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "02b8ce0a760ae6052a0d6c5770ae626ae71ea1b64853f97bb2c784839264054c64dc8717789f804342cbd9c5a364b32f4cbd9e9a4f91f48127a58f95014f6663" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index fc48de0975..5ef13291e8 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000b-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -4,16 +4,11 @@ "channelParams" : { "channelId" : "7d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 222163200, 3836794389, 3217943953, 1565449417, 2862146275, 3633046910, 2547274215, 4050695153, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "0201dd773d2108f3ed8f59669240c7f098d3083963bef5b36ebf940f4bc40724c8", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "0243f77d02fbb07f25d6fe14f9c7ee904e1a19442a4aea0dc1ce959d5d2caea9b4", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 200000000 }, "txId" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", - "input" : { - "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "8d3c57514dd1c01073b3d3704eddcfd8ca6ac3bb58f145f2c2dd921b77f238e842dce706a711b69b250e9ec3b827a9d9e144720a41fadce820bb18280667bffd" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json index e5426ace13..70363bb00e 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/fundee/data.json @@ -4,15 +4,10 @@ "channelParams" : { "channelId" : "ae58e31828b115b8a900a9d20b060ef2b2da2fcfe18991aff34168579d246b54", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2566431519, 2591595317, 1320214673, 2312083324, 3942626068, 2510065287, 1961707336, 236707474, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "initFeatures" : { @@ -37,11 +32,6 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "038039cb4b63c81052cf0893e27f7be58a5808453dffd44c662b1a75aebb1fc74e", "paymentBasepoint" : "0394e61db71e79c20f2ef131e99121996c7ae04330c4c50dae6bccc5afcf493661", "delayedPaymentBasepoint" : "029a15b644f2800a507565666956446c05ef8dd7b60023852607c97471d233d62e", @@ -88,18 +78,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -109,15 +104,18 @@ "toRemote" : 1000000000 }, "txId" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", - "input" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "c70d3fc3e720cc4fd1a2e07b97d454392ecfbe27c0ef2eed401538466924f6c24accb834eed784536fe5d30656eb8308a8b0f48bdba2ccc9812ce49a12b9dd80" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index fd8d104943..80a7846555 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000d-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -4,15 +4,10 @@ "channelParams" : { "channelId" : "ae58e31828b115b8a900a9d20b060ef2b2da2fcfe18991aff34168579d246b54", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_anchors_zero_fee_htlc_tx", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 461130578, 2441458723, 3315200959, 1624559903, 421875145, 1108846792, 2605590614, 1621284536, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "initFeatures" : { @@ -38,11 +33,6 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "031a9b266c6a1f5d05f6f2b040b0eea59819c2fb5715945628541c58c2605e5c42", "paymentBasepoint" : "02cb3e21fe0825663cfa1d8a8accc93dedbdb906b0ec4b779bb41b0f030b38fd33", "delayedPaymentBasepoint" : "0370c28e0286bfcf6bfb18c2b98d5b265594ec363822cbb323e7f7451ca60b0330", @@ -88,18 +78,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "anchor_outputs", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -109,15 +104,18 @@ "toRemote" : 500000000 }, "txId" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", - "input" : { - "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "353c51755a0c852a4c5dd9e2d33d4a9c0934e9a07a43da89f061a3b4499f374d1857dfa513ed714287baf4ac3f4375f06bf435577962f121182ad9ce4b19f549" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json index 1ed6f0b14f..2afb8de0d2 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/announced/data.json @@ -4,16 +4,11 @@ "channelParams" : { "channelId" : "c380aa11700db0a6d797dfd0be8aecfadad9397e4975f0fa8c9d10db71feac38", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey" ], + "channelFeatures" : [ ], "localParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", "fundingKeyPath" : [ 1057736322, 487845715, 3068040012, 2828312867, 3790939166, 2388092911, 4276180524, 217832603, 2147483649 ], - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 500000000, "initialRequestedChannelReserve_opt" : 10000, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "isChannelOpener" : true, "paysCommitTxFees" : true, "walletStaticPaymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", @@ -37,12 +32,7 @@ }, "remoteParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 1000000000, "initialRequestedChannelReserve_opt" : 20000, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "revocationBasepoint" : "02f943c4f199d1425fc6e52f160be536d526e9643af1430cfed4ce63f88beebd2f", "paymentBasepoint" : "028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12", "delayedPaymentBasepoint" : "03f3b6c8919d313d5bd14a2e567649ea80ed1dd299ebc72766775a1f4fdc3cc1b5", @@ -86,18 +76,23 @@ }, "active" : [ { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", - "amountSatoshis" : 1000000 - }, + "fundingInput" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", + "fundingAmount" : 1000000, "localFunding" : { "status" : "confirmed", - "txid" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3", "shortChannelId" : "400000x42x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 500000000, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -107,15 +102,18 @@ "toRemote" : 200000000 }, "txId" : "41418383164935b0753fa1f9b128dff2f1b3608e6b50e8beea4525edf7c3959d", - "input" : { - "outPoint" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", - "amountSatoshis" : 1000000 - }, "remoteSig" : { "sig" : "62de565629126afb02a76e67ddf18dfc883cf93bfeb7c807826dd6c8d1bd1f4d55528717f0d2303dc254b4512e32ce82dc7947d5d8e4d38647c2407fc53b874a" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 1000000000, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json index 1000fae8d6..75b97674e7 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/04000e-DATA_NORMAL/splicing-private/data.json @@ -4,15 +4,10 @@ "channelParams" : { "channelId" : "9c5655e99c1ef6d219eb093ca9f29e642167b4c776928df11c73e2293086b749", "channelConfig" : [ "funding_pubkey_based_channel_keypath" ], - "channelFeatures" : [ "option_static_remotekey", "option_dual_fund" ], + "channelFeatures" : [ "option_dual_fund" ], "localParams" : { "nodeId" : "02bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e63", "fundingKeyPath" : [ 2707154742, 1913609705, 398200054, 2009144985, 2117492679, 302300795, 2676284185, 1690699462, 2147483648 ], - "dustLimit" : 1000, - "maxHtlcValueInFlightMsat" : 9223372036854775807, - "htlcMinimum" : 1000, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 30, "isChannelOpener" : false, "paysCommitTxFees" : false, "walletStaticPaymentBasepoint" : "038bff1253b7b8e40532508a53d31b95b295df31edf0d067d734479a680bd395de", @@ -37,11 +32,6 @@ }, "remoteParams" : { "nodeId" : "02aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa", - "dustLimit" : 1100, - "maxHtlcValueInFlightMsat" : 9223372036854775807, - "htlcMinimum" : 0, - "toRemoteDelay" : 144, - "maxAcceptedHtlcs" : 100, "revocationBasepoint" : "02fab0332aecd8006f4937b220ea073b0e814a21e36f4d21de9bb3e5165c3ffa02", "paymentBasepoint" : "038bff1253b7b8e40532508a53d31b95b295df31edf0d067d734479a680bd395de", "delayedPaymentBasepoint" : "023a5359a4e12c66e5b6a1847161f837695ab6e7591e67cb5b1da82706b491ea12", @@ -86,10 +76,8 @@ }, "active" : [ { "fundingTxIndex" : 2, - "fundingTx" : { - "outPoint" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", - "amountSatoshis" : 2500000 - }, + "fundingInput" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", + "fundingAmount" : 2500000, "localFunding" : { "status" : "unconfirmed", "txid" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101" @@ -97,6 +85,14 @@ "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -106,15 +102,18 @@ "toRemote" : 1800000000 }, "txId" : "ec1fdbdffd12f20851f33319fb3ab6ecb8528a34a9a2c76e53aaf91cf4a18da4", - "input" : { - "outPoint" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", - "amountSatoshis" : 2500000 - }, "remoteSig" : { "sig" : "52ea4ba9421f4b4ad8a9a1e3041a940b13751504faf7d79338d73bbc1187fb551d9340de809849d6d7dcd745b813e01c07809cb8eacc57a211990b0c021d4c45" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { @@ -128,18 +127,23 @@ } }, { "fundingTxIndex" : 1, - "fundingTx" : { - "outPoint" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", - "amountSatoshis" : 2000000 - }, + "fundingInput" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", + "fundingAmount" : 2000000, "localFunding" : { "status" : "confirmed", - "txid" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005", "shortChannelId" : "0x0x0" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -149,15 +153,18 @@ "toRemote" : 1300000000 }, "txId" : "27f7218158a68f5bb23c9d8a280f85bfca32d7636529e1db2ba23172241161f4", - "input" : { - "outPoint" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", - "amountSatoshis" : 2000000 - }, "remoteSig" : { "sig" : "32a246840dd67eb72b59fc170303c508c77ee8666ac6be36abced02bf79dbfbe114cdd4e56600ae6e3573e6e10d3517ddc857fbd89c00e2cbcdf5186c6592f26" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { @@ -171,18 +178,23 @@ } }, { "fundingTxIndex" : 0, - "fundingTx" : { - "outPoint" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", - "amountSatoshis" : 1500000 - }, + "fundingInput" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", + "fundingAmount" : 1500000, "localFunding" : { "status" : "confirmed", - "txid" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f", "shortChannelId" : "400000x42x2" }, "remoteFunding" : { "status" : "not-locked" }, + "commitmentFormat" : "legacy", + "localCommitParams" : { + "dustLimit" : 1000, + "htlcMinimum" : 1000, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 30, + "toSelfDelay" : 144 + }, "localCommit" : { "index" : 0, "spec" : { @@ -192,15 +204,18 @@ "toRemote" : 800000000 }, "txId" : "381ee5a54f127b776114b197b2695c39f2d9b81cb967628a32e12766cdf8f1b5", - "input" : { - "outPoint" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", - "amountSatoshis" : 1500000 - }, "remoteSig" : { "sig" : "47f525be7304ef4fa4d868eccfa2389fe1d8f058d08305b0f1792cebbd14ec7b6b43f98d0f3aa22e60d7ef024785f3576d0c1d74b666c7c5d2da88c7e7f7bd18" }, "htlcRemoteSigs" : [ ] }, + "remoteCommitParams" : { + "dustLimit" : 1100, + "htlcMinimum" : 0, + "maxHtlcValueInFlight" : 9223372036854775807, + "maxAcceptedHtlcs" : 100, + "toSelfDelay" : 144 + }, "remoteCommit" : { "index" : 0, "spec" : { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index f01d37998b..3863478e16 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -268,8 +268,7 @@ object TestConstants { isChannelOpener = true, paysCommitTxFees = true, dualFunded = false, - fundingSatoshis, - unlimitedMaxHtlcValueInFlight = false, + fundingSatoshis ).copy( fundingKeyPath = fundingKeyPath, initialRequestedChannelReserve_opt = Some(10_000 sat) // Bob will need to keep that much satoshis in his balance @@ -460,8 +459,7 @@ object TestConstants { isChannelOpener = false, paysCommitTxFees = false, dualFunded = false, - fundingSatoshis, - unlimitedMaxHtlcValueInFlight = false, + fundingSatoshis ).copy( fundingKeyPath = fundingKeyPath, initialRequestedChannelReserve_opt = Some(20_000 sat) // Alice will need to keep that much satoshis in her balance diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala index c0c6fc5dcf..ce15232ac6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala @@ -92,7 +92,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - val closingTxInput = alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE].commitments.latest.commitInput.outPoint + val closingTxInput = alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE].commitments.latest.fundingInput val expected = MainAndHtlcBalance(toLocal = 0 sat, htlcs = 0 sat) assert(CheckBalance.computeOffChainBalance(Seq(alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE]), recentlySpentInputs = Set(closingTxInput)).negotiating == expected) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala index b9185cffee..f74a051fda 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala @@ -19,41 +19,11 @@ package fr.acinq.eclair.channel import fr.acinq.eclair.FeatureSupport._ import fr.acinq.eclair.Features._ import fr.acinq.eclair.channel.states.ChannelStateTestsBase -import fr.acinq.eclair.transactions.Transactions -import fr.acinq.eclair.{Feature, Features, InitFeature, TestKitBaseClass} +import fr.acinq.eclair.{Features, InitFeature, PermanentChannelFeature, TestKitBaseClass} import org.scalatest.funsuite.AnyFunSuiteLike class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStateTestsBase { - test("channel features determines commitment format") { - val standardChannel = ChannelFeatures() - val staticRemoteKeyChannel = ChannelFeatures(Features.StaticRemoteKey) - val anchorOutputsChannel = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) - val anchorOutputsZeroFeeHtlcsChannel = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) - - assert(!standardChannel.hasFeature(Features.StaticRemoteKey)) - assert(!standardChannel.hasFeature(Features.AnchorOutputs)) - assert(standardChannel.commitmentFormat == Transactions.DefaultCommitmentFormat) - assert(!standardChannel.paysDirectlyToWallet) - - assert(staticRemoteKeyChannel.hasFeature(Features.StaticRemoteKey)) - assert(!staticRemoteKeyChannel.hasFeature(Features.AnchorOutputs)) - assert(staticRemoteKeyChannel.commitmentFormat == Transactions.DefaultCommitmentFormat) - assert(staticRemoteKeyChannel.paysDirectlyToWallet) - - assert(anchorOutputsChannel.hasFeature(Features.StaticRemoteKey)) - assert(anchorOutputsChannel.hasFeature(Features.AnchorOutputs)) - assert(!anchorOutputsChannel.hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) - assert(anchorOutputsChannel.commitmentFormat == Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(!anchorOutputsChannel.paysDirectlyToWallet) - - assert(anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.StaticRemoteKey)) - assert(anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) - assert(!anchorOutputsZeroFeeHtlcsChannel.hasFeature(Features.AnchorOutputs)) - assert(anchorOutputsZeroFeeHtlcsChannel.commitmentFormat == Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - assert(!anchorOutputsZeroFeeHtlcsChannel.paysDirectlyToWallet) - } - test("pick channel type based on local and remote features") { case class TestCase(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expectedChannelType: ChannelType) val testCases = Seq( @@ -123,22 +93,22 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha } test("enrich channel type with optional permanent channel features") { - case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[Feature]) + case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[PermanentChannelFeature]) val testCases = Seq( TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Mandatory), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set(StaticRemoteKey)), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputs(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs)), - TestCase(ChannelTypes.AnchorOutputs(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, UpfrontShutdownScript)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, UpfrontShutdownScript, DualFunding)), + TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), + TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputs(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), + TestCase(ChannelTypes.AnchorOutputs(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = true, Set(ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = false, Set(ScidAlias, ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(ScidAlias)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = false, Set(ScidAlias, ZeroConf)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(UpfrontShutdownScript)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), Features(DualFunding -> Optional, UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript, DualFunding)), ) testCases.foreach(t => assert(ChannelFeatures(t.channelType, t.localFeatures, t.remoteFeatures, t.announceChannel).features == t.expected, s"channelType=${t.channelType} localFeatures=${t.localFeatures} remoteFeatures=${t.remoteFeatures}")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala index dcd287922d..0f56624625 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala @@ -17,12 +17,11 @@ package fr.acinq.eclair.channel import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxOut} +import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, TxOut} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee._ import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.reputation.Reputation @@ -31,7 +30,6 @@ import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions} import fr.acinq.eclair.wire.protocol._ import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} -import scodec.bits.ByteVector import scala.concurrent.duration._ import scala.util.Random @@ -492,22 +490,22 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isOpener: Boolean = true, announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isOpener, isOpener, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isOpener, isOpener, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val commitParams = CommitParams(dustLimit, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript)), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTxOut, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, @@ -517,22 +515,22 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announcement_opt: Option[ChannelAnnouncement]): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(remoteNodeId, 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(remoteNodeId, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val commitParams = CommitParams(0 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript)), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTxOut, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index 8a1144abec..f9eeeb2e8f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.{Actor, ActorLogging, ActorRef, Props} import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.scalacompat.ByteVector32 +import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.DummyOnChainWallet @@ -58,8 +58,7 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe val bobParams = Bob.nodeParams val aliceChannelParams = Alice.channelParams val bobChannelParams = Bob.channelParams - val aliceCommitParams = aliceChannelParams.proposedCommitParams - val bobCommitParams = bobChannelParams.proposedCommitParams + val commitParams = ProposedCommitParams(1000 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)) val channelFlags = ChannelFlags(announceChannel = false) val alicePeer = TestProbe() val bobPeer = TestProbe() @@ -82,9 +81,9 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe aliceRegister ! alice bobRegister ! bob // no announcements - alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, aliceCommitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.Standard(), replyTo = system.deadLetters) + alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, commitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.Standard(), replyTo = system.deadLetters) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, bobCommitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.Standard()) + bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, commitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.Standard()) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] pipe ! (alice, bob) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 79201b3a58..94667ba947 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -198,7 +198,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat ) def toClosingTx(txOut: Seq[TxOut]): ClosingTx = { - ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil), ByteVector.empty), Transaction(2, Nil, txOut, 0), None) + ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil)), Transaction(2, Nil, txOut, 0), None) } assert(Closing.MutualClose.checkClosingDustAmounts(toClosingTx(allOutputsAboveDust))) @@ -218,7 +218,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800"), Transaction.read("010000000235a2f5c4fd48672534cce1ac063047edc38683f43c5a883f815d6026cb5f8321020000006a47304402206be5fd61b1702599acf51941560f0a1e1965aa086634b004967747f79788bd6e022002f7f719a45b8b5e89129c40a9d15e4a8ee1e33be3a891cf32e859823ecb7a510121024756c5adfbc0827478b0db042ce09d9b98e21ad80d036e73bd8e7f0ecbc254a2ffffffffb2387d3125bb8c84a2da83f4192385ce329283661dfc70191f4112c67ce7b4d0000000006b483045022100a2c737eab1c039f79238767ccb9bb3e81160e965ef0fc2ea79e8360c61b7c9f702202348b0f2c0ea2a757e25d375d9be183200ce0a79ec81d6a4ebb2ae4dc31bc3c9012102db16a822e2ec3706c58fc880c08a3617c61d8ef706cc8830cfe4561d9a5d52f0ffffffff01808d5b00000000001976a9141210c32def6b64d0d77ba8d99adeb7e9f91158b988ac00000000"), Transaction.read("0100000001b14ba6952c83f6f8c382befbf4e44270f13e479d5a5ff3862ac3a112f103ff2a010000006b4830450221008b097fd69bfa3715fc5e119a891933c091c55eabd3d1ddae63a1c2cc36dc9a3e02205666d5299fa403a393bcbbf4b05f9c0984480384796cdebcf69171674d00809c01210335b592484a59a44f40998d65a94f9e2eecca47e8d1799342112a59fc96252830ffffffff024bf308000000000017a914440668d018e5e0ba550d6e042abcf726694f515c8798dd1801000000001976a91453a503fe151dd32e0503bd9a2fbdbf4f9a3af1da88ac00000000") - ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), ByteVector.empty), tx, None)) + ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), tx, None)) // only mutual close assert(Closing.isClosingTypeAlreadyKnown( 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 ca2cd2c6f9..75ca2e099a 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 @@ -86,12 +86,6 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit case None => input.sharedInput_opt.get } - private def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { - val sharedInputA = Multisig2of2Input(commitmentA) - val sharedInputB = Multisig2of2Input(commitmentB) - (sharedInputA, sharedInputB) - } - case class FixtureParams(fundingParamsA: InteractiveTxParams, nodeParamsA: NodeParams, channelParamsA: ChannelParams, @@ -110,75 +104,81 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit private val firstPerCommitmentPointB = channelKeysB.commitmentPoint(0) val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingParamsB.remoteFundingPubKey, fundingParamsA.remoteFundingPubKey))) + def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { + val sharedInputA = SharedFundingInput(channelKeysA, commitmentA) + val sharedInputB = SharedFundingInput(channelKeysB, commitmentB) + (sharedInputA, sharedInputB) + } + def dummySharedInputB(amount: Satoshi): SharedFundingInput = { - val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript), ByteVector.empty) + val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript)) val fundingTxIndex = fundingParamsA.sharedInput_opt match { - case Some(input: Multisig2of2Input) => input.fundingTxIndex + 1 + case Some(input) => input.fundingTxIndex + 1 case _ => 0 } - Multisig2of2Input(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey) + SharedFundingInput(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey, fundingParamsA.commitmentFormat) } def createSpliceFixtureParams(fundingTxIndex: Long, fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, sharedInputA: SharedFundingInput, sharedInputB: SharedFundingInput, spliceOutputsA: List[TxOut] = Nil, spliceOutputsB: List[TxOut] = Nil, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)): FixtureParams = { val fundingPubKeyA = channelKeysA.fundingKey(fundingTxIndex).publicKey val fundingPubKeyB = channelKeysB.fundingKey(fundingTxIndex).publicKey - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, Some(sharedInputA), fundingPubKeyB, spliceOutputsA, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, Some(sharedInputB), fundingPubKeyA, spliceOutputsB, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - copy(fundingParamsA = fundingParamsA, fundingParamsB = fundingParamsB) + val fundingParamsA1 = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, Some(sharedInputA), fundingPubKeyB, spliceOutputsA, fundingParamsA.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsB1 = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, Some(sharedInputB), fundingPubKeyA, spliceOutputsB, fundingParamsB.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + copy(fundingParamsA = fundingParamsA1, fundingParamsB = fundingParamsB1) } def spawnTxBuilderAlice(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsA, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, FundingTx(commitFeerate, firstPerCommitmentPointB, feeBudget_opt = None), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderRbfAlice(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, FundingTxRbf(commitment, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderSpliceAlice(fundingParams: InteractiveTxParams, commitment: Commitment, wallet: OnChainWallet, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, SpliceTx(commitment, CommitmentChanges.init()), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderSpliceRbfAlice(fundingParams: InteractiveTxParams, parentCommitment: Commitment, latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsA, fundingParams, channelParamsA, channelKeysA, + nodeParamsA, fundingParams, channelParamsA, commitParamsA, commitParamsB, channelKeysA, SpliceTxRbf(parentCommitment, CommitmentChanges.init(), latestFundingTx, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderBob(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsB, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, FundingTx(commitFeerate, firstPerCommitmentPointA, feeBudget_opt = None), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderRbfBob(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, FundingTxRbf(commitment, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) def spawnTxBuilderSpliceBob(fundingParams: InteractiveTxParams, commitment: Commitment, wallet: OnChainWallet, liquidityPurchase_opt: Option[LiquidityAds.Purchase] = None): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, SpliceTx(commitment, CommitmentChanges.init()), 0 msat, 0 msat, liquidityPurchase_opt, wallet)) def spawnTxBuilderSpliceRbfBob(fundingParams: InteractiveTxParams, parentCommitment: Commitment, latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( ByteVector32.Zeroes, - nodeParamsB, fundingParams, channelParamsB, channelKeysB, + nodeParamsB, fundingParams, channelParamsB, commitParamsB, commitParamsA, channelKeysB, SpliceTxRbf(parentCommitment, CommitmentChanges.init(), latestFundingTx, previousTransactions, feeBudget_opt = None), 0 msat, 0 msat, None, wallet)) @@ -217,11 +217,12 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit } private def createFixtureParams(fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false), nonInitiatorPaysCommitTxFees: Boolean = false): FixtureParams = { - val channelFeatures = ChannelFeatures(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), announceChannel = true) + val channelType = ChannelTypes.AnchorOutputsZeroFeeHtlcTx() + val channelFeatures = ChannelFeatures(channelType, Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), Features[InitFeature](Features.DualFunding -> FeatureSupport.Optional), announceChannel = true) val Seq(nodeParamsA, nodeParamsB) = Seq(TestConstants.Alice.nodeParams, TestConstants.Bob.nodeParams).map(_.copy(features = Features(channelFeatures.features.map(f => f -> FeatureSupport.Optional).toMap[Feature, FeatureSupport]))) - val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA, unlimitedMaxHtlcValueInFlight = false) + val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA) val commitParamsA = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) - val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB, unlimitedMaxHtlcValueInFlight = false) + val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB) val commitParamsB = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) val channelKeysA = nodeParamsA.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsA.fundingKeyPath) val channelKeysB = nodeParamsB.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsB.fundingKeyPath) @@ -230,7 +231,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit case (nodeParams, localParams, channelKeys) => RemoteChannelParams( nodeParams.nodeId, - localParams.dustLimit, localParams.maxHtlcValueInFlightMsat, None, localParams.htlcMinimum, nodeParams.channelConf.toRemoteDelay, localParams.maxAcceptedHtlcs, + None, channelKeys.revocationBasePoint, localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), channelKeys.delayedPaymentBasePoint, @@ -242,8 +243,8 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val channelId = randomBytes32() val fundingPubKeyA = channelKeysA.fundingKey(fundingTxIndex = 0).publicKey val fundingPubKeyB = channelKeysB.fundingKey(fundingTxIndex = 0).publicKey - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, channelType.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) + val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, channelType.commitmentFormat, lockTime, dustLimit, targetFeerate, requireConfirmedInputs) val channelParamsA = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsA, remoteChannelParamsB, ChannelFlags(announceChannel = true)) val channelParamsB = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParamsB, remoteChannelParamsA, ChannelFlags(announceChannel = true)) @@ -751,7 +752,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice and Bob decide to splice additional funds in the channel. val additionalFundingA2 = 30_000.sat val additionalFundingB2 = 25_000.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA2, fundingAmountB = additionalFundingB2, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -787,7 +788,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Bob has more balance than Alice in the shared input, so its total contribution is greater than Alice. // But Bob still signs first, because we don't split the shared input's balance when deciding who signs first. assert(spliceTxA.tx.localAmountIn < spliceTxA.tx.remoteAmountIn) - assert(spliceTxA.signedTx.txIn.exists(_.outPoint == commitmentA1.commitInput.outPoint)) + assert(spliceTxA.signedTx.txIn.exists(_.outPoint == commitmentA1.fundingInput)) assert(0.msat < spliceTxA.tx.localFees) assert(0.msat < spliceTxA.tx.remoteFees) assert(spliceTxB.tx.localFees == spliceTxA.tx.remoteFees) @@ -846,7 +847,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) @@ -859,7 +860,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice --- tx_add_input --> Bob val sharedInput = fwdSplice.forwardAlice2Bob[TxAddInput] assert(sharedInput.previousTx_opt.isEmpty) - assert(sharedInput.sharedInput_opt.contains(commitmentA1.commitInput.outPoint)) + assert(sharedInput.sharedInput_opt.contains(commitmentA1.fundingInput)) // Alice <-- tx_add_output --- Bob val outputB = fwdSplice.forwardBob2Alice[TxAddOutput] // Alice --- tx_add_output --> Bob @@ -934,7 +935,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(25_000 sat, 15_000 sat).map(amount => TxOut(amount, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -946,7 +947,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice --- tx_add_input --> Bob val sharedInput = fwdSplice.forwardAlice2Bob[TxAddInput] assert(sharedInput.previousTx_opt.isEmpty) - assert(sharedInput.sharedInput_opt.contains(commitmentA1.commitInput.outPoint)) + assert(sharedInput.sharedInput_opt.contains(commitmentA1.fundingInput)) // Alice <-- tx_add_output --- Bob val outputB1 = fwdSplice.forwardBob2Alice[TxAddOutput] // Alice --- tx_add_output --> Bob @@ -1031,7 +1032,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 15_000.sat val spliceOutputsA = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -1540,7 +1541,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) @@ -1667,7 +1668,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -1806,13 +1807,13 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceFeeA = { val dummySpliceTx = Transaction( version = 2, - txIn = Seq(TxIn(commitmentA1.commitInput.outPoint, ByteVector.empty, 0, Scripts.witness2of2(Transactions.PlaceHolderSig, Transactions.PlaceHolderSig, randomKey().publicKey, randomKey().publicKey))), - txOut = Seq(commitmentA1.commitInput.txOut), + txIn = Seq(TxIn(commitmentA1.fundingInput, ByteVector.empty, 0, Scripts.witness2of2(Transactions.PlaceHolderSig, Transactions.PlaceHolderSig, randomKey().publicKey, randomKey().publicKey))), + txOut = Seq(commitmentA1.commitInput(fixtureParams.channelKeysA).txOut), lockTime = 0 ) Transactions.weight2fee(targetFeerate, dummySpliceTx.weight()) } - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -spliceFeeA, fundingAmountB = fundingB, targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = Nil, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -1891,7 +1892,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds in, which requires using an additional input. val additionalFundingA1 = 25_000.sat - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA1, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2144,7 +2145,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val subtractedFundingB = 398_000 sat val spliceOutputsA = List(TxOut(99_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(397_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = -subtractedFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) val fundingParamsB1 = bobParams.copy(localContribution = -subtractedFundingB, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA, walletA) @@ -2205,7 +2206,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, which creates two outputs (a shared output and a splice output). val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2272,7 +2273,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, but she doesn't have the same commitment index than Bob. val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) val fundingParamsA1 = spliceFixtureParams.fundingParamsA val fundingParamsB1 = spliceFixtureParams.fundingParamsB @@ -2606,7 +2607,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val wallet = new SingleKeyOnChainWallet() val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head - val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput, 0, randomKey().publicKey))) + val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(SharedFundingInput(previousCommitment.commitInput(params.channelKeysB), 0, randomKey().publicKey, previousCommitment.commitmentFormat))) val bob = params.spawnTxBuilderSpliceBob(fundingParams, previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2622,7 +2623,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head val fundingTx = Transaction(2, Nil, Seq(TxOut(50_000 sat, Script.pay2wpkh(randomKey().publicKey)), TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0) - val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty), 0, randomKey().publicKey) + val sharedInput = SharedFundingInput(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head), 0, randomKey().publicKey, previousCommitment.commitmentFormat) val bob = params.spawnTxBuilderSpliceBob(params.fundingParamsB.copy(sharedInput_opt = Some(sharedInput)), previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index 99be9c28b0..90a32eb35a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -190,12 +190,12 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest val commitTx = commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitFee = commitment.commitInput.txOut.amount - commitTx.txOut.map(_.amount).sum + val commitFee = commitment.capacity - commitTx.txOut.map(_.amount).sum probe.send(alice, CMD_FORCECLOSE(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FORCECLOSE]] // Forward the commit tx to the publisher. - val publishCommitTx = alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.commitInput.outPoint, "commit-tx", commitFee, None)) + val publishCommitTx = alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.fundingInput, "commit-tx", commitFee, None)) // Forward the anchor tx to the publisher. val publishAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideCommitTarget)) assert(publishAnchor.commitTx == commitTx) @@ -1071,13 +1071,13 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // Force-close channel and verify txs sent to watcher. val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest val commitTx = commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitFee = commitment.commitInput.txOut.amount - commitTx.txOut.map(_.amount).sum + val commitFee = commitment.capacity - commitTx.txOut.map(_.amount).sum assert(commitTx.txOut.size == 6) probe.send(alice, CMD_FORCECLOSE(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FORCECLOSE]] // We make the commit tx confirm because htlc txs have a relative delay. - alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.commitInput.outPoint, "commit-tx", commitFee, None)) + alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitment.fundingInput, "commit-tx", commitFee, None)) wallet.publishTransaction(commitTx).pipeTo(probe.ref) probe.expectMsg(commitTx.txid) generateBlocks(1) @@ -1616,7 +1616,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w case Transactions.DefaultCommitmentFormat => None case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx]) } - val mainTx_opt = if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (bob.stateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx]) val claimHtlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala index f64f9c7802..d00b3f429e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala @@ -112,7 +112,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = ConfirmationTarget.Absolute(nodeParams.currentBlockHeight + 12) val input = OutPoint(randomTxId(), 3) - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(anchorTx, null, null, confirmBefore) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -125,7 +125,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomTxId(), 3) - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Priority(ConfirmationPriority.Medium)) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -183,7 +183,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned].actor attempt2.expectMsgType[FinalTxPublisher.Publish] - val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimLocalAnchorTx(fundingKey, localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd3 = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd3 val attempt3 = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -206,7 +206,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt1 = factory.expectMsgType[FinalTxPublisherSpawned] attempt1.actor.expectMsgType[FinalTxPublisher.Publish] - val anchorTx = ClaimRemoteAnchorTx(fundingKey, remoteCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val anchorTx = ClaimRemoteAnchorTx(fundingKey, remoteCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd2 = PublishReplaceableTx(anchorTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd2 val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] @@ -248,7 +248,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomTxId(), 7) val preimage = randomBytes32() val remoteSig = randomBytes64() - val htlcTx = HtlcSuccessTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), 3, expiry, preimage, remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val htlcTx = HtlcSuccessTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), 3, expiry, preimage, remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(htlcTx, null, null, ConfirmationTarget.Absolute(expiry.blockHeight)) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] @@ -314,7 +314,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomTxId(), 7) val paymentHash = randomBytes32() val remoteSig = randomBytes64() - val htlcTx = HtlcTimeoutTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil), ByteVector.empty), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, CltvExpiry(nodeParams.currentBlockHeight), remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val htlcTx = HtlcTimeoutTx(localCommitKeys, InputInfo(input, TxOut(25_000 sat, Nil)), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, CltvExpiry(nodeParams.currentBlockHeight), remoteSig, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val cmd = PublishReplaceableTx(htlcTx, null, null, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index c44d613474..eef593fd1c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -297,22 +297,22 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val aliceChannelParams = Alice.channelParams .modify(_.initFeatures).setTo(aliceInitFeatures) .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000)) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(5000 sat) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))))) + val aliceCommitParams = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000)) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(5000 sat) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat) val bobChannelParams = Bob.channelParams .modify(_.initFeatures).setTo(bobInitFeatures) .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))) - .modify(_.maxHtlcValueInFlightMsat).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat) - .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(5000 sat) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(wallet.getP2wpkhPubkey(), 10 seconds))))) - val aliceCommitParams = CommitParams(aliceChannelParams.dustLimit, aliceChannelParams.htlcMinimum, aliceChannelParams.maxHtlcValueInFlightMsat, aliceChannelParams.maxAcceptedHtlcs, bobChannelParams.toRemoteDelay) - val bobCommitParams = CommitParams(bobChannelParams.dustLimit, bobChannelParams.htlcMinimum, bobChannelParams.maxHtlcValueInFlightMsat, bobChannelParams.maxAcceptedHtlcs, aliceChannelParams.toRemoteDelay) + val bobCommitParams = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) + .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat) + .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(5000 sat) ChannelParamsFixture(aliceChannelParams, aliceCommitParams, aliceInitFeatures, bobChannelParams, bobCommitParams, bobInitFeatures, channelType, alice2bob, bob2alice, aliceOpenReplyTo) } @@ -640,7 +640,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val commitTx = s2blockchain.expectFinalTxPublished("commit-tx").tx assert(commitTx.txid == closingState.commitments.latest.localCommit.txId) - val commitInput = closingState.commitments.latest.commitInput + val commitInput = closingState.commitments.latest.commitInput(s.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val publishedAnchorTx_opt = closingState.commitments.latest.commitmentFormat match { case DefaultCommitmentFormat => None 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 6987e09afa..155990c181 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 @@ -84,7 +84,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs())) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -94,7 +94,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -104,7 +104,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } @@ -126,7 +126,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(accept.channelType_opt.contains(ChannelTypes.Standard())) bob2alice.forward(alice, accept) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == DefaultCommitmentFormat) aliceOpenReplyTo.expectNoMessage() } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 2bcd596e15..e73ed7dc53 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -68,7 +68,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) } test("recv OpenChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => @@ -77,7 +77,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputs())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv OpenChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -86,7 +86,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => @@ -95,7 +95,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(StandardChannelType)) { f => @@ -104,7 +104,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(open.channelType_opt.contains(ChannelTypes.Standard())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.commitmentFormat == DefaultCommitmentFormat) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) } test("recv OpenChannel (invalid chain)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala index a921194a25..752b769125 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala @@ -151,7 +151,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF assert(aliceUpdate.shortChannelId == aliceChannelReady.alias_opt.value) assert(aliceUpdate.feeBaseMsat == 20.msat) assert(aliceUpdate.feeProportionalMillionths == 125) - assert(aliceCommitments.localChannelReserve == aliceCommitments.commitInput.txOut.amount / 100) + assert(aliceCommitments.localChannelReserve == aliceCommitments.capacity / 100) assert(aliceCommitments.localChannelReserve == aliceCommitments.remoteChannelReserve) val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(bobCommitments.commitment.shortChannelId_opt.nonEmpty) @@ -190,7 +190,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(aliceCommitments.commitment.shortChannelId_opt.isEmpty) assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceChannelReady.alias_opt.value) - assert(aliceCommitments.localChannelReserve == aliceCommitments.commitInput.txOut.amount / 100) + assert(aliceCommitments.localChannelReserve == aliceCommitments.capacity / 100) assert(aliceCommitments.localChannelReserve == aliceCommitments.remoteChannelReserve) val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest assert(bobCommitments.commitment.shortChannelId_opt.isEmpty) 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 b8e5ce5f5a..57749678fe 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 @@ -301,11 +301,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik TestHtlcs(Seq(adda1, adda2), Seq(addb1, addb2)) } - def spliceOutFee(f: FixtureParam, capacity: Satoshi): Satoshi = { + def spliceOutFee(f: FixtureParam, capacity: Satoshi, signedTx_opt: Option[Transaction] = None): Satoshi = { import f._ // When we only splice-out, the fees are paid by deducing them from the next funding amount. - val fundingTx = alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get + val fundingTx = signedTx_opt.getOrElse(alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get) val feerate = alice.nodeParams.onChainFeeConf.getFundingFeerate(alice.nodeParams.currentBitcoinCoreFeerates) val expectedMiningFee = Transactions.weight2fee(feerate, fundingTx.weight()) val actualMiningFee = capacity - alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.capacity @@ -3082,7 +3082,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectNoMessage(100 millis) awaitCond(bob.stateName == CLOSED) - checkPostSpliceState(f, spliceOutFee(f, capacity = 1_900_000.sat)) + checkPostSpliceState(f, spliceOutFee(f, capacity = 1_900_000.sat, signedTx_opt = Some(fundingTx2))) assert(Helpers.Closing.isClosed(alice.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[LocalClose])) assert(Helpers.Closing.isClosed(bob.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[RemoteClose])) } @@ -3447,7 +3447,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik test("put back watches after restart") { f => import f._ - val fundingTx0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get + val fundingTxId0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingTxId val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat, pushAmount = 10_000_000 msat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey))) checkWatchConfirmed(f, fundingTx1) @@ -3480,7 +3480,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2blockchain.expectMsgType[SetChannelId] alice2blockchain.expectWatchFundingConfirmed(fundingTx2.txid) alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid) - alice2blockchain.expectWatchFundingSpent(fundingTx0.txid) + alice2blockchain.expectWatchFundingSpent(fundingTxId0) alice2blockchain.expectNoMessage(100 millis) val bob2 = TestFSMRef(new Channel(bobNodeParams, bobKeys, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer) @@ -3488,7 +3488,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectMsgType[SetChannelId] bob2blockchain.expectWatchFundingConfirmed(fundingTx2.txid) bob2blockchain.expectWatchFundingSpent(fundingTx1.txid) - bob2blockchain.expectWatchFundingSpent(fundingTx0.txid) + bob2blockchain.expectWatchFundingSpent(fundingTxId0) bob2blockchain.expectNoMessage(100 millis) } @@ -3565,12 +3565,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val aliceCommitments1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments1.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(aliceCommitments1.channelParams, alice.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(alice.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments1.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(bobCommitments1.channelParams, bob.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(bob.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } // alice fulfills that HTLC in both commitments @@ -3579,12 +3579,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val aliceCommitments2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments2.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(aliceCommitments2.channelParams, alice.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(alice.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments2.active.foreach { c => val commitTx = c.fullySignedLocalCommitTx(bobCommitments2.channelParams, bob.underlyingActor.channelKeys) - Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(c.fundingInput -> c.commitInput(bob.underlyingActor.channelKeys).txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } resolveHtlcs(f, htlcs) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 386071d0d8..6d89e3dd4e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -224,7 +224,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] // The anchor outputs commitment format costs more fees for the funder (bigger commit tx + cost of anchor outputs) - assert(initialState.commitments.availableBalanceForSend < initialState.commitments.modify(_.channelParams.channelFeatures).setTo(ChannelFeatures()).availableBalanceForSend) + assert(initialState.commitments.availableBalanceForSend < initialState.commitments.modify(_.active).apply(_.map(_.modify(_.commitmentFormat).setTo(DefaultCommitmentFormat))).availableBalanceForSend) val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add @@ -941,8 +941,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.channelFlags.isEnabled) inside(bobListener.expectMsgType[LocalChannelUpdate]) { lcu => - assert(lcu.commitments.channelParams.localCommitParams.htlcMinimum == 1000.msat) - assert(lcu.commitments.channelParams.remoteCommitParams.htlcMinimum == 0.msat) + assert(lcu.commitments.latest.localCommitParams.htlcMinimum == 1000.msat) + assert(lcu.commitments.latest.remoteCommitParams.htlcMinimum == 0.msat) assert(lcu.channelUpdate.htlcMaximumMsat == 1000.msat) assert(lcu.channelUpdate.shortChannelId.isInstanceOf[RealShortChannelId]) assert(lcu.channelUpdate.channelFlags.isEnabled) @@ -3584,7 +3584,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val fundingTx = aliceState.commitments.latest.localFundingStatus.signedTx_opt.get val (blockHeight, txIndex) = (BlockHeight(400_000), 42) alice ! WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTx) - val realShortChannelId = RealShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitInput.outPoint.index.toInt) + val realShortChannelId = RealShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingInput.index.toInt) val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigsA.shortChannelId == realShortChannelId) // Alice updates her internal state wih the real scid. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 606ab2fb5b..792fea73db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -37,7 +37,7 @@ import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomBytes32, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomBytes32, randomKey} import org.scalatest.Inside.inside import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -292,10 +292,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(alice), 42))) } - def testMutualCloseBeforeConverge(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testMutualCloseBeforeConverge(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ val sender = TestProbe() - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) bob.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(minimum = FeeratePerKw(250 sat), slow = FeeratePerKw(250 sat))) // alice initiates a closing with a low fee alice ! CMD_CLOSE(sender.ref, None, Some(ClosingFeerates(FeeratePerKw(500 sat), FeeratePerKw(250 sat), FeeratePerKw(1000 sat)))) @@ -323,11 +323,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (mutual close before converging)") { f => - testMutualCloseBeforeConverge(f, ChannelFeatures(Features.StaticRemoteKey)) + testMutualCloseBeforeConverge(f, DefaultCommitmentFormat) } test("recv WatchFundingSpentTriggered (mutual close before converging, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testMutualCloseBeforeConverge(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testMutualCloseBeforeConverge(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (mutual close)") { f => @@ -666,10 +666,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - def testLocalCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testLocalCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) val listener = TestProbe() systemA.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed]) @@ -723,11 +723,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (local commit)") { f => - testLocalCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testLocalCommitTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (local commit, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testLocalCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testLocalCommitTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (local commit with multiple htlcs for the same payment)") { f => @@ -1413,7 +1413,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.SimpleClose)) { f => import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - assert(alice.commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey)) + assert(alice.commitments.latest.commitmentFormat == DefaultCommitmentFormat) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 2) // two main outputs @@ -1431,7 +1431,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] - assert(initialState.commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + assert(initialState.commitments.latest.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 4) // two main outputs + two anchors @@ -1447,10 +1447,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testRemoteCommitTxWithHtlcsConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice) @@ -1463,7 +1463,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the latest commit tx. val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs } @@ -1489,15 +1489,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment)") { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRemoteCommitTxWithHtlcsConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRemoteCommitTxWithHtlcsConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRemoteCommitTxWithHtlcsConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (remote commit) followed by htlc settlement", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -1589,10 +1589,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputSpent(htlcTimeout.input.outPoint) } - private def testNextRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, PublishedForceCloseTxs, Set[UpdateAddHtlc]) = { + private def testNextRemoteCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, PublishedForceCloseTxs, Set[UpdateAddHtlc]) = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // alice sends a first htlc to bob val (ra1, htlca1) = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice) @@ -1611,7 +1611,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the next commit tx. val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs } @@ -1623,7 +1623,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit)") { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit @@ -1646,7 +1646,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) assert(closingTxs.mainTx_opt.isEmpty) // with static_remotekey we don't claim out main output alice2relayer.expectNoMessage(100 millis) @@ -1665,7 +1665,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, tx)) alice2relayer.expectNoMessage(100 millis) @@ -1748,7 +1748,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val (bobCommitTx, closingTxs, _) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val (bobCommitTx, closingTxs, _) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1770,10 +1770,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputsSpent(htlcTimeoutTxs.map(_.input.outPoint)) } - private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Transaction = { + private def testFutureRemoteCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Transaction = { import f._ val oldStateData = alice.stateData - assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.channelFeatures == channelFeatures) + assert(oldStateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitmentFormat == commitmentFormat) // This HTLC will be fulfilled. val (ra1, htlca1) = addHtlc(25_000_000 msat, alice, bob, alice2bob, bob2alice) // These 2 HTLCs should timeout on-chain, but since alice lost data, she won't be able to claim them. @@ -1807,7 +1807,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) // bob is nice and publishes its commitment val bobCommitTx = bob.signCommitTx() - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs } @@ -1817,7 +1817,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit @@ -1834,7 +1834,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) // using option_static_remotekey alice doesn't need to sweep her output awaitCond(alice.stateName == CLOSING) alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) @@ -1844,7 +1844,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchTxConfirmedTriggered (future remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // alice is able to claim its main output val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") Transaction.correctlySpends(mainTx.tx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -1861,7 +1861,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1879,12 +1879,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseFixture(bobRevokedTxs: Seq[RevokedCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) - private def prepareRevokedClose(f: FixtureParam, channelFeatures: ChannelFeatures): RevokedCloseFixture = { + private def prepareRevokedClose(f: FixtureParam, commitmentFormat: CommitmentFormat): RevokedCloseFixture = { import f._ // Bob's first commit tx doesn't contain any htlc val bobCommit1 = RevokedCommit(bob.signCommitTx(), Nil) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 4) // 2 main outputs + 2 anchors case DefaultCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 2) // 2 main outputs } @@ -1900,7 +1900,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit2.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 6) case DefaultCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 4) } @@ -1916,7 +1916,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit3.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 8) case DefaultCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 6) } @@ -1930,7 +1930,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit4.commitTx.txOut.size) - channelFeatures.commitmentFormat match { + commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 4) case DefaultCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 2) } @@ -1940,11 +1940,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseTxs(mainTx_opt: Option[Transaction], mainPenaltyTx: Transaction, htlcPenaltyTxs: Seq[Transaction]) - private def setupFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): (Transaction, RevokedCloseTxs) = { + private def setupFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, RevokedCloseTxs) = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs val bobRevokedTx = revokedCloseFixture.bobRevokedTxs(1).commitTx @@ -1954,7 +1954,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedTx) - if (!channelFeatures.paysDirectlyToWallet) { + if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) { assert(rvk.localOutput_opt.nonEmpty) } assert(rvk.remoteOutput_opt.nonEmpty) @@ -1962,7 +1962,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs - val mainTx_opt = if (!channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val mainPenaltyTx = alice2blockchain.expectFinalTxPublished("main-penalty") Transaction.correctlySpends(mainPenaltyTx.tx, bobRevokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val htlcPenaltyTxs = (0 until 2).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) @@ -1971,8 +1971,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice spends all outpoints of the revoked tx, except her main output when it goes directly to our wallet val spentOutpoints = mainTx_opt.map(_.input) ++ Seq(mainPenaltyTx.input) ++ htlcPenaltyTxs.map(_.input) - channelFeatures.commitmentFormat match { - case DefaultCommitmentFormat if channelFeatures.paysDirectlyToWallet => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet + commitmentFormat match { + case DefaultCommitmentFormat if mainTx_opt.isEmpty => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet case DefaultCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 2) // we don't claim the anchors } @@ -1985,10 +1985,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with (bobRevokedTx, RevokedCloseTxs(mainTx_opt.map(_.tx), mainPenaltyTx.tx, htlcPenaltyTxs.map(_.tx))) } - private def testFundingSpentRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + private def testFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, channelFeatures) + val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, commitmentFormat) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobRevokedTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the revoked commit @@ -2005,20 +2005,20 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (one revoked tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey)) + testFundingSpentRevokedTx(f, DefaultCommitmentFormat) } test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testFundingSpentRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testFundingSpentRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchFundingSpentTriggered (multiple revoked tx)") { f => import f._ - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey)) + val revokedCloseFixture = prepareRevokedClose(f, DefaultCommitmentFormat) assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTx.txid).toSet.size == revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct def broadcastBobRevokedTx(revokedTx: Transaction, htlcCount: Int, revokedCount: Int): RevokedCloseTxs = { @@ -2055,10 +2055,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - def testInputRestoredRevokedTx(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testInputRestoredRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, channelFeatures) + val (bobRevokedTx, closingTxs) = setupFundingSpentRevokedTx(f, commitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -2081,21 +2081,21 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv INPUT_RESTORED (one revoked tx)") { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey)) + testInputRestoredRevokedTx(f, DefaultCommitmentFormat) } test("recv INPUT_RESTORED (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testInputRestoredRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv INPUT_RESTORED (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testInputRestoredRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - def testRevokedHtlcTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + def testRevokedHtlcTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, channelFeatures) - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) + val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) @@ -2105,7 +2105,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedCommit.commitTx) - if (channelFeatures.paysDirectlyToWallet) { + if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) { assert(rvk.localOutput_opt.isEmpty) } else { assert(rvk.localOutput_opt.nonEmpty) @@ -2115,7 +2115,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs and watches outputs - val mainTx_opt = if (!channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None val mainPenalty = alice2blockchain.expectFinalTxPublished("main-penalty") val htlcPenalty = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) alice2blockchain.expectWatchTxConfirmed(rvk.commitTx.txid) @@ -2176,22 +2176,22 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRevokedHtlcTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRevokedHtlcTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRevokedHtlcTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked aggregated htlc tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ // bob publishes one of his revoked txs - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) @@ -2259,7 +2259,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes one of his revoked txs. alice.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2_500 sat))) - val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) val commitTx = bobRevokedCommit.commitTx alice ! WatchFundingSpentTriggered(commitTx) @@ -2359,10 +2359,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - private def testRevokedTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { + private def testRevokedTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.channelParams.channelFeatures == channelFeatures) - val initOutputCount = channelFeatures.commitmentFormat match { + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + val initOutputCount = commitmentFormat match { case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 4 case DefaultCommitmentFormat => 2 } @@ -2409,15 +2409,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs)") { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey)) + testRevokedTxConfirmed(f, DefaultCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + testRevokedTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) + testRevokedTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv ChannelReestablish") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala index c1b644f42a..9313a2669a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala @@ -134,14 +134,13 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory val currentChannels = Seq( Peer.ChannelInfo(TestProbe().ref, SHUTDOWN, DATA_SHUTDOWN(commitments(isOpener = true), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None))), Peer.ChannelInfo(TestProbe().ref, NEGOTIATING, DATA_NEGOTIATING(commitments(), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), List(Nil), None)), - Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil), ByteVector.empty), Transaction(2, Nil, Nil, 0), None) :: Nil)), + Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil)), Transaction(2, Nil, Nil, 0), None) :: Nil)), Peer.ChannelInfo(TestProbe().ref, WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments(), ChannelReestablish(randomBytes32(), 0, 0, randomKey(), randomKey().publicKey))), ) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, currentChannels) val result = peer.expectMessageType[SpawnChannelNonInitiator] assert(!result.localParams.isChannelOpener) assert(result.localParams.paysCommitTxFees) - assert(result.localParams.maxHtlcValueInFlightMsat == UInt64(500_000_000)) assert(result.addFunding_opt.map(_.fundingAmount).contains(250_000 sat)) assert(result.addFunding_opt.flatMap(_.rates_opt).contains(TestConstants.defaultLiquidityRates)) } @@ -155,7 +154,6 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory openChannelInterceptor ! openChannelInitiator val result = peer.expectMessageType[SpawnChannelInitiator] assert(result.cmd == openChannelInitiator.open) - assert(result.localParams.maxHtlcValueInFlightMsat == UInt64(450_000_000)) } test("continue channel open if no interceptor plugin registered and pending channels rate limiter accepts it") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala index e4ae7b3b8e..713b399db9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala @@ -73,7 +73,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac val probe = TestProbe[PendingChannelsRateLimiter.Response]() val nodeParams = TestConstants.Alice.nodeParams.copy(channelConf = TestConstants.Alice.nodeParams.channelConf.copy(maxPendingChannelsPerPeer = maxPendingChannelsPerPeer, maxTotalPendingChannelsPrivateNodes = maxTotalPendingChannelsPrivateNodes, channelOpenerWhitelist = Set(peerOnWhitelistAtLimit))) val tx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000") - val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), ByteVector.empty), tx, None) + val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), tx, None) val channelsOnWhitelistAtLimit: Seq[PersistentChannelData] = Seq( DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerOnWhitelistAtLimit, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))), DATA_WAIT_FOR_CHANNEL_READY(commitments(peerOnWhitelistAtLimit, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), @@ -91,13 +91,13 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac ) val channelsBelowLimit2 = Seq( DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(peerBelowLimit2, channelIdBelowLimit2), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(peerBelowLimit2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(peerBelowLimit2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), DATA_SHUTDOWN(commitments(peerBelowLimit2, randomBytes32()), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None)), DATA_CLOSING(commitments(peerBelowLimit2, randomBytes32()), BlockHeight(0), ByteVector.empty, List(), List(closingTx)) ) val privateChannels = Seq( DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(privatePeer1, channelIdPrivate1), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(privatePeer2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(privatePeer2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), ) val initiatorChannels = Seq( DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, randomBytes32(), isOpener = true), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, randomBytes64()))), @@ -320,7 +320,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac val channels = Seq( DATA_WAIT_FOR_CHANNEL_READY(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), - DATA_NORMAL(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, None, None, None, SpliceStatus.NoSplice), + DATA_NORMAL(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None), DATA_SHUTDOWN(commitments(randomKey().publicKey, randomBytes32()), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None)), DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(randomKey().publicKey, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))), ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala index 8d704c66c4..ab4bdca2ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala @@ -166,7 +166,7 @@ class SwitchboardSpec extends TestKitBaseClass with AnyFunSuiteLike { def dummyDataNormal(remoteNodeId: PublicKey, capacity: Satoshi): DATA_NORMAL = { val data = ChannelCodecsSpec.normal .modify(_.commitments.channelParams.remoteParams.nodeId).setTo(remoteNodeId) - .modify(_.commitments.active).apply(_.map(_.modify(_.localCommit.input.txOut.amount).setTo(capacity))) + .modify(_.commitments.active).apply(_.map(_.modify(_.fundingAmount).setTo(capacity))) assert(data.remoteNodeId == remoteNodeId) assert(data.commitments.capacity == capacity) data diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index b74a56dc07..8f96c7918a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -25,7 +25,6 @@ import fr.acinq.eclair.balance.CheckBalance import fr.acinq.eclair.balance.CheckBalance.{GlobalBalance, MainAndHtlcBalance} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{ShaChain, Sphinx} import fr.acinq.eclair.db.OfferData @@ -33,7 +32,7 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.io.Peer.PeerInfo import fr.acinq.eclair.payment.{Invoice, PaymentSettlingOnChain} import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.transactions.{CommitmentSpec, IncomingHtlc, OutgoingHtlc} +import fr.acinq.eclair.transactions.{CommitmentSpec, IncomingHtlc, OutgoingHtlc, Transactions} import fr.acinq.eclair.wire.internal.channel.ChannelCodecs import fr.acinq.eclair.wire.protocol.OfferTypes.{Offer, OfferTlv} import fr.acinq.eclair.wire.protocol._ @@ -122,10 +121,12 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val probe = TestProbe()(system) val dummyPublicKey = PrivateKey(hex"0101010101010101010101010101010101010101010101010101010101010101").publicKey val dummyBytes32 = ByteVector32(hex"0202020202020202020202020202020202020202020202020202020202020202") - val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), 546 sat, UInt64(Long.MaxValue), Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(dummyPublicKey, 546 sat, UInt64.MaxValue, Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) - val commitmentInput = Funding.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), TxId(dummyBytes32), commitmentInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(1000 sat), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val localCommitParams = CommitParams(546 sat, 1 msat, UInt64(Long.MaxValue), 50, CltvExpiryDelta(144)) + val remoteChannelParams = RemoteChannelParams(dummyPublicKey, Some(1000 sat), dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) + val remoteCommitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) + val commitmentInput = Transactions.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), TxId(dummyBytes32), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 50_000_000 msat, 100_000_000 msat), TxId(dummyBytes32), dummyPublicKey) val channelInfo = RES_GET_CHANNEL_INFO( PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), @@ -136,7 +137,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat Commitments( ChannelParams(dummyBytes32, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = true)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, commitmentInput.outPoint, 150_000 sat, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), inactive = Nil, Right(dummyPublicKey), ShaChain.init, @@ -145,7 +146,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat ShortIdAliases(Alias(42), None), None, ChannelUpdate(ByteVector64(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 100 msat, 0, 2_000_000 msat), - None, None, None, SpliceStatus.NoSplice + SpliceStatus.NoSplice, None, None, None ) ) val expected = @@ -163,24 +164,14 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "localParams": { | "nodeId": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "fundingKeyPath": [42], - | "dustLimit": 546, - | "maxHtlcValueInFlightMsat": 9223372036854775807, | "initialRequestedChannelReserve_opt": 1000, - | "htlcMinimum": 1, - | "toRemoteDelay": 144, - | "maxAcceptedHtlcs": 50, | "isChannelOpener": true, | "paysCommitTxFees" : true, | "initFeatures": { "activated": {}, "unknown": [] } | }, | "remoteParams": { | "nodeId": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - | "dustLimit": 546, - | "maxHtlcValueInFlightMsat": 18446744073709551615, | "initialRequestedChannelReserve_opt": 1000, - | "htlcMinimum": 1, - | "toRemoteDelay": 144, - | "maxAcceptedHtlcs": 50, | "revocationBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "paymentBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", | "delayedPaymentBasepoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", @@ -201,22 +192,34 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "active": [ | { | "fundingTxIndex": 0, - | "fundingTx": { "outPoint": "0202020202020202020202020202020202020202020202020202020202020202:0", "amountSatoshis": 150000 }, + | "fundingInput": "0202020202020202020202020202020202020202020202020202020202020202:0", + | "fundingAmount": 150000, | "localFunding": { "status":"unconfirmed" }, | "remoteFunding": { "status":"locked" }, + | "commitmentFormat": "legacy", + | "localCommitParams": { + | "dustLimit": 546, + | "htlcMinimum": 1, + | "maxHtlcValueInFlight": 9223372036854775807, + | "maxAcceptedHtlcs": 50, + | "toSelfDelay": 144 + | }, | "localCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 100000000, "toRemote": 50000000 }, | "txId": "0202020202020202020202020202020202020202020202020202020202020202", - | "input": { - | "outPoint":"0202020202020202020202020202020202020202020202020202020202020202:0", - | "amountSatoshis": 150000 - | }, | "remoteSig": { | "sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | }, | "htlcRemoteSigs": [] | }, + | "remoteCommitParams": { + | "dustLimit": 546, + | "htlcMinimum": 1, + | "maxHtlcValueInFlight": 18446744073709551615, + | "maxAcceptedHtlcs": 50, + | "toSelfDelay": 144 + | }, | "remoteCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 50000000, "toRemote": 100000000 }, @@ -296,7 +299,6 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val inputInfo = InputInfo( outPoint = OutPoint(TxHash.fromValidHex("345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 42), txOut = TxOut(456651 sat, hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63"), - unusedRedeemScript = ByteVector.empty, ) JsonSerializers.serialization.write(inputInfo)(JsonSerializers.formats) shouldBe """{"outPoint":"9f0b9c0ce92c175ca4e78acfd13a718099c73818b6d3140cfe6f04ec052b5b34:42","amountSatoshis":456651}""" } @@ -413,7 +415,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat test("TransactionWithInputInfo serializer") { // the input info is ignored when serializing to JSON - val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil), ByteVector.empty) + val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil)) val htlcSuccessTx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800") val htlcSuccessTxInfo = UnsignedHtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, CltvExpiry(1105), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index f9c9c77bbd..785cd099d8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -35,7 +35,7 @@ import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFromUpdate} import fr.acinq.eclair.router.BlindedRouteCreation import fr.acinq.eclair.router.Router.{NodeHop, Route} -import fr.acinq.eclair.transactions.Transactions.InputInfo +import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} import fr.acinq.eclair.wire.protocol._ @@ -745,23 +745,24 @@ object PaymentPacketSpec { def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat, channelFeatures: ChannelFeatures = ChannelFeatures(), announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = testCapacity * 0.01 - val localChannelParams = LocalChannelParams(null, null, null, UInt64.MaxValue, Some(channelReserve), null, null, 0, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) - val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, None) + val localChannelParams = LocalChannelParams(null, null, Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), null, null, null, null, null, None) + val commitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)) val fundingTx = Transaction(2, Nil, Seq(TxOut(testCapacity, Nil)), 0) - val commitInput = InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, ByteVector.empty) - val localCommit = LocalCommit(0, null, randomTxId(), commitInput, IndividualSignature(ByteVector64.Zeroes), Nil) + val localCommit = LocalCommit(0, null, randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, null, randomTxId(), randomKey().publicKey) val localChanges = LocalChanges(Nil, Nil, Nil) val remoteChanges = RemoteChanges(Nil, Nil, Nil) val localFundingStatus = announcement_opt match { - case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) + case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx.txOut.head, ann.shortChannelId, None, None) case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } val channelFlags = ChannelFlags(announceChannel = announcement_opt.nonEmpty) + val commitmentFormat = DefaultCommitmentFormat new Commitments( ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(localChanges, remoteChanges, 0, 0), - List(Commitment(0, 0, null, localFundingStatus, RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(fundingTx, 0), testCapacity, randomKey().publicKey, localFundingStatus, RemoteFundingStatus.Locked, commitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index d209eb70ab..fe15c42619 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -511,7 +511,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit val normal = ChannelCodecsSpec.makeChannelDataNormal(htlc_bc, origins) // NB: this isn't actually a revoked commit tx, but we don't check that here, if the channel says it's a revoked // commit we accept it as such, so it simplifies the test. - val revokedCommitTx = Transaction(2, Seq(TxIn(normal.commitments.latest.localCommit.input.outPoint, Nil, 0)), Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey))), 0) + val revokedCommitTx = Transaction(2, Seq(TxIn(normal.commitments.latest.fundingInput, Nil, 0)), Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey))), 0) val rcp = RevokedCommitPublished(revokedCommitTx, Some(OutPoint(revokedCommitTx, 0)), None, Set.empty, Set.empty, Map(revokedCommitTx.txIn.head.outPoint -> revokedCommitTx)) DATA_CLOSING(normal.commitments, BlockHeight(0), Script.write(Script.pay2wpkh(randomKey().publicKey)), mutualCloseProposed = Nil, revokedCommitPublished = List(rcp)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala index f384a82ce8..00569b880e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala @@ -166,9 +166,9 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { def makeChannelData(htlcMinimum: MilliSatoshi = 1 msat, localChanges: LocalChanges = LocalChanges(Nil, Nil, Nil)): DATA_NORMAL = { val commitments = CommitmentsSpec.makeCommitments(500_000_000 msat, 500_000_000 msat, nodeParams.nodeId, remoteNodeId, announcement_opt = None) - .modify(_.channelParams.remoteParams.htlcMinimum).setTo(htlcMinimum) + .modify(_.active).apply(_.map(_.modify(_.remoteCommitParams.htlcMinimum).setTo(htlcMinimum))) .modify(_.changes.localChanges).setTo(localChanges) - DATA_NORMAL(commitments, ShortIdAliases(Alias(42), None), None, null, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, ShortIdAliases(Alias(42), None), None, null, SpliceStatus.NoSplice, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 2856f0f48c..db201cebb8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -1102,7 +1102,7 @@ class RouterSpec extends BaseRouterSpec { } // The second channel is announced and moves from the private channels to the public channels. - val fundingConfirmed = LocalFundingStatus.ConfirmedFundingTx(Transaction(2, Nil, TxOut(100_000 sat, Nil) :: Nil, 0), scid2, None, None) + val fundingConfirmed = LocalFundingStatus.ConfirmedFundingTx(TxOut(100_000 sat, Nil), scid2, None, None) val commitments3 = commitments2.updateLocalFundingStatus(commitments2.latest.fundingTxId, fundingConfirmed, None)(akka.event.NoLogging).toOption.get._1 assert(commitments3.channelId == commitments2.channelId) sender.send(router, LocalChannelUpdate(sender.ref, commitments3.channelId, aliases2, x.publicKey, Some(AnnouncedCommitment(commitments3.latest.commitment, announcement2)), update2, commitments3)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 1d2ad03c59..1931aa8b1b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -20,13 +20,11 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi, SatoshiLong, Script, Transaction} import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.channel.ChannelFeatures -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants} +import fr.acinq.eclair.{ChannelTypeFeature, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants} import grizzled.slf4j.Logging import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -37,10 +35,17 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { // @formatter:off def filename: String - def channelFeatures: ChannelFeatures - val commitmentFormat = channelFeatures.commitmentFormat + def channelFeatures: Set[ChannelTypeFeature] // @formatter:on + val commitmentFormat: CommitmentFormat = if (channelFeatures.contains(Features.AnchorOutputsZeroFeeHtlcTx)) { + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + } else if (channelFeatures.contains(Features.AnchorOutputs)) { + UnsafeLegacyAnchorOutputsCommitmentFormat + } else { + DefaultCommitmentFormat + } + val tests = { val tests = collection.mutable.HashMap.empty[String, Map[String, String]] val current = collection.mutable.HashMap.empty[String, String] @@ -99,7 +104,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_pubkey = funding_privkey.publicKey val per_commitment_point = PublicKey(hex"025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486") val htlc_privkey = ChannelKeys.derivePerCommitmentKey(payment_basepoint_secret, per_commitment_point) - val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.contains(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey val delayed_payment_privkey = ChannelKeys.derivePerCommitmentKey(delayed_payment_basepoint_secret, per_commitment_point) val revocation_pubkey = PublicKey(hex"0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19") val feerate_per_kw = 15000 @@ -116,7 +121,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val funding_privkey = PrivateKey(hex"1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301") val funding_pubkey = funding_privkey.publicKey val htlc_privkey = ChannelKeys.derivePerCommitmentKey(payment_basepoint_secret, Local.per_commitment_point) - val payment_privkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey + val payment_privkey = if (channelFeatures.contains(Features.StaticRemoteKey)) payment_basepoint_secret else htlc_privkey } // Keys used by the local node to spend outputs of its local commitment. @@ -144,8 +149,8 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val fundingAmount = fundingTx.txOut(0).amount logger.info(s"# funding-tx: $fundingTx}") - val fundingScript = Funding.makeFundingScript(Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat).asInstanceOf[RedeemInfo.SegwitV0] - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat) + val fundingScript = makeFundingScript(Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat).asInstanceOf[RedeemInfo.SegwitV0] + val commitmentInput = makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat) val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, localIsChannelOpener = true, Local.payment_basepoint, Remote.payment_basepoint) assert(obscured_tx_number == (0x2bb038521914L ^ 42L)) @@ -442,27 +447,27 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { class DefaultCommitmentTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-default-commitment-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures() + override def channelFeatures: Set[ChannelTypeFeature] = Set.empty // @formatter:on } class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey) // @formatter:on } class AnchorOutputsTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputs) // @formatter:on } class AnchorOutputsZeroFeeHtlcTxTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-zero-fee-htlc-tx-format.txt" - override def channelFeatures: ChannelFeatures = ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) + override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) // @formatter:on } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index d8ee07c2cc..a8ae00261e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -24,7 +24,6 @@ import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.transactions.CommitmentOutput.OutHtlc @@ -251,10 +250,10 @@ class TransactionsSpec extends AnyFunSuite with Logging { val walletPriv = randomKey() val walletPub = walletPriv.publicKey val finalPubKeyScript = Script.write(Script.pay2wpkh(walletPub)) - val fundingInfo = Funding.makeFundingScript(localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) + val fundingInfo = makeFundingScript(localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) val fundingTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(Btc(1), fundingInfo.pubkeyScript) :: Nil, lockTime = 0) val fundingTxOutpoint = OutPoint(fundingTx.txid, 0) - val commitInput = Funding.makeFundingInputInfo(fundingTxOutpoint.txid, fundingTxOutpoint.index.toInt, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) + val commitInput = makeFundingInputInfo(fundingTxOutpoint.txid, fundingTxOutpoint.index.toInt, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, commitmentFormat) val paymentPreimages = Seq(randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32()) val paymentPreimageMap = paymentPreimages.map(p => sha256(p) -> p).toMap @@ -316,6 +315,9 @@ class TransactionsSpec extends AnyFunSuite with Logging { commitTx } commitTx.correctlySpends(Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + // We check the expected weight of the commit input: + val commitInputWeight = commitTx.copy(txIn = Seq(commitTx.txIn.head, commitTx.txIn.head)).weight() - commitTx.weight() + checkExpectedWeight(commitInputWeight, commitmentFormat.fundingInputWeight, commitmentFormat) val htlcTxs = makeHtlcTxs(commitTx, outputs, commitmentFormat) val expiries = htlcTxs.map(tx => tx.htlcId -> tx.htlcExpiry.toLong).toMap val htlcSuccessTxs = htlcTxs.collect { case tx: UnsignedHtlcSuccessTx => tx } @@ -587,7 +589,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { theirHtlcPublicKey = remoteHtlcPriv.publicKey, revocationPublicKey = localRevocationPriv.publicKey, ) - val commitInput = Funding.makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) + val commitInput = makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) // htlc1 and htlc2 are two regular incoming HTLCs with different amounts. // htlc2 and htlc3 have the same amounts and should be sorted according to their scriptPubKey @@ -648,7 +650,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } test("find our output in closing tx") { - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val commitInput = makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val localPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) val remotePubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index ba6d64e1a4..b34f084a56 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -17,11 +17,10 @@ package fr.acinq.eclair.wire.internal.channel import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, Satoshi, SatoshiLong, Transaction, TxId} +import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, SatoshiLong, Transaction, TxId} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature -import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.crypto.ShaChain @@ -91,7 +90,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // and re-encode it with the new codec val bin_new = ByteVector(channelDataCodec.encode(data_new).require.toByteVector.toArray) // data should now be encoded under the new format - assert(bin_new.startsWith(hex"04000a")) + assert(bin_new.startsWith(hex"05000101")) // now let's decode it again val data_new2 = channelDataCodec.decode(bin_new.toBitVector).require.value // data should match perfectly @@ -175,7 +174,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // and we encode with the new codec val newBin = channelDataCodec.encode(decoded1).require.bytes // make sure that encoding used the new codec - assert(newBin.startsWith(hex"0400")) + assert(newBin.startsWith(hex"0500")) // make sure that round-trip yields the same data val decoded2 = channelDataCodec.decode(newBin.bits).require.value assert(decoded1 == decoded2) @@ -241,7 +240,8 @@ class ChannelCodecsSpec extends AnyFunSuite { val remoteSig = newnormal.commitments.latest.localCommit.remoteSig.asInstanceOf[ChannelSpendSignature.IndividualSignature] val commitTxId = newnormal.commitments.latest.localCommit.txId assert(testCase.commitTx.txid == commitTxId) - val commitTx = CommitTx(newnormal.commitments.latest.localCommit.input, testCase.commitTx) + val commitInput = Transactions.makeFundingInputInfo(newnormal.commitments.latest.fundingTxId, newnormal.commitments.latest.fundingInput.index.toInt, newnormal.commitments.latest.capacity, testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, newnormal.commitments.latest.commitmentFormat) + val commitTx = CommitTx(commitInput, testCase.commitTx) assert(commitTx.checkRemoteSig(testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, remoteSig)) } } @@ -255,12 +255,7 @@ object ChannelCodecsSpec { val localChannelParams: LocalChannelParams = LocalChannelParams( nodeKeyManager.nodeId, fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)), - dustLimit = Satoshi(546), - maxHtlcValueInFlightMsat = UInt64(50_000_000), initialRequestedChannelReserve_opt = Some(10000 sat), - htlcMinimum = 10000 msat, - toRemoteDelay = CltvExpiryDelta(144), - maxAcceptedHtlcs = 50, upfrontShutdownScript_opt = None, walletStaticPaymentBasepoint = None, isChannelOpener = true, @@ -268,12 +263,7 @@ object ChannelCodecsSpec { initFeatures = Features.empty) val remoteChannelParams: RemoteChannelParams = RemoteChannelParams( nodeId = randomKey().publicKey, - dustLimit = 546 sat, - maxHtlcValueInFlightMsat = UInt64(5000000), initialRequestedChannelReserve_opt = Some(10000 sat), - htlcMinimum = 5000 msat, - toRemoteDelay = CltvExpiryDelta(144), - maxAcceptedHtlcs = 50, revocationBasepoint = PrivateKey(ByteVector.fill(32)(2)).publicKey, paymentBasepoint = PrivateKey(ByteVector.fill(32)(3)).publicKey, delayedPaymentBasepoint = PrivateKey(ByteVector.fill(32)(4)).publicKey, @@ -310,22 +300,22 @@ object ChannelCodecsSpec { val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut.head.amount val fundingTxIndex = 0 - val localFundingPubKey = channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParams.fundingKeyPath).fundingKey(fundingTxIndex = 0).publicKey val remoteFundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat) val remoteSig = ByteVector64(hex"2148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d") - val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), TxId.fromValidHex("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), commitmentInput, IndividualSignature(remoteSig), Nil) + val localCommitParams = CommitParams(546 sat, 10_000 msat, UInt64(50_000_000), 50, CltvExpiryDelta(144)) + val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), TxId.fromValidHex("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), IndividualSignature(remoteSig), Nil) + val remoteCommitParams = CommitParams(546 sat, 5_000 msat, UInt64(5_000_000), 50, CltvExpiryDelta(144)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(_.opposite).toSet, FeeratePerKw(1500 sat), 50000 msat, 700000 msat), TxId.fromValidHex("0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey) val channelId = htlcs.headOption.map(_.add.channelId).getOrElse(ByteVector32.Zeroes) val channelFlags = ChannelFlags(announceChannel = true) val commitments = Commitments( ChannelParams(channelId, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32, remoteNextHtlcId = 4), - Seq(Commitment(fundingTxIndex, 0, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, localCommit, remoteCommit, None)), + Seq(Commitment(fundingTxIndex, 0, OutPoint(fundingTx.txid, 0), fundingAmount, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), remoteNextCommitInfo = Right(randomKey().publicKey), remotePerCommitmentSecrets = ShaChain.init, originChannels = origins) - DATA_NORMAL(commitments, ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, channelUpdate, None, None, None, SpliceStatus.NoSplice) + DATA_NORMAL(commitments, ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, channelUpdate, SpliceStatus.NoSplice, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala index a4d9772623..48a558a8ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2Spec.scala @@ -1,9 +1,9 @@ package fr.acinq.eclair.wire.internal.channel.version2 import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction} -import fr.acinq.eclair.Features import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.channel.{ChannelConfig, ChannelDataWithCommitments, ChannelFeatures} +import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat} import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.Codecs._ import fr.acinq.eclair.wire.internal.channel.version2.ChannelCodecs2.channelDataCodec import org.scalatest.funsuite.AnyFunSuite @@ -30,18 +30,24 @@ class ChannelCodecs2Spec extends AnyFunSuite { val commitments = channelDataCodec.decode(dataNormal.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) + assert(commitments.latest.commitmentFormat == DefaultCommitmentFormat) } { val staticRemoteKeyChannel = hex"00020000000303af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000090ef5e61dc12e5215dfcf8a1263f66b998a162fd80ea9aabf4fd1483d63fcd68280000001000000000000044c0000000008f0d1800000000000002710000000000000000000900064ff160014170e6de64a6d55c73f3ff657ef441d43739837ab028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a7022dc9905a27397dedf60ef915579f2464c8fa930fae14adb6563e5559905067e5028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b12032ed22e583b835accc8b4b5bfc94331ce06b07c31952551fd9520e126b1ed43c703da7493b310a19286c452e3d1f8ee66afaf6f1656736f6464d00deedd0b58585b00000003026982000000000000000000000000002710000000002faf0800000000000bebc20024f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000002b40420f000000000022002065adbae4db91fa9069e2c8dce1f716c65e6dbc302229772d62305bd2e0e6494e475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752aefd015b02000000000101f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000000036a2ab8002400d030000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3b8180c00000000002200204b354f6e432cae820a572c8294423f4bc6a5b9a2509d3de22b93c6316b59e14b0400483045022100fe9a0106b51293216f200a5a0b17a783f7ace4edbd71b644dee3e25c2688834802203a1087649b2cba80f5a634c1c67fbce4355f6c5605dd873f12d0399a542e363d01483045022100e71334e385755ab65b7ac6be2709a0be39f98fde6a6c10a895fd6f90ecd3fd7d022036550ecd40ed942f0a4ae3d0e29b6142a3cc4492eb73c3a62ced6d934e48584d01475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752aeac67892000000000000000000000000000002710000000000bebc200000000002faf080018fa9f3051c52bba85fc20763e8835aed58c763f52a1647c90cc850aeb62ac3d02eada0cd5618069ed848f3975a631f02c87beb9968df8bd99511fa08f75d64529000000000000000000000000000000000000000000000000000000000000ff03b960d87d264cc2c99f71ed6ba6dd1bdb06c03666f76d90a1670a73dc468428e924f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f000000002b40420f000000000022002065adbae4db91fa9069e2c8dce1f716c65e6dbc302229772d62305bd2e0e6494e475221023da0b6ec447fbda75506f743247a7fb6f8e29c1e18cfff53f2f9136801c617a72103a62130179b4cb0451ddbcd4480c326d5343a691bc01e07bb6eee35fc91a7601752ae000000f452ff0b3c6f363530f2a203e8fd6fcd88d177a2dac14b4b045abead090e4f4f061a8000002a00000000884a5e841be07f8612321ece5e40b39d715ffe94978c06cf3bb3af1a0cadcefc4b4aaade3deb191662f00b304edeae18ae8c80ed3dc505e8c78876b6fb61682fcb06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a0000629dbe540101009000000000000003e8000854d00000000a000000003b9aca000000" val commitments = channelDataCodec.decode(staticRemoteKeyChannel.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) - assert(commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey)) + assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) + assert(commitments.latest.commitmentFormat == DefaultCommitmentFormat) } { val anchorOutputsChannel = hex"00020000000703af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000094b82816a3f15ab3e324ddf6f0f5a116cef8c3200a353240db684337d2dcaa81e80000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff160014f09f6f93de60c6af417ed31d52c2c883b6113f260000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000225982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e02e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d5002eb112dd8d61a02bf3434d5417e1020ed2d951e75bd1e14615d473dd5f1991b4402ca8b6891d6a53aea035259fa0d57a1e672d3cc59a2c696a96247d3557ee726cd030f2aa0ca0ae0e00d6da847d897cf7288ae286dd12037f06250937dc7b37fe0b5028d59e68c5799980a98b120acb891dabfd091e80c6596b0c2a7de57a298d48512000000032259820000000000000000000000000009c4000000002faf0800000000000bebc2002432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352aefd01bc0200000000010132e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000000089273f80044a010000000000002200202102815a0b14d50a2a8c028cd47413e743f02ad5460151bd4ae5930639cbbd484a010000000000002200204e81bd6470c45c349068c2b0ad544934b718ef49c1615f2a0b0cd8766400b562400d0300000000002200208eadab92c95168438acfb73f2d3d92f56d6fc30d3f79fc4527f4e77df88b8add72270c0000000000220020895c0fef7efb7aa0e4805381434363b30386d31f6cb621abb0ca4a52b269fbc304004830450221009f09a584b1af9ba86f1618aad1ca9817324f97c3d3789b2490c3b9435c7bf1dd02205e4dcca3d7937f6b80a0ed39084e4ea9e9c531b80698ebd0c116fd9319a0db1201473044022001e4104ec25ba9bc43bf3f623d6e98f371434442b134255667ddeedd9cd9cd1b02201f057d2c15ee4e5d62a73f1db262846ca7ad35deaed2496abdb5b16cd91c5cb80147522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae25899520000000000000000000000000000009c4000000000bebc200000000002faf0800acdb0bf90ed7350d429c2d70fce66300ac9b27d8dbb3bceb5bd2cc8bd443938f03f54d00601f1b95e82836540d760ab6b38bc6fc1701df5d5e3b410f1936212d54000000000000000000000000000000000000000000000000000000000000ff03658b1e02efd8464688f87a052518691ff4d818a7c36addf4c6520ae07b7e20912432e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63000000002b40420f00000000002200204de81ee2e9850dabdb9c364a12cc2dd75dd20c21d4ca0a809a2e6af3caeeaf9647522102e9cd12509fbc345c10e2b2e10427ae43eabbf0788bda8118b34344e5576d5d502103683425bf640d5e4338553ca6f6afea948ff2bf378eed1b3e9497a3f18f477d5352ae00000032e16e6e875d1f02a9512943ba2eb9309f4715390b52e4965dd6e39a2989ae63061a8000002a000000008833a9275befbedbf928e743a71c9476f46528baf6e832ad25478c2607e4108dea67657c51db7344032627c855518179dc7a94b59f306faf9a3b4eef7684d5d73106226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f061a8000002a000060e32a590101009000000000000003e8000854d00000000a000000003b9aca000000" val commitments = channelDataCodec.decode(anchorOutputsChannel.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments assert(commitments.channelParams.channelConfig == ChannelConfig.standard) - assert(commitments.channelParams.channelFeatures == ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputs)) + assert(commitments.channelParams.channelFeatures == ChannelFeatures()) + assert(commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) + assert(commitments.latest.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) } { val wumboChannel = hex"00000000000103af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d00009b3b6da18b7e5e43405415a43e61abd77edf4d300c131af17a955950837d7db4980000001000000000000044c000000001dcd65000000000000002710000000000000000000900064ff1600142946bdbb3141be9a40ed8133ef159ee0022176e90000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4982039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a358500000000000003e8ffffffffffffffff0000000000004e2000000000000003e80090001e03a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b81607025c24f0c3572631ea09dade913ccbb1cd8a55585fc152cc2165bf4f43af702c8b0306c241c667eefee031639052c4a35c4ae076ab56b49f76f7e12f63dfb7bb32a503fdbff1ca92c99c09a9c0eac3cfe1ca35deb658d5fdb815fe7a240df25da5e20f02c47d9e4e1fb501c81933cfc4d3ba7f0e8203003cda2683569f1f67aac2b6e20d000000030a4982000000000000000000000000002710000000745e66c600000000000bebc2002464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aefd01590200000000010164fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000000018e2678002400d030000000000160014cf3c9d08ec27d3ffefbf7e2640d3773e11a60ceb783bca1d00000000220020153e514ade07f347db9ed32cf6b2096d066d48e0cf8c504590cd1155de68be2204004730440220056ac83d5b2eb9eb9714e85294d080a9b64f73916d8206b15533613b4303ed3d022044da40ba433e27ddb8743c75acc0f6b050c346bc9d241ab3a1b65051820ce06a0147304402207683c10f33682e2389e5f02dd34090bbe4d786633d49e1777d5ec2ab71a7924902206d99dceb74ff809237ae8e06c118b38047e2d4a49f9956c538bedca47cb4b1da0147522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752aef5c4602000000000000000000000000000002710000000000bebc200000000745e66c6000bdebd6cf39db4c2ce1a675281b0a714e525525149032ba048d75c89e5a9b355021a87b53e12e4b36287635cdf887991a732c2fd56b12f5377d755b8b890795db6000000000000000000000000000000000000000000000000000000000000ff032c5f941f78a00105b995fba7f3c340afe53e287a60361135386ae1448d6f3d632464fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c000000002b0065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b981047522103044d393b0da6bcd92030f3131d0780b0d6fa017a0ff45b7fed708903c2950ced2103a6be2613a9ff761a769b077abb43d76c5f06944fc32010891156179bf8b8160752ae00000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32cff5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff010065cd1d00000000220020d1b88cc50b09abce945ce6703f30dae3d71ec626be2201925db9379a3e8b9810000000000000000060e32bf9000082000000000000000000000000000000000000000000000000000000000000000064fc06b43e94d9ce0a5a0e1ef13f63ba78e56d920e7d606d0c6e7a631c48c32c0000f4957823b0a6b76697d7c545bcca66abec8522a0f6350a722e45f3f2830f7f824c84efed8daffa04bb0822ce1a08fb2de77dd20c23a91cced7a3966808956644" diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 3ea77fc4fd..a7bb87db0c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -1,23 +1,18 @@ package fr.acinq.eclair.wire.internal.channel.version4 -import com.softwaremill.quicklens.ModifyPimp -import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, TxId, TxOut} -import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} -import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion} -import fr.acinq.eclair.TestUtils.randomTxId +import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxId, TxOut} import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} -import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} -import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit -import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.transactions.{CommitmentSpec, Scripts} -import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 +import fr.acinq.eclair.wire.internal.channel.version3.ChannelTypes3 import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.Codecs._ import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.channelDataCodec -import fr.acinq.eclair.wire.protocol.{LiquidityAds, TxSignatures} +import fr.acinq.eclair.wire.protocol.TxSignatures import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, UInt64, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -26,14 +21,6 @@ import scala.util.Random class ChannelCodecs4Spec extends AnyFunSuite { - test("basic serialization test (NORMAL)") { - val data = normal - val bin = channelDataCodec.encode(data).require - val check = channelDataCodec.decodeValue(bin).require.asInstanceOf[ChannelDataWithCommitments] - assert(data.commitments.latest.localCommit.spec == check.commitments.latest.localCommit.spec) - assert(data == check) - } - test("encode/decode channel configuration options") { assert(channelConfigCodec.encode(ChannelConfig(Set.empty[ChannelConfigOption])).require.bytes == hex"00") assert(channelConfigCodec.decode(hex"00".bits).require.value == ChannelConfig(Set.empty[ChannelConfigOption])) @@ -48,7 +35,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { } test("encode/decode optional channel reserve") { - val localParams = LocalChannelParams( + val localParams = ChannelTypes0.LocalParams( randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Satoshi(660), @@ -62,7 +49,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { Some(hex"deadbeef"), None, Features().initFeatures()) - val remoteParams = RemoteChannelParams( + val remoteParams = ChannelTypes4.RemoteParams( randomKey().publicKey, Satoshi(500), UInt64(100000), @@ -78,16 +65,16 @@ class ChannelCodecs4Spec extends AnyFunSuite { None) { - val localCodec = localParamsCodec(ChannelFeatures()) - val remoteCodec = remoteParamsCodec(ChannelFeatures()) + val localCodec = localParamsCodec(ChannelTypes3.ChannelFeatures(Set.empty)) + val remoteCodec = remoteParamsCodec(ChannelTypes3.ChannelFeatures(Set.empty)) val decodedLocalParams = localCodec.decode(localCodec.encode(localParams).require).require.value val decodedRemoteParams = remoteCodec.decode(remoteCodec.encode(remoteParams).require).require.value assert(decodedLocalParams == localParams) assert(decodedRemoteParams == remoteParams) } { - val localCodec = localParamsCodec(ChannelFeatures(Features.DualFunding)) - val remoteCodec = remoteParamsCodec(ChannelFeatures(Features.DualFunding)) + val localCodec = localParamsCodec(ChannelTypes3.ChannelFeatures(Set(Features.DualFunding))) + val remoteCodec = remoteParamsCodec(ChannelTypes3.ChannelFeatures(Set(Features.DualFunding))) val decodedLocalParams = localCodec.decode(localCodec.encode(localParams).require).require.value val decodedRemoteParams = remoteCodec.decode(remoteCodec.encode(remoteParams).require).require.value assert(decodedLocalParams == localParams.copy(initialRequestedChannelReserve_opt = None)) @@ -95,75 +82,15 @@ class ChannelCodecs4Spec extends AnyFunSuite { } } - test("encode/decode optional shutdown script") { - val codec = remoteParamsCodec(ChannelFeatures()) - val remoteParams = RemoteChannelParams( - randomKey().publicKey, - Satoshi(600), - UInt64(123456L), - Some(Satoshi(300)), - MilliSatoshi(1000), - CltvExpiryDelta(42), - 42, - randomKey().publicKey, - randomKey().publicKey, - randomKey().publicKey, - randomKey().publicKey, - Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), - None) - assert(codec.decodeValue(codec.encode(remoteParams).require).require == remoteParams) - val remoteParams1 = remoteParams.copy(upfrontShutdownScript_opt = Some(ByteVector.fromValidHex("deadbeef"))) - assert(codec.decodeValue(codec.encode(remoteParams1).require).require == remoteParams1) - - val dataWithoutRemoteShutdownScript = normal.modify(_.commitments.channelParams.remoteParams).setTo(remoteParams) - assert(channelDataCodec.decode(channelDataCodec.encode(dataWithoutRemoteShutdownScript).require).require.value == dataWithoutRemoteShutdownScript) - - val dataWithRemoteShutdownScript = normal.modify(_.commitments.channelParams.remoteParams).setTo(remoteParams1) - assert(channelDataCodec.decode(channelDataCodec.encode(dataWithRemoteShutdownScript).require).require.value == dataWithRemoteShutdownScript) - } - - test("encode/decode rbf status") { - val channelId = randomBytes32() - val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey)), ByteVector.empty) - val fundingTx = SharedTransaction( - sharedInput_opt = None, - sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat), - localInputs = Nil, remoteInputs = Nil, - localOutputs = Nil, remoteOutputs = Nil, - lockTime = 0 - ) - val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( - InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, randomKey().publicKey, Nil, 0, 330 sat, FeeratePerKw(500 sat), RequireConfirmedInputs(forLocal = false, forRemote = false)), - fundingTxIndex = 0, - PartiallySignedSharedTransaction(fundingTx, TxSignatures(channelId, randomTxId(), Nil)), - Left(UnsignedLocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 100_000_000 msat, 75_000_000 msat), randomTxId(), fundingInput)), - RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 75_000_000 msat, 100_000_000 msat), randomTxId(), randomKey().publicKey), - Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 100_000 sat, LiquidityAds.Fees(1000 sat, 500 sat))), - ) - val testCases = Map( - DualFundingStatus.WaitingForConfirmations -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfRequested(CMD_BUMP_FUNDING_FEE(null, FeeratePerKw(750 sat), fundingFeeBudget = 100_000.sat, 0, None)) -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfInProgress(None, null, None) -> DualFundingStatus.WaitingForConfirmations, - DualFundingStatus.RbfWaitingForSigs(waitingForSigs) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs), - DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)), - DualFundingStatus.RbfAborted -> DualFundingStatus.WaitingForConfirmations, - ) - testCases.foreach { case (status, expected) => - val encoded = dualFundingStatusCodec.encode(status).require - val decoded = dualFundingStatusCodec.decode(encoded).require.value - assert(decoded == expected) - } - } - test("decode unconfirmed dual funded") { // data encoded with the previous version of eclair, when Shared.Input did not include a pubkey script val raw = ByteVector.fromValidHex("0x020001ff02000000000000002a2400000000000000000000000000000000000000000000000000000000000000000000000000003039000000000000006400000000000000c8000000000000012c02000000000000002b04deadbeef000000000000006400000000000000c8000000000000012c00000000000000000000000042000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000ff000000000000006400000000000000c8ff0001240000000000000000000000000000000000000000000000000000000000000000000000002be803000000000000220020eb72e573a9513d982a01f0e6a6b53e92764db81a0c26d2be94c5fc5b69a0db7d475221024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076621031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f52ae00000000024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f000000000000000000000000014a000002ee0000") - val decoded = fundingTxStatusCodec.decode(raw.bits).require.value.asInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx] + val decoded = fundingTxStatusCodec.decode(raw.bits).require.value.migrate(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // check that our codec will set the pubkeyscript using the one from the funding params val channelId = ByteVector32.Zeroes val script = Scripts.multiSig2of2(PrivateKey(ByteVector.fromValidHex("01" * 32)).publicKey, PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey) - val dualFundedUnconfirmedFundingTx = DualFundedUnconfirmedFundingTx( + val dualFundedUnconfirmedFundingTx = LocalFundingStatus.DualFundedUnconfirmedFundingTx( PartiallySignedSharedTransaction( SharedTransaction( // we include the correct pubkey script here @@ -175,53 +102,95 @@ class ChannelCodecs4Spec extends AnyFunSuite { ), createdAt = BlockHeight(1000), fundingParams = InteractiveTxParams(channelId = channelId, isInitiator = true, localContribution = 100.sat, remoteContribution = 200.sat, - sharedInput_opt = Some(InteractiveTxBuilder.Multisig2of2Input( - InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script)), Script.write(script)), + sharedInput_opt = Some(InteractiveTxBuilder.SharedFundingInput( + InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script))), 0, - PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey + PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey, + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, )), remoteFundingPubKey = PrivateKey(ByteVector.fromValidHex("01" * 32)).publicKey, - localOutputs = Nil, lockTime = 0, dustLimit = 330.sat, targetFeerate = FeeratePerKw(FeeratePerByte(3.sat)), requireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)), + localOutputs = Nil, + commitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, + lockTime = 0, dustLimit = 330.sat, targetFeerate = FeeratePerKw(FeeratePerByte(3.sat)), requireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)), liquidityPurchase_opt = None ) assert(decoded == dualFundedUnconfirmedFundingTx) - - val dualFundedUnconfirmedFundingTx1 = dualFundedUnconfirmedFundingTx.copy( - liquidityPurchase_opt = Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 250_000 sat, LiquidityAds.Fees(1500 sat, 700 sat))) - ) - assert(fundingTxStatusCodec.decode(fundingTxStatusCodec.encode(dualFundedUnconfirmedFundingTx1).require).require.value == dualFundedUnconfirmedFundingTx1) } test("decode dual-funded unsigned local commit") { val bin = ByteVector.fromValidHex("00130d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b7230101041000100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009f22f74ef88d63eb6ece1076957feb94a7cf7f20b07f3ee710a9f2ac024d3872180000001000000000000044c000000001dcd6500000000000000000000900064c0000392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca0000000000000003e80090001e021ef6e2ee1a5d15aba3c663f1b608ed66315fa8a855b91f53eefa8debec663b5e0392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d0289365d47702d068083d9444c9167be1addfe353d9466a9805215c89361aaf85903e60c20e9744d7a3ff07a72c0170569a9841ea2fb6e0dac7d90ee550a491e931b0000001408000000000000000000000000001008228a598200000262866d2d58787368382a8fc145e34c08f5fbfd54d582b582e088de5e32fd8736000000000000000000000000000000000d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723ff00000000000f4240000000000007a120000230d9b88fad260907c43f97b88bb9992bc334ef29015cae8d7a8aa5063539931d000000061a80000000000000044c000027100000000000000002000000000000000422002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3000000003b9aca00000000001dcd6500000000000000000000010100000000000000005202000000014a12a0174ed233d1e65150f14ccb948a0508dbeaaa49236dce4c4fde7b69d31201000000000000000001e0c81000000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000000000000000000000000000101000000000000000124e3b1062400aaf012a8e25bb0932a3de9ab16a1accf7302b333f6020dfea5a22b000000002bc0270900000000002251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d8250000000000010100000000000000020000000000016ec21600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abc000101000000000000000300000000000176d82251202ee2991cf9a98cb8be01ed4ebc8279bcd84536bed6f73f4dd0012d1927d5d82500061a80af0d411da7b37693960ef0a495ffa8c0fcbf82c1fd8034ab3bf37f8a184242b723b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430001006b0247304402203f4fdd467e2d27c89946d2851004c01988294b0071748c4acd315c90b9ab0b1902202c86d68228cf322d4b676ece89a5106d2518565cb859c436b9284d78f81b4d1201210392a15254566846b06ea693dbeedb1a6ded24b02d53ee40f091f628dc117a1a5d000000000000000000000000002710000000003b9aca00000000001dcd650024b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa143020000002b60e316000000000022002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3007d0200000001b0e1b56831c5dd8ef32c85df9896c057c36a7e8d3c95aeba8c9a4da79b0aa1430200000000a9d907800220a10700000000001600149e0bb5d89fbd10f3054bdde0fa3e22d42d1e7abcf8250f00000000002200203737db5c8aa01d33fd415cabd8b8680a16527c8d9c6fe32ab5cedbe610484aa187c9772000000000000000000000000000002710000000001dcd6500000000003b9aca007c6fd871c702806ae018d8609df56a1705d275b99994bb3563878d554fed62f4039fa3697fcaf99e44e3a5b601600667e00d22e940cba46708e3bf0beae9fda8e20000") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding)) + // Local params. + assert(decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) + assert(decoded.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.signingSession.localCommitParams.dustLimit == 1100.sat) + assert(decoded.signingSession.localCommitParams.htlcMinimum == 0.msat) + assert(decoded.signingSession.localCommitParams.maxAcceptedHtlcs == 100) + assert(decoded.signingSession.localCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) + // Remote params. + assert(decoded.signingSession.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.signingSession.remoteCommitParams.dustLimit == 1000.sat) + assert(decoded.signingSession.remoteCommitParams.htlcMinimum == 1000.msat) + assert(decoded.signingSession.remoteCommitParams.maxAcceptedHtlcs == 30) + assert(decoded.signingSession.remoteCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) + // Signing session. + assert(decoded.signingSession.fundingTxIndex == 0) + assert(decoded.signingSession.fundingParams.commitmentFormat == DefaultCommitmentFormat) + assert(decoded.signingSession.fundingTx.txId == TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0")) + assert(decoded.signingSession.fundingTx.tx.sharedOutput.amount == 1_500_000.sat) + assert(decoded.signingSession.fundingTx.tx.sharedOutput.pubkeyScript == hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3") + assert(decoded.signingSession.fundingTx.tx.buildUnsignedTx().txOut.indexWhere(_.publicKeyScript == hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3") == 2) assert(decoded.signingSession.localCommit.isLeft) val unsignedLocalCommit = decoded.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 0) assert(unsignedLocalCommit.txId == TxId.fromValidHex("ab3c0f7a2242e5656cc59db4852347b9ad4c6b54186639bf3b5698df2b129f88")) - assert(unsignedLocalCommit.input == InputInfo(OutPoint(TxId.fromValidHex("43a10a9ba74d9a8cbaae953c8d7e6ac357c09698df852cf38eddc53168b5e1b0"), 2), TxOut(1_500_000 sat, hex"002059106401877b19606b711f9980431fd80e830d2a30c59103d7f12e16a7e141d3"), ByteVector.empty)) } test("decode splice unsigned local commit") { val bin = ByteVector.fromValidHex("001801d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230101041000100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e630009439468f0eab4a6375ce444fc12eaf57df47aba7e2644be598e5249349f8172218000000000000000000003e8000000003b9aca0000000000000003e80090001e000003fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a0000001408000000000000000000000000001008228a598202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064027800eedc36e641dff2b5cd5041895a8dbff6b52bf1d8fe58511fe0b2ae60c19503fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a03c6804903002dbb70e488fb53009ea0a024f87acafda1d96f8a0f1fd2f98b3e1302a26a217b4263be92199009db2b503e48272c0600d753a97ae34bd9827251c6700000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808220a59820000000000000000000000000000000000000000000200000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a147010700010000000000000000000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab0afd01810200000000010223c6a41f797be480b870e70fbc0b567935abc812d5b0e9bdb57e9992dbb54d770000000000000000005b11e204242c6a81fdfaf3d00b8825ca2b3eb16548c572ff9d1d8eef653d7e240000000000000000000360e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc0a77010000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f0906e010000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac0140ec5f7239b44a84754b02b947a6210e6d76ab2e57ad71a68b4080b3e68e3d3355f426455a86dcd678677d62262352258d5a170621aeca6643b8f94677667fa44102483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a801a0600061a8000002a0000ffb0d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2344fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620001006c02483045022100ff4ba957db4a6f149ed4513bc8cd64c9cc430f4c5810c1d01d73df72926e492e022013495ad376bd22c6683cc334542192411369201e4eaef605b59d34403c1f5de0012103fcfbfa87a789958625832770826a2e391b87880a5698e601033942c75173460a000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000027100000000027a31840000000002de544802444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00fd0129020000000144fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfea8880b0000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2021396d48be2b7fbc88fd5fe4ab91a19d7af24be98161251a487a8616cf6b425a7980d99b2a2105ccd02fcc73f3449fb169573f0d411ff78aa338eaa344cf549300041124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b000000002b983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b00000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a000000000000000100061b100a7a13c66544686dfc48ad7fc7d5abfb15a1404f9cb3df4abbb78e810023923100d382bd7186e9d7b47a3be1d8bf7b54eca322931d7514a3260bc6dd4cbb56131124d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b010000002b983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b01000000000000000001221f0000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe00000000342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a2000000000000000000061b10c5e470c9b1c309e76daf590ed9f1a7a1111ddef24029bef966622220d059acaf72d241dcade8b164a4309422d2d02130b50f81c0652cef0944d410b3a38a04531324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b020000002b983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b02000000000000000001b2200000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e000000000000000100061b10cd3756e951fcd4e7638026ce54e7962dfa1d6cc829a0ee998281b23118f5c83e2d7ff2aef3c1842b2fb552f2d2783f3d8187db9021cf41450d4f1ca15c1582f71324d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000002b204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66c005e0200000001d570c959ea1c621f803b28f343b929732718012795559fe1532e03c42b29e59b030000000000000000013a340000000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe101b0600af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d000000000000000000061b10c2ed771e0c811887d21c9d5ad6b72039505e19d879892aa99b83b943374b380e188a459147347953f4a3c34a29d471309e672b9d72c97bd6e64a6e9ff26e31f300000000000000010004000000000000000000000000000000000001ff0000000000000000ff000000000000000100002710000000002de544800000000027a31840b315b404d6bae38833774438441fb36bd2925416d27eeec63326e772032c950f029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c966000000ff0357ca5aa7f38f8ae405263cb1adf2e1469c7f39ee4f9c9b1e70ba2bd02b625478000100400000ffffffffffff0020a409b8983df586e5b71d21ad4824f82a2719d3cad0fdb071c20d0f265b9ba9d080007fffffffffff800002000000000000000000034273b111141c4f04ba55c7b2db01fb50000000000000000100039eb27ed9be0947c7be5941507f4f316200000101eecdd250c851e3ff0344421a4e791eaa008829834f77192587723797bbc77e9f05a886000220d741fabc6a2c7be66dde92517ef89f6ce9ee5288e02f46727ecc12038e9491ad408776ba708b6dafa314db2906226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01eecdd250c851e368414f7c030100900000000000000000000858b800000014000000001dcd650000000003d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d230000000000000000000000000000061a80ff00012444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b962000000002b60e316000000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fc00000000000218d33da90930d78dfda97b94815fd8543f4485d0574727143932171cc4d796ab02790a3f53b2f208b1ff65cbd654baf8c0f105b405e8e587438f88eb38bd081479000000061a80000000000000044c00002710000000000001ff0300000000000000022444fde6f10018b4cec394cce110bac5d84cce63839bed6752d3c1498c5662b9620000000022002049205995639f7357505aed54f7b392d2662276bc5d8968da78e6d623d9e6c7fcfffffffd0000000027a31840000000002de544800000000003dfd24002000000000000000622002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a58750000000027a318400000000045bcc8800000000003dfd2400000000101000000000000000024f48b66833f2ecbd6f125eb8b4c38dd3836ea3e122b64fbe4e293fff54f32ffd0000000002b200b200000000000225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac00000000000000020100000000000000040000000000183c10225120a4ac7358d683a4a296a84a196286ea8bf7c80683f3ea8c30be932eeb30b4cfac01000000000000000800000000000186a0220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00061a8086d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2341f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70000fd025940e6616fcd9dce55776bd871840e88a65415dae895324ce3d454a59626ddc029474733535c6f675d83cade543d03cf7e422d8bb7f1dea3c9821d40cd55035691f10000000000000000020004fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000027a318400000000045bcc8802441f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce7010000002be0fd1c000000000022002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a587500fd0129020000000141f6c672e644f3aefdda12824a642ca2cce409ef69c5530dcbb2438d64a56ce70100000000521a7a8006983a000000000000220020016c1483322b7ef8721387e87a51948fa2e8ecc28dfe33a48327a9eaafd70376983a000000000000220020092dc847c0de4b3408a7b0dced30f091ef9d73e5f8e719794e4267399ed5286d983a0000000000002200209c01da2ecb83e740748c0120909a95cd06387b45902c49810e55f86d34189a85204e000000000000220020c5a1bd48f7ffbc9cd20dadd15f11380a4e878dac76e56080c555134940f2e66ca8250a00000000002200202b0c7734d2e70db4213bca81a2e46458f6e105ff6e2e66aaa3b76133134c3dfe28a3110000000000160014cf85091a7295547ebd2066aecb1a295440a9f6f080f0fc2000000000000000000001000400fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000000e4e1c0342bb8ca8412a52cbaa04072e0e285ff414a4bf36be1828482705a6bd13501a200061b100003e368b6d07bc35ff7e3475feddeb313912771046d96fa86ec9a5ba2e9024f5df44c2609468b1f86640de3863c1e3ff60042667392d43809462bc3d18d454ad70de3d85e738909aaa1930559a093e363449e7a00c85cb62562298a108131f6f88635d990d4a6de980d076bfee5281d62828c19da4db7843c304c6d2fe742a8421ffb46c26b5e06249fb8fb4ff0b4d1f08d44d8d7d87953db3da6154f8d31d5af9780f6e45f4867fc7d77a1e438dd87172758779414784275dd05b88eafca27ed4424f1249c14d0894202377b2a394e3cc063badd9bd57f66122bb391fff3718520fc9dffa0b225b1eba27be4f0a386aeda91f85b126771f356afa9d1c9aeed249ac100ece76fc8b32903eaa9caeb2c0e3e6d05f69ad025d7ba625d536e6ad06d8b77aceb6d7a01dbb17adee7e17eca234b251e353e26de28849876b8cf88bd57f4a04745d6a88d32f00b35afdf416efd8f84826840e9e974cbd8aa4a102507f0c7e441ad9e1c1275ccc89266bde55f4a3582b78fa8aafa1994ef36b35d3943d3cd326bc70a3ddd377e895b498d79cbc857b5bb2e61f30ac8ca48fe186e40326fd9fef0215e2ef31844bcaec6334490a89dcd42219d64bfeacec7978294ac964b66b49958130a77f7569b083b1c280d4e451e872403454a3947d2cb10ad2f96f92b070f886a5ed771332f51889d18cc2ad941c6638eebefe5fe6cc4b79468b1e469adb08c34982783d49be95f32a774f51db6788d34f26a8af1b31fc709d78338c6c4d9834fa25a910b4b63e05aa6d383d282bba31c9378e3f27f985088e682e11a46a9a1c8e18be9b7c7ef02d9faa8bb08a53d5ffe059d48d440c6364292fb42df68a04aacdfaaba029f1460912b11a6f8eb57578832fb3c10675c7989114177bdb3b587a80856f351177d01eefbb1de210839c9b8b9ba621942f250dc6b4bbf014899b77f52e8e40cb06f7063371251c419bb8aa9d3c0bd83ce651520b6e2911dd38db6c0fff7cc64550320c5cac621765175460cc8f54ee4a0026ec8e57fd789e0fef66c4940cabc3944ff4e3fe10229294e6fcb3059adb4f9868e53b52a7da60f5ae75d4e42e6a94c44e375960f9ed44a56c0b6c49e7c5775dcc1a819547bf3f146e8bdb13f305ea0d15b5616623db4c0657059d07df7acf1062069705ae5699ce86d9d0de16f3afae97f5327fd340693f756aba0bf338af880927cd7f976705c2b511077bbd19b9a842cb7a9b2f713f183c1e4eac23bee3ac081be66de275451773444e81536e183c8e2f033766b854cc92773205abb0f39d90e762c1f0673af8e4051205f752757d2deb7dce6adf8a55297a3a56fe1265368970e8d480b8ee61fa38313e5a7069579f10ac2db37c05c58b38544a8750e186c43a153c7780cb8ddfa309fac85b487b43b1452624afbc4005bbc0777eda63c96edc94016d1fe880dcb2faab472802921adc29dd3410628b8431186a7ef3a6839eb16123ffccf40c4c0907764e40d1dabbaa12a3a9ed3f341c070fb622600b6f4595f4601f67f2d03e7221d57a20bad2e0fcde738107a57fea1e1257a89970bc94517fecfe4990c67f7bfde2ad523f0f4dc760e0e8958eb32c6a7b0e7d952e75fdf08550ab72b6824122697846e599c8a2cd7f3066c8c444e2bb41aa38cb063493af2106eeae5228247706a53169f306d6b0d74867db69d29e9a6eca2b77aeb3353fa5a593b9f4fa379ffd01464a6929841f0fe934d68c8ae9e523a87d02ce5df5636cc5dcd72536d044cedd2a1d1e9047712a399627a44c8776cc5d0b2fa7e3dbef52102602f97090afab7a6070af4a1e50d19fb646f25212a7e750d6d0f765c0d5324721afe49d1070a686b05dafd39114bc65cc11cb30cda09b872c79521273ff2f075ebc4a2e5444c6880b3854d623bf1c42be1026d8b0409fe0001a147010700fd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0175abe3cc14d94456c949372ed6ace0b5b92e58c595a6297e6c1cdfbe5bd750a00061b1000025b15002f7b27451dba144086e3ccebd9726c2e1af20883b676631e13908f2a8a0dcabd2fe4d39cd5b0c9d007a4dc3f1a5624208693e461d934fcbb48428eb1cfba41682d19ab1221e11648459eee58b855ea196e854047d82869b62b2efc978f520eb13e35e3752e53026a0f7b22ddf65de7a4e3fea56fc86b9c85cb8df4cfd0262cf3bbe1e16eb25574db8fd56cee33cbfdb73073f700b9992c19ac808354db41e488aa31d6b8a3be8d151732aaf6353f30a18cd773285cddbd3b279e6455f9b90aa37d7055ccd9fdafa66ab0ad216fd123e4a4aadb25f9e004c5a8e794abced52cd6e208d8d2657d03f5179b6353133a9eaf151cf5d47298058ad85ada4e42d29b9c61524ac20264d3b6551efef60789c6a91cf1ce4e069e3ad123fba8d515d705c0e2f3aec108ab317116d593100a4db4affd32b4091a1368cfe3c91b422ff53c75ade0ed61dd562e55cd674e5a0c5ca5952c2204c401d9c61c25b565c83c38cc09eaa6e25c42d234875f2a353813b8db46f031f0392f73a9cc8a88925347e04cfb477f3a7c5578edaa447927c048aa631b4f3dafcb608fafef2e125a549e029f2598cae566f93febee55ab1175359913732673a44fd2a8e28f71e281917c7d0fc04816a5774a3411d1db8f290fe442e3d3141ab366633f07e46528d44528a422c9225129b0ae4e7f3b76c9c8fb25b9c7722be9b5862ce866980003752c178e7a94224a2b0b84fa1e2635b5e2207ade589baa96372d4453868b3e01e9ac2d1d80bc0c7e06a22c205f85e578d55e3283e9b2defb70f46a6084ea40e92b25f35550e7f3fc3c9e764dd4eea7b85df10175a0799e6a46cae937ddb1fbc43795a88850870cdd0f5bfb4506f227b1420f6e8d39b7ccbdc888a7df7066cfcd8da6ba6bceb2aa80bf14fd5d096f604af41ae92b9f4e3c6a7f29208e8b3fb70cf81fd56ff17ee56adf35cb18b39a067f67f9fc3f94e8c30d44760da2322a77679bd6ca0da0e47f206ef7b644448e7330bf19455ca777418896f0e07e551ae4995c7ac5026a7b84c0cd07baf9569135ae4cc3893339ac886ab704b37d46aa3edcfeaa6c4c8aec3ec8bf8ffb51a591ff02f04367314c267a3152070069950d384b99235e5398547d5084c4afbe86ce6a6a60964b8cd89b20c690e8de12606f64ea559a36431dc98d6189a3f15d439dcafd831a9dab64bf8dd5722c91d59fab00fad5b27a89d5f4237cb20ca55648a8256e8a0ba18394608840336d15a84451821619a2363eb51c5ee83e1722476106248962ccb5f7bd843e7dd14620a2b40107d972fa6b1e4ed7bd6b13b659b012870b64906229c93c2625b02da587d2239e09d64bec81f40c50398eb666ff8912711c2377165cbb0ad16999c0b09bc36f354cd495d63fc6cd7b89e4063ff1b63b319afa4a7a64da69aa41ba9af3131df5cb384ce68ab74898d699ea09730b53d3d862521927b0cde151844a9eb11aef8e0e25a3fb607d57c2a9f83eaf01f1c387e09be5fc5ac0eb091359ca26c8fb36cea3ccae8a63e57dcbc60ee66975bfee8964cfdfc840b114dc6ed5eac0babd26f99ec251c45ec602a44fcc6965c0d7c065c186569be6426658b8a5b32fc2dee315a0457e963635795a0d699db9587eeacf8f893699410a87b6390d8c8736552e325f5ccec1b6d4b45002b678649b234301610ecd605f9e56c5dc45d81cccf73e442b872369cbbd3a03eaba79c0af36e23ed97cf73808dc5ed0f5fcd751772b57e2666c5a70b1a5af61a0dd461e8a383e8426369ee6a4e03cf256a6bc3570997c3c06879946ffdd2924aa329e006759c0d6b564c89198c0c09f88c2c9d0c2cd4e0163e8f98145456cc859f55aa8d11a9969ae253d93d56d9fc6a386ae7c6ee884f483913bd739124f69b4bd5238fdc33d860a9cd4670de5dafe4a24fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000000000000001312d00af8cea7a0a1be6687852d7f2249be3b445ae0cddc097a05ea468ef797567856d00061b100003e5507553c137054ae33b5d7cdaf142fbdbfae51246ea3db4441251aa49dac2e8cac063650741353682da85f0df1b1db72997177b74940bcabcb961f364c1861a493066a9cf2533aca5f866b280419fe04f9e9807d3049b759f78d7ce322554ed7891184e73d934c62f88ca33f7cd75d56617664cc674d74f553b8674093e60f014a89e1ab4ea7adde5d54f28a9976d2a3ddb20854b3a1f3ecac8490a9243234c7ce9c5df1b0e45d862c847fd93b99ed1f4d5fd5b06ca416ed048433782d4bd867d36196a7ebe1106f687d0868cd2f2cc1fd94f999de8e59e9a9b333aac6304ac7c01cb91d20c09d4e4f38397ccc1a0cbb46865b7292b84d4f11a783abb0d5da2e31fdc7c2885c6c90053f716264d0b8ba1ce53dc72e8f04e702e319029ca234b8bf72f5321382723010153eaf113e0c823914bf6ea319a1083efe2f0736e1f9b36125c4b77b9e4a581e6fecec959be70f2e21b8c9bd568641d45feb3ffdd39491abc973f310cb1f9c46a874471e5edc3ea7d4063274a9e10e2d21a9e836e99d386d062ac31bf19a14ad6d39c19752eb749c4dacc64aa4591228c039a1f3c0dfd919a2576edf70cbe018075d90c94bebbb91290487cb9cc613eef312a87025891ea6d8b02435b26abf72fea504aad333100e4f14ee916c678c6c9a42b3421bb8f7389b642f89b5abaf99f0df25e5859bee216c3845bbc9fdb032acb88cc144306cc17fe754280bccfecd86670d6994833c4847c71913caed6d904525d22d28aadf3d4e867a617f20a9cf61e95152eb1cab90bae7885da01effee11b7780bd2398d37f04096e7d14252e2026f329422c077762996f45a7c4d546d7ad0422996e659c98f1feebc5c73eeecab837c025a44d94cb5ee39d5a1bbd95973e8e5cdd97cabb7b8f4f86a5bb228c0d12be6b9b2032c54795f4f7180828e94377bf6e0f56545dfcd5252da0936d79aef70caa8715e8416579ca1a928c2aecfa1e03b6cd795528a967fb3dbe524996be92b6168222112063077fb39811ebf9dbb97abafe1b315488c8812224faef56c1eb86837ffcf61bc22a60de7e4f6e0df67c332389556df7da838200dcceeafcb6e8a94aaad013c30934ec0ada06fa17afb5414de4812deb6fa96517df57d503e1e521562ed62a65073252dd08aafba343c029e04faf58b4c451061f156669be5ef68a2adff506cba6351e407d3c6077b71807f21e93d0ca69843f2d5fd6b7c7f415d740fac1f1f8c8bd8f95297ee1ffafbe1b41c548b69dd6d211352c556f14bb4e5af76244e7656f882452201e2bfd90a87413904081a78c2d6849a6a07ad77920465141da19034b70c3edac53a560e7b5c07923c9bc6e17b8091257fe7431e7cb18d770ba1457e62714a47beb8e503874f24405c49121d2cb024edee3458b31ebe1bf40a13f564b8ff95dc537ec926311f59150544fcc57efcef53d9f43c41b0dc32f93b91d2663e9f6b09f9483b4a788b0642d667c722d10ddf09a70886153fc8c943a9d6f3eecec3ab7144669c10b3c5864a174c7343a74e4b58da29f0ae480d5be8adb5d5b55e9cc3c11da1a935c2b1cb966d85612b6c4bbd42aaaeeebada9f062175f502c2cde7daa345239f99201be98ae989cd8a3cd0610779fd413ef1c6c48cd6e8bcb454b847815016e214f1e6162df1b388503b06bad1e5e35ca424fa1ac4407757504e311f4ee0f4d8e5dce45759a96031460842697d577205255b9ba2bbbe4eb08528a9224087a40129906655170405ad4491052ba71d6dde4000b7d7154634db60db540a16cc430b5998dc1465bc57b5a4125c967e4fc451d66026b650b9a70c26998f559793809df549ca1d5e730db0759ed40d3a030c8a1c6bccb28b5fd9230c55ef83d23b369938200099274978bcc017a96bd1b506c6d28b46788f9a483f3e0b234fe0001a1470107fffd05b1d174a99366c416559a37c33a3fc696a1abb769ce6d9e977c7c67f272f4483d2300000000000000010000000000e4e1c0415149b9751963f87c1583f34e4fb1bc5599cb48dc36e0ab744c37fb0630f93e00061b1000033fabe908e0bb47028814d9361e2518822ec95a72c8e611b1562fb595ad452db3272b17a6f9280115fa6e17434cfc1639b3b1795ff6cfe4aaa80b3393100ba4a2308b9e42dbbb3cba55bec53bbf574b9307e2023f3e13e6d0424ce7b4376280ea0d3a187c8585212fe9effee9aa102873a48f1b71a2ae81cc97fdf6075b4ee17716bbcbcd930a23d483f04907dcf89d9d55d62ac6935ce45afe8549de48a363c595876e32e8ba33806671cb54a92a81409f9bc8964983552f709335efb962d17c2bcc348fcf9af666ae7cff45833b4b0449481a6cc869a6cc2d928ccba9499e6415a52c455ebce14cd94a36fa0bf127682e890be81a1dcc77dd2b94f886103d793ffce97430edf04f4b8a0aaf9622b34bfceaef96bd6848717430d2212f2df30105354306fe8d5d452c32e1b0f1872cb7c953295d2794805fcad0c50cebd26b14cc903fd995d4eb397d85d3bbcc92a4b640ec4448a16eaae0941f2f074ea2f292b55c8cd8051f5b43c4f0d14ae04038d06216cf01b344fb3b8b42ae2680d7998c5f24c16813dcfc521525b5bda78bf604edab7edcac3934cbb9e5e1baae044ea136613ac003e86e3caa9f0e93c38c5997a17ab472004726730f79248be824fbe894422e2b07f951d31ccc32c7f513a61c7e0a4f3790faf2dca5fc4afca1a0f14b0a7a07178402f1875430c905250a5d4d8d70521418cca1c82a2b3721831087e342d74bab5395c5099fe8710c667fb463e50f59da0b67d7f8b2a4b47896c84954a82d604a570f41fb1a5a9906592ee050b0cad8fdc652a411f8dd0d4aa5c46540479b202f7752985311671be2fca007d87b2f27f16be6a590357aeb87f9f75d9f2c798599f3e8bc2fe5b1e7b5d4e3a779761d983f52e5d4844c7eabbd4e543436954a9e361f2f099bcb603a94f2cfc1f1409aa50aee8644c5a1222e2df52fd8a1f884c0a1a800c92be47b7bed41ceff4ee44700c88e527b55c0abdd04f86ce1dae36f0c2802bc4cc74e0698092fed0e8ab7a95e1bfdf0b6fd160a7c80b0f1bba8dde89740e4caa6681f1d4c926a49ee2088282d18e3646e4ade293e1b2cb5a0155d3ee662d5c468cb5d2ac7557e7666396336e3608d84c97fb1f48dd8ca7f8528b218eeac9a825b161147849d1c2a1ee461150cf8984f492d02221ea43d994821790f5e7357e615ce721817e89495bc3422b98a7e6d6288d75c3b727d1669742b682560660079d8860ed65022d279ca414cd56e169e1147d27ad808290f7daeb665a949b793ae503c1dc9d3f1ae0e57b02d373357408d881e2354337143f7d0b99dd2adf48e6c4f4983bc1de85345892e246c6edbd9aee3747fcc2efa956e3e89e9f5a9be0401933b51d625e25c8296be8ef13720d15411d029a471387fa2c28a168f72e0802d84b3085937f9a83353e002b39596e1567d712e729aa55e648f87043be90b359d3e1fd439091d3ded3b72c0c0bdac8e3033a2b2d6e4e074094b7ff6eb631e2b62ec36e35ea434154e0189b1b1e8b9b60da771cf0077ff267e7e861045d24d0c3fa57b9408d3e2116bf75ba02b4d8cd78701ca9836074440c6c0c0ccdb2cd490ee616bc6d3f8d10ddaf60dfe77003ea326673193d21032997f8fb77c2393ce7af5f1d3d3abef5550e80c5a4e3b7ae75ae3f79950a2f8e5065e8d8fecd2f195aac557d7a4b4a5d1e0c5f1e60c0005d73df261401196c93384fcc271b0ebf0a9da2adee213319ed1fb5bec02202b4a0778bdd48420e701105a135b8bf2d4f50b6939ca0890174d7ce9852473456059b9b7a85db8e4664fa67dbc2d729080df312f3d8970e28e7304510b4828624279cf789d9deebf08cce0c1cf84ffb2abc52784230a5b488fe167632882be37fd1564c22b0dc89cf86ea5ffe6d7880b7199eecb9957b28ba3035316b763a4c0a0691bfe0001a1470107000027100000000045bcc8800000000027a318401078a5654313845429e9ff89f5848fb34caef8f89701d89c1f989a2169a7d1ea029ced6a1eb5263d34d0d6877178f5b9027cbd9014780197c22fb6fe6849d8c96600") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.channelParams.channelFeatures.features == Set(Features.DualFunding)) + assert(decoded.commitments.latest.commitmentFormat == DefaultCommitmentFormat) assert(decoded.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] + // Local params. + assert(!decoded.channelParams.localParams.isChannelOpener) + assert(decoded.channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) + assert(spliceStatus.signingSession.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(spliceStatus.signingSession.localCommitParams.dustLimit == 1000.sat) + assert(spliceStatus.signingSession.localCommitParams.htlcMinimum == 1000.msat) + assert(spliceStatus.signingSession.localCommitParams.maxAcceptedHtlcs == 30) + assert(spliceStatus.signingSession.localCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) + // Remote params. + assert(spliceStatus.signingSession.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(spliceStatus.signingSession.remoteCommitParams.dustLimit == 1100.sat) + assert(spliceStatus.signingSession.remoteCommitParams.htlcMinimum == 0.msat) + assert(spliceStatus.signingSession.remoteCommitParams.maxAcceptedHtlcs == 100) + assert(spliceStatus.signingSession.remoteCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) + // Signing session. + assert(spliceStatus.signingSession.fundingTxIndex == 1) + assert(spliceStatus.signingSession.fundingParams.commitmentFormat == DefaultCommitmentFormat) + assert(spliceStatus.signingSession.fundingTx.txId == TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641")) + assert(spliceStatus.signingSession.fundingTx.tx.sharedOutput.amount == 1_900_000.sat) + assert(spliceStatus.signingSession.fundingTx.tx.sharedOutput.pubkeyScript == hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875") + assert(spliceStatus.signingSession.fundingTx.tx.buildUnsignedTx().txOut.indexWhere(_.publicKeyScript == hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875") == 1) assert(spliceStatus.signingSession.localCommit.isLeft) val unsignedLocalCommit = spliceStatus.signingSession.localCommit.left.toOption.get assert(unsignedLocalCommit.index == 2) assert(unsignedLocalCommit.txId == TxId.fromValidHex("88d4950aff3bb218308d09ca650ac4648f4cbbadbd1d0f333c0e6fd0a483cf07")) - assert(unsignedLocalCommit.input == InputInfo(OutPoint(TxId.fromValidHex("e76ca5648d43b2cb0d53c569ef09e4cca22c644a8212dafdaef344e672c6f641"), 1), TxOut(1_900_000 sat, hex"002029fedca3b62676f613aaaea53ff97b497f8799f40a5dc586e67f023c5b1a5875"), ByteVector.empty)) } test("decode local commit with commit and htlc transactions") { // Before https://github.com/ACINQ/eclair/pull/3099, we stored the unsigned commit tx and HTLC txs in our local commit. val bin = ByteVector.fromValidHex("001801fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5901010340100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000999383d0b615906c5db6e5faf402f8773d6d1f0725eb0efe6f704b78f0325c95680000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c0000000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000180802aa698202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e03d634673ed2604fc4bec7b755da75f47baf62f271710775c1f283b33f7ec9816e02c37e84f926a720ce810decfa0f080ca43aecfa9bb52464656532ece22361b8920395356cfcfdf4473db386008e6cacbc489d0cade2d59bc502d1fefa0e67652371036a529fc054ac492739b0a286456849a0bb2fe129bf858bcf603247c1f0ead492000000140800000000000000000000000000100802aa6982000000000000000000000000000000000000000000060000000000000000000300fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000030000000000e4e1c036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b10000332c98f379daa236da56ac028f19b025e0b1fff57b4051ddecfb9aed7f4c676a7302ce27f88b74e6d77ee96dfc20b29b73d239bf369230407406dd3d23f3d55f9d6f16b0cb112acda00db9c8d6da811add226c8c673476eebc7b91b1995653afd9ab7693419809bbd44f32be98144911079cd5e0ce023519671fb12fea4b77ef14ec40dea3e1a13c0df50ec2133234fc55c0473105a0fa2ab10b9ea7a3db42f02a9270a9ae032b393ad7d192b96877a5770c3803a4a3eea32e61e9a2854affdf4754f0667bb95bf1c07bd63decd294a167fb7fdcd70e527b27aeaeb48689be4942f0ae3d9b49ae78adfac66d726c021ef0e33af84c4840281915c8ea4cd77ca0441e8810a27c517e9a62385c28590c221dc38f3eb05b0aa371d3250d4ba373e2b79adccf6751f8ae1eb3e363c2b01f9592b0ec35951580ab88eb5069a1e708461e37766af7c3d86c8ffb3ad1981c68d7ffa74f9d4379eb977eda42146871b367cec89afd8d9c0f434e1c132f28de409880f2fa540f16685326fa09e4e4ff48adcf7ec29150ab5b2f581841000401ab4412c1ab3dea8c904c7c5e24421359de09bce12a2a42830a699756530f75ba3febfd16cffc73dc06a4faabb01d7d8914f4a17a0e463ed824e52cd10fa6ddf4bb9b3f7dc57137b42d7fa2acc782414a65813af01b27217e80e69ec3e4b6c74e2dbc87f1cfbcc88e9f450361d40ee5dc4d0311618b01d3030d47a96a569b416a028f6f356f17348276cda2a68816f2a3ae09b5495b1f4d6d3eefcc4ef3572cff82e4ecc6a32adde0142b665123831db84511f3e878bb1e9c06ebfa73b19ced2dd905a51a7eabb4ef3c13c2a874dfc200e2408dddd8536b78d9a60929d47beed0c0dcce1c52ff1aa29a5e4446acd66aa887b36d63967b23ceaca0468f7ac5f616018264b2f66fde267cb9d5f3f9982297bb56e16b93d6f5f028bff9e9f76475cdce9a702747b24ffc91620282a1ddd6dc721225bbd203bff7e3851aa22d6f7dfdab44247728d5cd6ea6f5d80b9928cd28c40187b5753454f96ebd014981fcf72eab4fa1937ff9a958e50b40c4527913e5b9b30dabb8b2d695c19fdb1704540c70aec09f551df64ead2dbabf1917a038e263f0c0166a903f949da0b9a9b285a8ad2c3cfa7f5ad57f07a63b1383a55ed294fecb2225f9640901c7dc3102603c1adc574bbf43730f93d64cf36dc9ddbef0ae5a7939e84987be7ca88081567ef7ef89da7618658465cc05a115b62a25a2e14584e1fe83d5c9cc2b30f8675f7e51f256139f836d8a70a88a7fdec726ffdc6d67b4bb5dc186801ed4fea79efb93a03cd3f17f2411d69c8ae6f6aaefe777d44ec7199c267f14c6a60eb7071cf67f62d453eedb7d54e048dda18466a1c9b47e8920699473d3559c5e223a80e09f4a891fd68ab2cbc12ee0524052b773fd5d8a872754e1336883edea17990de729f7a119bd4f2ccecfed6ea22ae5de41193c6f51ed4c31ead31e5dd661f8dede9d03372645f5a3b5632075ef844f773a52559e46fc2714b5cb2600df2e74275fe1d95e8f3f44dc91840123a885edd2e2d972ca1a0917c54859670ed3091638a0dbc9580f5b25acb5b8688f6d19e00782fb7f2e965c60dfcc8e53b15a1ab399fd09e1c6bcccf6d35df8f69ed2854039041764e101b4e60fde246734971cd98493c45db7fa12b92969ba79ccfd35409f47c84b38f3b2b11e6614a278dc8717955b0500f3d9a88179be50ade3626472a821b15e413c69ea6367f8b56638ee34070adfb6edb8dbd03b595ba089317f9b7b93b317699284d7a562821f74ec92b9e55100e804232c2231d6400b70e2cc6463f17f778c7e98dbdb6da0646a23c6e90e07f80c42a61e23314464986b3cedc9d6a9255b002d209dd2e3576658692b2f3bf6036a263663f9b99074cd514fe0001a147010700fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000040000000000e4e1c036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b1000034086ba1a46c484ed86af94562b643be22879d2dade698f04ec40dab91997e719e19f921b0bf7f276d42672953db62c9b8e4569ef8df2eaf0e4c9349e3b2691f38b6459430b08d288ec8a3d8dccd2e420cf892c8c2ef053d45c21e03dfb438f78041aaf0df9a11c2569476f8f3a525c412f37819f1784fa8f47a53f110f1432a4c55b684a6ad5920f74e378f07802907fd5f4dc6a6aa00bf93d88a3d087642ba67fdc0d8da3fc7b4463b760267d1e55d5ea5158c9b0633f704d57d9f72b847dc3efe35ef861066a9d8d3a555db9344e94fc428ecec960570cf208f3c690ebadc443a8464a74c4fcb32e7e53994d44426ef26402bf9317840b5ef6c41edaf43e9b70da123a06bc31ced8499cd08796cdbdc9b80e6a523b483928afc032439f8e0b47af48384c2004cbc9decb82ea36e62fd3ca07f20f7729684dfa4793ae24a4d1977a5a37f0ffb9f8e5860261372e5107ee58029ba6058141a39a3d4390a86f8a38554d87bfcbbaec495cab0939cff761a316956f6db23fcd3d552b2a6f620d3a2b01868f712784d5a8cd3c64d394f1f8abcf6184862419da136db11f53292df1863eed4e2a04a0ef59a573b800913e27e11fab79260f1a79c35e36026bd77ec852618ced1b5b24909197964331da3f687daa82a34d2ea5f73e2bcc6bc55dfa9e07187a146101c224755b1436e292b4df8597418ac9dc0f46b39a7b2630cdd73383c03f0905a25d3a52ab2b46b49e407fccb7e2fd943603710808b122195d22b2f2d022aea4b0dce9bef1b40e37600d3d652a28a144cf39a088f09d19948fae90142384066cd56541d2003b38ecb5050beb6f618dd3c619973ee64cb76a2565a2ed1ba6c6838dac3698385fc33ca4951f6ac6ae7dfa2c4bc027e3fefb520be24b5d26d8fc929ea1b14cd9427997b7634893928bcaa32600ea7b65a5e96fcd345962f5f4fc68905f9286db7bea219757399d11774491c5159663e9295bf5911eec5a1dc45505bd425dce8ae5e09fbef489276ced951e20d2154d7bfee8a125eb565e0b980abf53f1fd031412233bc0868568a7314945af9c276e52e09add4be6d0ed8ce30d8da4c62cbaac98f26a72bc15e0b5ef185431362698e6a1715a6768070f6216f1380dd1a643ee772c6b93b900afa8f05dccefabb46895402988e39b66839b5c4b0c53857d3ba67ecc279a29351b84677f98e23fca658413c0368227ce74ed9a4fc4918495312126514922b0e361df5c9cb8c4e44b418b885cb812d27958635864dcdaef25c82dcdec31b200a14e56e54de368738e83e8bf94dfe930b23d8544bc72f1a6ef0a74d2e8e84c786fbf01f48f30263d7a080a33fd7d9eeb432316127fed2bedc060ff55494ecc281a92675bde4f42ec73db25c1023c8fb616a78144518a240cf1873f4d7c61200e70e21d165c2bca5c50a0ab60bf4ccf4738f506e7110feb6c6e9782a5036a800e41c128c7c75aa59207d92e1e4ef0b83e3177c12fbe056c332b9db9b5903257c9b113812a485622a6e0fb7add7be65539d195af090288400df1ea795dd08222f2954e811742d3d8d11ad70196a695871ca39ec9e42c1c96bda5c4a03098afb7d46fc90bd9d0e62b2ea1b631dca3dcb9e47f6bbe17e26c04f15fc855120d843763d13972a78988445924a59b361591f249e4c95f1ecc6b994d107a7f47eed6aa4d88bc9bf259da17a7cef6c6aa4331211c56d4252f6ee1e7c7451ae30914c3e78aab657f4194226c79ba19a9569d9ad2eb88ce9d3f19384ae58619d655aefddcf44a1532ce9697368a42aa78b2c13361b323718e19636001ef8d83a78e5f956b9044828db98ef283524ea4e2f4f1564eff7e7ee9eec80d80372d2bae41da1910055e8a0b50b53c1474c188cface6edb9092f7caf99cf354260f95fc93151342113084192acaffe0001a147010700fd05b1fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac5900000000000000050000000001312d0036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e96800061b0f0002c711f0fdd00dc0d59cc9744be7844b230c3e6d896640c686f7d1c93fd005fc90d43ca381ddcc524d7c924ded0fddff49e674ff3fe851d356b17c73e361d7326917281c6ada5dee7b967504f0b3e030963bf12ff9449a0fdbd53a9248b58339507e29e19f5c2591970d6afb7abd49ca61222862d902a0de2417f471bc497842848a897cc7d3bb19c03816c0575b26d5b638fce6f15f0b546614748ec5d0e14bfbb2f5e4e2d65d17f98481b972066a3b7b97bd5e587ccebfc6e7e3d6885ebc9c6c0e09d3d4ddb58b9043fbccf297bd2a9783717a31ed0b39be3c14297a4970fe0a7f1154781cf315d9e4fff61edd2d47c32f1eed011860a4cc2c0e057f35afa6c92529be18b195fb0fd7577042e3f31d115571579959fd329c92fe0d68df2db844ab8296e95f5393810b2744fe0324c118e70c26cd912f4296d7bc2199e93ba20907f5713088d779e5177f16043baf8d59f4208229817acae5512f6eec8e9680204593c67508bb8246f99da80e53cf74147a911ef6891b1d61fcc43135e9a3b7d875faacbd051d277b275000f284cec9499a87a3ae5c62327b707e50cab7d7587b6c9aab7b556df9d3ab865e544615ad397d67063636c9d675a092ddf6ebf00110ec728ca8fa686882cd18fd6abc46f57978cda0d95fcf075c1b35cf08b151f6779b85f9a0ab7da7eae71ed0f8245372b7feb81b4d243a153b7330f90cddc9f18763373f4727780b2563899bdc5b8f79903c5a00cb1862e91981360c776ab503e46749344e5cda444c9a6bcce40aa2b4f0f5ea066a20171f9cfea53cf58fe426246acab1f5326b06e8baff1f2d76976e8a4d6c9001b7a2e8f739df41d80f54e965b5cfb80d811c31bc32b0d91041bf28f45aa5a6ab15abe6b44e9f27dc980e119f2865731e9c5dac0161bc3a320b30467fdc6cda7d105fce503dcf6b62d8e8dd61457b401b849990049926d3715b2131eed48652ce60ce3dd4eaebc7d19b0a35d4c561c2a488d1ec0d480ebaad47ff63987331b882842b36712e6ca9581532251e0ffd7647d62295b33bea95818d04ee37f5b7695b85dd28700c8d5dad87aa2786006590796c354804810f277404c2437c2b70de2d2b10ed91ec4305e80a2f18666c791039c0c3308884012a8a2c92ab76f69c0404ae784a9f748faae201b2a58db8598938978a159e14dfd05c2d10db3d6072274f5f92b7f8cb1d30fe73842272ab6d2bf3901b81e85ef6b1e0a4f0821cd25be5e6021f6702ee804f2ae51382110173a9b206f9340b891985c36eb64af00eaec166507524bc7a2b32ce1e8de40ec127cf8c3f38dbdb4e483e1869faf6513a131853c11e9bf1717f45d3af8be50eee21c8768adeadd9d79df49c862a0bcd1a2938ab43909ac155febd48468285266148c8dada96d85065251f18830299b8383fe694b9561d32a59e3a5f4a95324cb6ef2760abc947351d19882fc671d7ba36510139a186308b8d7ca5ce5c430860e21cb6c218452740e1a30f79541899bca6cd13414e003e847778171bf8ddae4ed9ad9dbc0ed6dacbb603d8f4255745ce5247508222c0d8f46a4a517e6ee30b02a7e690b7bc45e5d260bd4e7d0d27131f088351f116d85eb9e472d01d78d3440a3b6dc1d3da2a3c6ee57e78dd2f2138979c880df9cd94574929895e361a4d43fec2f5f4890eefad4bac1ef4d244d64641e600e4102c20921901b903d0ba382995aeb027906c0f1283315ad454f986734932fead46a7e06edc5ef3817f7b94a831fff268ab255aa251ad08df630790b100b153b809d31b68e6080586d4d9b271b36c67c86a68f4a9034658b7fe8cb7ba94af8c0503ba3df909a9ee8e394bde5e701013f92e3f45c2e67f27e8fbc206721430ba9aa3657ecb9dbf0691bfbefbcf3ee20981d06d8977a7c401f1bd1187d3abbe3c3e9ffe0001a147010700010000000000000000000000000322298336489c06b5e7fc7da4d6dc4f1ffd2a59b06bcc5a6e81feff828cb841d00a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f0000000000220020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c8800000000061a8000002a000000000200000000000000070003000000000000000003000000000000000004000000000000000005000009c40000000008f0d180000000002faf080024fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac59000000002b40420f0000000000220020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c8800fd01600200000001fbbf4f2af338d22194a95c045dd36c1eaf9c090f6668f85e9976a8cb2418ac590000000000da4aaf80074a01000000000000220020f76d112d2b74c50fe65bb3f151b3c98b737e72f02125dd3b0826baa5b596263c4a01000000000000220020ffdacc7e0491932fe22a1411c331e3588ead7849eb1fb2e11318656f276cb93e983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf204e0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf58370200000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac3600350c0000000000220020c76babcbc3844154035e3c5b4138a4f20c06dff53dd04810a1d916f800d96599b2fa742006e35027e3a48a8c39f686e51c10c648d15d6c0543fa6328f78c7453bc09d8504e8883c2fc83471f799ce1f57b9591b9b4a95dc0cf9cb1ed878e0d67de7bf40200031324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6020000002b983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c602000000000100000001983a0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac36101b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000300061b1065cffa80c72f9e291456fabc3771865f4993108f8a90deddf041a9bcd523d3ec2d5361430705447f87312b2b858327f5f0c4999b7bdd7da84dd270189304def11324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6030000002b983a0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c603000000000100000001983a0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac36101b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000400061b10c5930b24a9e50aa0680daf5ab04d82e21fe1edfaf2455e07e08484660cf4c12d384c22a3aa8b32fad23ba7f451ea718c718439b0b256b120f22eeadc5b995f491324dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c6040000002b204e0000000000002200200af572135c9b725f663119cbeedbb7726c42878179f5f52d798d790ec9034abf005e0200000001dc5602643922fb60fb2c94970db12260850742e9c0f9f026bf28119fcf67e9c604000000000100000001204e0000000000002200207ce427f9be2c35185ab7151f477327acb20657856b69cc652c1a802b6254ac360f1b060036e6f4bd1403580346f7ad969d0b45f035220481207ddf9e43cbf0b49014e968000000000000000500061b0f03821c3180261d81126c2157282ea17be5131081c2d27c34f55a4000d06fc12b1a00bc7750d6e8d6765d1d53aae75cef20be8117cf7fc5c91c0ba017d3e214b100000000000000070003ff0000000000000003ff0000000000000004ff0000000000000005000009c4000000002faf08000000000008f0d1807a4b393c2c3f0677b66dec80d334b84644151a4373bba21436e873eeeebe9a61022ecd4622a2074bdf0ea45fc77632342eba413ae5a99afa1e57884176212716cb000000ff02557c4e960b3d9d9e0f73945a59a7cbad0f6f1b0ef9702d4e1b363a333c0362770003003e0000fffffffffffc0083c14c2553d85d117442efb0e845fdce17d61c8ab7215214f19b8857f57fa44a7800fc0003ffffffffffe80100830bc65499bcd444ee2554dc8a2e077dddf4caddea225a3383042e7edad3d11002000007ffffffffffc801022bb1088890b62d59d04fdd3907aa1f4a566019c687c4fd5c6eddde7b1bb746dc0003ffffffffffe40003000000000000000300038ee783097f5141649678366bf8596ed7000000000000000400032db7f6ee8f34459f84965d1a0bec8b8d000000000000000500035c4ac1741ba34f978d316fa4b05d974b00000101460f3d693d6660ff01e5e5b0cbb3ce11008877f608d74589ff7ee0e6cc675de4deeb42cba8e0a1fdd4ced5c0c51a98536631639717aa11043a699d13b2e98960ccc523109349f8ef0f9b0d9c74d84fa51d0806226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f01460f3d693d666068414d270300009000000000000003e8000858b800000014000000001dcd650000000001") val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.commitments.latest.fundingInput == OutPoint(TxId.fromValidHex("59ac1824cba876995ef868660f099caf1e6cd35d045ca99421d238f32a4fbffb"), 0)) + assert(decoded.commitments.latest.capacity == 1_000_000.sat) val localCommit = decoded.commitments.latest.localCommit assert(localCommit.index == 7) assert(localCommit.txId == TxId.fromValidHex("c6e967cf9f1128bf26f0f9c0e94207856022b10d97942cfb60fb2239640256dc")) - assert(localCommit.input == InputInfo(OutPoint(TxId.fromValidHex("59ac1824cba876995ef868660f099caf1e6cd35d045ca99421d238f32a4fbffb"), 0), TxOut(1_000_000 sat, hex"0020520f04b40b1ec2c985ede593cfff212cac1088275f72e9158a4d05e7f95f0c88"), ByteVector.empty)) val htlcRemoteSigs = List( ByteVector64(hex"65cffa80c72f9e291456fabc3771865f4993108f8a90deddf041a9bcd523d3ec2d5361430705447f87312b2b858327f5f0c4999b7bdd7da84dd270189304def1"), ByteVector64(hex"c5930b24a9e50aa0680daf5ab04d82e21fe1edfaf2455e07e08484660cf4c12d384c22a3aa8b32fad23ba7f451ea718c718439b0b256b120f22eeadc5b995f49"), @@ -243,6 +212,64 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(!nonInitiatorDecoded.commitments.localChannelParams.paysCommitTxFees) } + test("decode splice funding status") { + val bin = hex"00180288db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e64390101041040100002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300099a646c0bc1ac29cc54d267a136c058acda0bec29753f44d86d03f166c830e4dd8000000000000000000003e8000000003b9aca0000000000000003e80090001e0000000000140800000000000000000000000000100822aa698202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000000000000044c000000001dcd6500000000000000000000900064034ed663d94a40aa41a1cee6a3a51e61a8d850dcb279b3fdcc2cc3de9429aeb58702272bcc43918fa19c19ec1fe51d2f880d519672fc9c8b1b16897e1effdb9da6b303db2ac88901d891e5535c90a41a6f3b8f07c94d46c4d5a836e02987a5eea700940214a6afde6d3e2af143d9b213ccf01358bca3207c21b98f6ba73c57f4a8712d2b0000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000180822aa69820000000000000000000000000000000000000000000200000000000000020004fffd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000000000000000e4e1c082c2d32d968632e86cd06acf803d2d05f01d3597c87304dbf0c8e49942d98d8c00061b1000028a074fea4c0f9a59f316f0b61b109f516315e73d86a38a3c8cc52a88f076caa0119c980e5f307fe7d43e34de0fbc89bab1a0d962e91421ad832fae5f3c6906b5a624ba40d32692ec9fde23bf9a8f165bdfd657d76f99351913f04f59c6051f460af79c14567a9ca99a1e4882f92b9b8ac201d98f028b555ef542fe083099d2c7e146e936fb44e5fb4f99079895b70447564cffee641fdcde15e08832753411e6c3a89f8b3b8cf302686b539c589754be7ea78d3401d7aff82f7001e0095e1ee3f9b7756f3af91f8741465462761a84ae850aee95434ff2b2807043b7b17eecbb4a8125533c1c6a86ca8e4a0386f9ae7361d96563994bce7fed637fe3741b7255cbc1da887f52f37f7d11baf9fc05fd31b1a9f6bd50dd554393f63ca887cd33b98a98a1018426ff755734086e02b2e807c2761d81c835ea2bb3d9d965400ebe56023d13846308181cd6dc3d79b7ec2a3fdd777bdf3a8ac716c335e2dcec499a73dc76f32b4915c771ddbc4d680e47198cd7a1965ce9dbe7ec181967167e729e008e88203d83f2763b5f23d8b0eb9673548e15d597462cf21433d80d490703e1ba9972b4a27ee7ee0f75f8866cb8120e8114bdd6ad864079c71bb5877e86f699fc8e5d0b3d104b12827eec7caee60edbe29f0365d9d892594278ee7e0e96584f8b0d9b13cb89a468ce9c42224ff3da92ff6f27fb74c4eeaec3c198ac6f9e1bb57647d739510c846decbaa686a2c285be618968e4c7983e2e1ef30414090db0d64a1945c638a78811341aedc21ac0c746ab6a0f84ec21c2f35a9a31842adb1c5adb6df9709ef64c5ac8c4cf75d3ef8ca06024c1636ef47d4f8fd4c2b19bb73d317443cd74de11c0a9f9ad40a4c5f4d2ae586330930e3f4e009c913fe7b42ae385e125f8659035134ab6019bff84b2b9d5d40bef7577f91ebee5c3b351f1f3367efb4fa5bdbdf40b35d8ccab063d6005d4b70c18c46cf0400da206d843b9579ba2683b7117c0b793fcbab95f96589dc62c8614f2da74a4e5a3c38bdf753e6e8353bc572a4f4edc69b630d80309a57405844dac33562cae7bfde1a2499d6cf598455d281e92e45f23fd7fe92f62a115c6c861b4cf1d2427b6290ceef71d724ec48a1eac4f31e129c576eaf044f9f2f79102eac265f5c7b4048d868384702baafab5f6ffa5ab59969ae7d1c4318a4bd35c8b42eee940925ee215170a81d1a94c8b1065b90ff4626bb3d7fde2086edc5de13cd40c3eede7e963e8c775ea78366997e9a3169c80c494cfe897ea7ed7ba2a62be75c18eecc130786b17dba81b3d2dbb1ee43a781b2ffda07c6c0e458493530ffe99054f87d169824dee928410d5e411973e9b3f00a3d824b2ff7dfebae9e0f399814907d4176a058f6bc1e5f9ee795a7be4296703a184cffebe1966851feaac4063f2cb873600a33f250979e494234736fdf14862fb70c86acb751eb0648a5b41fb96adfdf907a2b1580c3e2d3e95a1c6e505a89a4a06fac2900e723c2e15591d7418467702da21d7f0c7366aa29a7312017faf46466cb156761403c0f875ac6afe98df4466ae98d2e22afa06d37c256558e703b3769138d534acb6775801ccc0faa9b278cc327dcc4eee928f20bf05229cc0f48ac2be90cd93cb694cdc0471e1d31f9da1e7e9a977f66aa2b8a38e33259585eda46fe9e82ff6045dd02bb7fc3efcdbdbd4e3eb5dd8553bebecf6712ee5162162ea6b1fe2cd4564b76ac1df08ad5678caf454509066f6ec698d6141fddf772143eb408df29173c356192ee7ec5d77607646c9d056cbf86c4d7931d58b2656d0ed2aeff07bc32cf8b4414cb376daa22cf74d65e984addb8831d18295ab85dd7b109bea7eaf45018b1057ebbf30d3aca0a8cf8c025537e4551137a00f9a7eae4137616d6207c2e127b8f97bbfef58898693cc5ffe0001a1470107fffd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000010000000000e4e1c0cdf8287d2073d6609eec784342ab5602f261f14cdde43e2acda1a5f90a292c0700061b100003722a2399cf3177602f17f4d874a6c9fd72c899dad457a29068e2171e8f9a4ed5950db41d0b129c05c97a720ba35121cf8f4197e0e5104a88a96b37d8131466554f6978ab35d4649c58c902fe94e015fe8c0b9248bdb05c0c66b477fa40b15be3dc58e0ff2db305d8e47728cd8d5aaf643d9ff82a4af2be1f59db05ebe1e0bf4744d8075da2df109b745966551785e1a61f62a2dd5d65035933baa95af075a4dcb2894215715631b01a05dffa2efa53c53fc5923b84c38fd2888c8955efe535ded16712b493ea8e8e7f3d91273233ef900ec1097611ec8c201f2ad6b0c7c8aac70d79fcb1451e1d8f6f6b92401899981f2462a4d9ce1fca2ec4add624b7334fd0017247e5a09a6cfb2173cd1203e35ae2d081e456bd7e1d6af93cee779b8a6d008db9651bed09ebee4ca68c4b387a28637cb557b70772325c745719344a49ada5193e2b9dca5c07e3d69f89abd44cae48da9127981b12079262193dcd0395761c94b7886297d323523ca7e10398e4b9a4359ff0b66370e87f3ee266d04f4ddcad5bc9e24ae22e899aa5653f8ed4821c3f7379f8d01b9bce03b40a9bca25fa4417cac5e81c6b3157b810fc8d5ac3d3ff709817098c65f652edbf955af263e75b54f351a339f0af55360b88ad02de6ae428728e5aaf6cd0553c3693359b6e4b842a53111e3e4374c06bc345fae8010034376f5dd9b7427c9da86de8f55c6f19728140e60458e35a49a02cb4f796485d9589890a9405186cc9944ec0f9da2f558b936e5f2895409dfd52b76de27a7688d2ac099f488066c90add0c5c3a683111325538c944c02b5dea4d13fc3804982f3ffba4f67167d172f2390d87c5070803d46217e2f57050511161984513a1e6f1acf30c74e50fae89c215cfc8f2f8d21a94ed9828e6d2ca05d47670d6219b0fba3890f55842f2e7979b82b8da0c105c034bc9b0c024e10f2d7f23c2b69864be11116859d1e397f6bbca615b2d9d84f2330a04ac213b66191864aaa6d2177baea64c05c47c06a0cf36decaa6613d366be5141eedc4e68063f476380ef8d32c3a365a66ff9b2c40ae66fd18550d28b9888398a5b451eef781021b8689e4828c914145374d90be02ef7efcdc4336c12cdb4feb84b0fca1a16bb05df307843a32605adae4d51ee34b80e1073db2328ed8d04605ad4572e7f33bfed0d1a5e3a2d7c7756f65f86fd585032371ff99bd9545a7a62a7aa60eaa201f3dae2653cf9bb0b217f035b24daf6c8b0f662452f05cb90cd27ecc8917a77fadc5d90e025968be3944be33dda246baf059ead13c023f890732026be8e731ba6657a7b2815765067e212b856c87ed8356ec2370ce7271d36d307976733a86919c0b2d475836200f46be6a158e95cb410d49b59b16c6a240a61ae75f430445d43e101adabe632ec8d6fcdbde202097c3a3e0ef7395551163f874978527e5ac440c26cb853dee881f5e6d977bb95e7d4bab1a6c492e709d51dbcb7a1cda3beda32ef12fb4a49aea8f19cdceefe44e37e9d43a7d486498fe14e6e927c3403050604a64c60c5e9cde8a5250bdf885331a71d619f452aad90fa9a168f26a0867d7463e0d3a86c22266364ec1b1bb1fa3625bee7a1f3257223a29e8bac794d1a7de6feced768b8ec0a02dd35269d42042bfa06c2a9570a8f8215ca10dc7c74d57468ae142912ee501f0384714baba32189caf0be0e3906de0ead04ede8adb3c4a5257b0c3fc145855282ad19ebe86631597898467cd09a7a75c25e08ade2d7765f747fe96cb762b19290df5603a82a8db9df2ff45884fb1dfe1ca24dbe4da61ca8a38ac38633dc5adf778e0559a771fc748eae51fe0cf303eb2cd86f700f1e135f35c74becc12b7dd0f010134d0e17229a7f7f6eef4cc8c489195fac42ee80f46ce509eec846ba3489afee7570053ce5b2b98fe0001a147010700fd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000000000000001312d00c7b3affe908092921acc012ddbd332d9b4de4cd0d3f4baa07b9ac8630ca1c48c00061b100003ed1256e3ec04374d407e7e55ad00d6217909bf5f95eaa52783ab8475b0c214f0d2e785867d545446aae27e5b8465ba2fac58293050d248992f5015a76bd51732cdabba5fb78fc7ac9c46a4c0b2c83c89a3103f803669414cd7c189d324714ee44323bf2c203ac5726e9f35268fa3427c2fd217e30b9ba23a7606a9058bf46b68007c5627390b3dd2b57e079778f180d1bfc4e2ef0a715d757c5a890617e719a26940a3fbc44b8a2a5abc23c775f04105eddd3838920b72f7a28988c0874e44f5ae083b8ba0884477f1c7a6b699bf567316fad67ce50ec8ca2dab228acbe91804c3e7d734df7b88bf0478cbeb5787af45724101946d81dc3326f650b5c260ba541c085ad95c70fd169ff44bd56896dd4db21aa3cf4a5eb1c0b3fdc3527c7cd1ae3a08113e67d69bd9854086f570b811668c2682b7dcf86f2a496115625e72cebf805b13d8d73efd9c0a7cf314cc3153b9470481655c9fe84dda120e10652c7e9ad2f6c6f8c221760f172aa2d7c51b54ada10970091e1f7a1cc985d4dd48a6e5b867a25fd261e22e7cb31425438fff4036a6bb08cc4482b9dbaa5aca16667d1cec6442e77832e1b5bc6e8542b8307dc3004a2854735de738cf2f4d5256597e9fe9dfbd3ae2ba64e56abb10fc9740f744227a830d112fd23c1304f23a6525cb8d3011f4cb0b39ca7306d4857d324cfddd39da92f16f654b0eb4173218678ad1547796fff00bf1529d1b3361c06797573c2f488e3307fccfc0f8d9bda4333cbcebfb3e805ddb59c053c58c04e5663e8151ff6855b194f555ca21100e965cad0da2cfee5a939420c2bb0764bdbe91af6f7fc485b7dda6a4483a0c09b4ac04217724bc70635f71cd1065d5c2488e3e0833ea03feb4f8fe1aa70379d7bfc573178e6f22c90b58ee85e181426b5af6b8b3ce5a7e1b78a727560ddef288284384520479188df239156b083f76b94d9042abb16c293f39271ad2cf5c6980ab58004bdfb5806908b4ff533c217fee88f8483dd14b55df97c2cd2627479c504d9e9fbd109dd63284514e8f4d43ba0086487c624dfa207df7640154fe587b10d97286ff4cb52fc6a06de4c429d0edd452631be4e0cf71b434555c596e8c45c299a885d283311780c4bb0855e0919a60d0671f6643a09a98cb7b9da858e2787814d2e139c3a244c5fc0245d51dd759700de5e53d757b078b7df7d7e2bd645dcc0fbf1856a4398409183853d119ddeee94dcafadd7ac8d457670155a36e4f009526330c6f8c205a4686fddc0db213ece3764fc16450bbd318059ac6adaba7c0a60465d168b3c09a553339bba95891273b097412d7d2cb5c5599434bcf3ec6f62c2f14e82e702e3efeade357d5870f6009a521d4b99487db44a6f85436935b9602a5b80a34731d73d4ff9202d82c74b1bd865689c25f378b2626db1e30b980a3567d1ddc2988cab6e9d6f9b5fe71a331e1efcdb5718af345b8bd6afc911f0e82c9001e297a71018141f43d6b694d1559944c0f51f1fd476633738d971a74ee7d60e0f9ec495f307411414901e6802c974690e6fbe74de9494efa5f9ce9443013552cbc73cfc4a15f476e69897d2e1f0b7e848c139a7e84f0c5f8e53f5d761ba30a98de6950cb497e9797823cb98b806143d5a412d4016b97f9c954c420cef50a80b26efc691167d2824ff07e6229016cf92125c51d8f7e2233b9f2cef8d04de2a74d18397f8d457bcd978a9355f7ba2b0d5afd5d076e8399bbaba89e8713e9c4cf37d5be57cfc39c60fd75b2cbabdc81ecb1a7c1b13450718d4a11a1bba70e9ec933904b269af5129226b373cfbac857010c2f69501ced5df7e0b1625383f33cdbde978aef56e7b835f3ee23837af9066d49d40e1e98f5e4be6fa6cd7b485401d64276c44a7c57aad2f367ff3466f2d1a8ecf962e79d060cc6731da9fe0001a147010700fd05b188db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643900000000000000010000000000e4e1c0543ab5ecdaf8b274c0bda28ac14c1097f005a43c545ef0d30ce13c0a292344de00061b100002e0e8650e6adbaedc25afa18beda08790a9db0fcf0042556ffe84eab837f6a6b0c9bd896142015019742cfa46c2b86750473af812d901d486f3aa072c4736ab0b82aa79897090333df1f5097ed5490ce2404acbc6fb34d16180de9ffc7c557ac5896e576e30079e720d886f76472a202add1f954d81b7660cf9f3e24938f8fcae5bc2700eebe6f076f807766a6cf07be02eaa9a9cb2264ac65cbc42cbd52613525399614da2ae8103ad0d6e6930f0320762c43b3d42701a70d71284ef73e11edf5d4003f9e910aa69bb6b708b3cca0e3256d3fdd1ff3c295a5fe26d8669f1c081bd1e73687d7f5ecf97de95c06ccff7908b2bcda7cad01308b863a4f39c4bc73f499564b170a9f9f5d72059989f2c032133a4c7250de697c0108e5a17ce2551d2b3f5df969b1b13399ffc141b1fdb7e6e701b8a591f5fa050a277c07f4b6f435b6b01e06f71a9e8c769dcd3248a6b2b07c635d3896be395560cc1664264477ba3b0cf36701753465e774c5adf87e804d49280035799038da1225f62ccd269a004a749e99b78f23f8e5ac4060c3e056a9d654a8e04e1f519178395d16b0ed570cab5519dbfb0a01540d051b23754ebfb35fc0c391362b1b0193fdb0e0c2e768b0cd81a73c8b6ac48f972e1ac6a3eb39115a591187bf5cdba870ab32cc5b8669a330dedd692413ed130a09b73ffb3a1af74a9c892272c3a5057816a36416769e3698873035656863d4e6ec100244dd65ad3695f1e47bc20fbd6bbc662a55e52058fe11d23650c3e37cd1f00da99b0840a794818df62b1dc0508fc03b99ce41400c1aa00a52090d7df37f49851c83468158cfafa7b851af9af61d136ec0ba6ca95466925081a1e85cc28cafb82bf6866e117eb32575decfea78e065175de8ae66db5e136a1089034af2804addbb39febb82b1955de49492b074769264c37d8dd870fec2424f7f09e418ddf668a1fbba77d9b575bbe119cc48b3a16c8eaefe1edb857966ffb8142676ebd6a2334ddb26084247a0f1001df83624f56c005936fec2625f6618661f939e27796c5d61906ea9438bf90540366e55609a21aeabafc217c23a60821a9a3c21a13f43a65dd88a4c9ea9dfdf41c206eeb42157b17ec1bbfc5608550b19bc0abdb30f22b2dd02c3b474cad58d08165a708341719174fcc4bc8cc986d66d382b1a20e58f0eb72450ae9c56c57d958c39ac3c0fe2c9584fec432db05f78f66c04f049296f85353792659f98c18c42da952193ef0b4b7259886aaaf69d6adcd8009498d880da8f43ac6e1d8c893eaa38ed5ef5a2716a1e0a61d9f39d77dec91d9c6b14a0b424203a700bc41c07b36ab0d180cb738deb676832e13a3d8d701fd38eb192a8f3d5affb9b5f56d3e5e3c9127c4d7c81c495a9fad90efdb4d6f86224117af17b4f86ef5e12fa17054f3770dfc964c9323ddf6b8e850fcac7e82c620cbcf50ea6962e69deb2f3a6c3f650a9c5c14653bd69a3d2d250efe5a05f4b1dff36b78aefdfc55a129b54d42fdbad60b37419c76a3e9970adc3cfd0ec348ae7a9d9c0716406e626af2948b2d2696002d11918b57ed3d1b902682daabeffec13b899567b5bd2b09ab713f1687d0309db22e316326ba7cdba717a3aac9811bb0e5a0fa4f1f8abc54cfdb076e1f231ece9e571bfccf406f4f5d85acd3c7b7f4acca04eec47b36494426fcc4d74ed1a0a95d38b7df77cbd073a7badb980bc09827fc5b48a0adf908dc7b690f72dda5523a01bf0c0af864e6f3ec452c4ce7caa032f9563352ee973be3988a79cff1ef69848f7eb8a9b54193121a55322b184275b08c21d891fb84f879557d7ada6e0c0db1785b4bf621c8caac7fa094cbba8a8538ac2bb8489158db8ef1fd2818a5daa794e15572250bff62379e5c99bd8a84ffbf68895eeaaae495b1fafb9593d54e7ad430fe0001a1470107000300000002000000000000000102ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da070002ff03000000000000000224a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9fffffffd0000000027a318400000000045bcc8800000000003dfd240020000000000000004220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661040000000027a3184000000000638a2d800000000003dfd2400000000101000000000000000024a29b71d6f6d8bb043041ef7c26d5fc5424678d770a8e577e860d77e0128d7e0d000000001fa0252600000000001600141dabbcfbe5ad548fb216213f29a3c1a1425be153000000000000000101000000000000000600000000001e5d7a1600141dabbcfbe5ad548fb216213f29a3c1a1425be15300061a808688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643915d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde560000fd0259406c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e97c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4af388db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e643915d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde560001006b0247304402200e163c65da5d684d8a16f17377e0fa0afd13c82338de0d210c7a1a0f28a17ecb02204f2f1212f1b123e6536341a246d575750365e4c90f0d720bf176f0e4ef266313012102c26f5ff5c5a29adbe0cf8759332d602136094a0f11c4423bc251ee53aef87143fd025940f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d20358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2ff00040047304402206c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e902207c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4a01483045022100f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d200220358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2014752210208ecc4fb85c80be134fe55d186aadc6223da2130c19e0054bd73f3e92188bc1b2102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d52ae00061a8088db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e6439000000000000000000000000000007a120ff000124a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002be0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9000000000102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d02ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da000000061a80000000000000044c000027100000000100000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a3184000000000638a2d8099db32761e7adfe0311fc55cd09e6b885b9da5306aec45f18a094fec153747a52415d166c2439ed1d5bc23187430f880d78120873deae182197fb6690c63edde56000000002b009f240000000000220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661040001cb527b847d4dd0996a9976469ccb970742d0d671f0630d955211924df177631d7502ab009fb7b25773f69cefe6935338ca1988f60e47653ac35a4029d34f5c96000439f05280ab3807c97fae8fddc28dbd6d4205b18486ac42040934099f4325db61067eca53a21847522796799f44e0ef3416f622efbb6d934396abb8aebb5e6e9c8f6ea7d8d841456ad634bdc64fd3234b3172280d24397c4ae9886c7c8aab88957a22c5e5ad74465546c5d66eb6850caac0124232aea9e5d0077daf70a30e325ba0bd0066bd3767873f67e7c5d3290388702bc59dac3259b2b14032499e8fe2646284d99290910aba711423ad3bae2cb1b4e47350b0dd5d5c924ae720bbd814604b5249580cef86c2fe9394f5fa07f56b7aacee46ca00a127bf0f5747d9dd8c93352731d92b8891ccb3072170612c981b6c73a76e36043ed812e5aeaf89d7faad00000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c400000000638a2d800000000027a31840a46aa4f35b1c3ebd340deb44bf796b673f59c76d526a4559bd3de933685387150346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da976350000000001000000000000000102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d0afd01fd02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600061a8000002a0001ff8688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e6439a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d340000fd025940a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b806e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c55000100000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a318400000000045bcc8807ba7b4e5bc38a5539fc4af56886665324dae2388a36062dbc02a3b8ad62abd2b24a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d34010000002be0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a90001f13220c847e1bd076e28522c94cf35f787bcd4fc11a1aefdbcfbd0d448ba7150523549e4347f9a59fda8961e658d8b363a6f9d8eff378cce170ad269c4b978f60004739cc824f0700b15a42ebf94401769ea2d22823353fa903be65181362a888b306c537515bfcb0dc1e1115eeb4e815887324e030b30a8fc32c89415618c24344f1ef7ee9cec3e72b92dab710efee375a4772fc4a0ab7cab72a8267dc4dad963f06222ec4eda3b2b35a056a5c999864235c8c29e2d67cb2084fe5cf4feb68f4e986472411da6723729081f67ca093275aeb3ba31757f778ceb8fc765df4a3c02b50253efa47f9112cf7b9427218b40219c31c127492ccf51a0aca39df911b7e76a5eb9299b716b18f12298bda3970460247b60ef2b56160e7d7446ee96258dd5fe7dce1b64d3fbbf76aa48abcc7118c17e09005b865b6da07b9482c331df56cf3c00000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c40000000045bcc8800000000027a318400edc489971adba4e677a7d5fef263e13620dcd03963aadb7c52bc164a66307ab0346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da9763500000000000000000000000000039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a00afd016302000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600061a8000002a0000ff8688db77733522f259128f787f0a22f9c2390632979883f46a0d1ff8d67a1e64394cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f000100420140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9000200000000000000020004ff0000000000000000ff0000000000000001000000000000000000000000000000000001000009c40000000027a31840000000002de544807637465daef2054492a18c48e33e4b67a5abb832fb4f36bc0de8006ed23ab72e244cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f000000002b60e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c550001d8737c0d71faaced2f80aafaeb2b7fdc1561fcc50a04dcbd751f071e236239857ab5956ffce0017fff28635bca90aabc5a89f15e1a2a28fe31e474e97a419edb0004a1e194f4fa7b508de8b8e032dbaf4fe0c6fd6e50f3c843c58e7dbe85795b8f141d9c1e4509d0087df32d77dd42192e958018bc4cc945e7eff49e6351f6f121c7ed0fc6efd1c37bf6532c11ea356fd5112c182d58e35a0537652053a1da69a96737884ab9d55a2c7ed5a3fc4d4013c46d9a05268d6c3074613d9d4f6634ddf56b8f86bc8d4cd7496c1f1ca53755849ba6d532a7b364d3ee35e6e75ee81f4823ea449f6a4ec2cda780bf57804004d269a348881676b32f08fb2cd4f76d382dca16196296afe40f961d8d89690fb78d69b09a03e804e3a9f1f0e8622df513666d4c3b37b27b232d5e1fe2daef59c891946a5153b2017354fc7808939d78a6bdc18700000000000000010004000000000000000000000000000000000001ff0000000000000000ff0000000000000001000009c4000000002de544800000000027a318407628ac5ffaec424592e2982d06f0d33af3a11a433a49ae2a34d0ffbca4354af50346de84396b4d528a5df97c98d3de60ca4d3b35d28a910aa5c1bf9bf65da97635000000ff03e55ccaeae4f9ac9f6881a9190ba34d2a14e043a4c4ebd91cb0b0646375183ac3000100400000ffffffffffff0020df6e8e87e6778d04281bf8a7c764c5941f3e6ba30bfafb78bd5cee9cd06953ff80007fffffffffff80000200000000000000000003070b89f1d49c4cfba425b334768ee20500000000000000010003da3fa65ef88e47fb9fe95351fc8690ec00000102fc6501ffb2af39ff0121386985e0987d008868beb4605ff90ae905650aad67d7e9df4ce486f5d6f979bb6980e405382f5e05573bbf403a7641cf93ee4e7fed11997cc970f4e0cea278d689799086e0b0d6d806226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f02fc6501ffb2af39684bf79d030100900000000000000000000858b800000014000000001dcd650000000001" + val decoded = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(decoded.spliceStatus == SpliceStatus.NoSplice) + assert(decoded.closeStatus_opt.isEmpty) + assert(decoded.commitments.active.size == 3) + assert(decoded.commitments.all.size == 3) + decoded.commitments.active.foreach(c => assert(c.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) + assert(decoded.commitments.channelParams.channelFeatures.features == Set(Features.DualFunding)) + // Local params. + assert(!decoded.commitments.localChannelParams.isChannelOpener) + assert(decoded.commitments.localChannelParams.walletStaticPaymentBasepoint.isEmpty) + assert(decoded.commitments.latest.localCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.latest.localCommitParams.dustLimit == 1000.sat) + assert(decoded.commitments.latest.localCommitParams.htlcMinimum == 1000.msat) + assert(decoded.commitments.latest.localCommitParams.maxAcceptedHtlcs == 30) + assert(decoded.commitments.latest.localCommitParams.maxHtlcValueInFlight == UInt64(1_000_000_000)) + // Remote params. + assert(decoded.commitments.latest.remoteCommitParams.toSelfDelay == CltvExpiryDelta(144)) + assert(decoded.commitments.latest.remoteCommitParams.dustLimit == 1100.sat) + assert(decoded.commitments.latest.remoteCommitParams.htlcMinimum == 0.msat) + assert(decoded.commitments.latest.remoteCommitParams.maxAcceptedHtlcs == 100) + assert(decoded.commitments.latest.remoteCommitParams.maxHtlcValueInFlight == UInt64(500_000_000)) + // The latest splice is unconfirmed. + assert(decoded.commitments.active.head.fundingTxIndex == 2) + assert(decoded.commitments.active.head.firstRemoteCommitIndex == 1) + assert(decoded.commitments.active.head.remoteFundingPubKey == PublicKey(hex"02ab7a85d532b3cf955d440c7b92a368c65067cd05588385ae3bbb45d8187825da")) + assert(decoded.commitments.active.head.fundingTxId == TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115")) + assert(decoded.commitments.active.head.fundingInput == OutPoint(TxId.fromValidHex("56deed630c69b67f1982e1ea3d872081d780f830741823bcd5d19e43c266d115"), 0)) + assert(decoded.commitments.active.head.fundingAmount == 2_400_000.sat) + assert(decoded.commitments.active.head.commitTxIds == CommitTxIds(TxId.fromValidHex("99db32761e7adfe0311fc55cd09e6b885b9da5306aec45f18a094fec153747a5"), TxId.fromValidHex("a46aa4f35b1c3ebd340deb44bf796b673f59c76d526a4559bd3de93368538715"), None)) + assert(decoded.commitments.active.head.localFundingStatus.isInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx]) + assert(decoded.commitments.active.head.localFundingStatus.signedTx_opt.contains(Transaction.read("02000000000102a29b71d6f6d8bb043041ef7c26d5fc5424678d770a8e577e860d77e0128d7e0d000000000000000000a96e5c0f0fc3cd3d7b17e192c6dcb09c5bb1b5be0cfa3779c2c24cb37ffc4d340100000000fdffffff02009f240000000000220020205f93e0d37d24c30b744c6557445988860ed0fc3ca24482a4c55b63884661047a5d1e00000000001600141dabbcfbe5ad548fb216213f29a3c1a1425be1530247304402200e163c65da5d684d8a16f17377e0fa0afd13c82338de0d210c7a1a0f28a17ecb02204f2f1212f1b123e6536341a246d575750365e4c90f0d720bf176f0e4ef266313012102c26f5ff5c5a29adbe0cf8759332d602136094a0f11c4423bc251ee53aef87143040047304402206c23a27b31022ba82a5011ea1330b46f67b3d8b96de7fa2ae9d7fc0b1b2807e902207c1fa4e5a11e0cc7ec09810ba719af7b95c09a2ab1c5400723087d2f0c4a3f4a01483045022100f391ffededb5b824f85db92103f7f0a70903a18336c1ef6ee57759ee94fa8d200220358ab2eda8a7ed6f4b271ea1416007940cb694ff0c29ad712ad0a168e2c787b2014752210208ecc4fb85c80be134fe55d186aadc6223da2130c19e0054bd73f3e92188bc1b2102db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d52ae801a0600"))) + assert(decoded.commitments.active.head.remoteFundingStatus == RemoteFundingStatus.NotLocked) + // The previous splice is confirmed but not locked by the remote node. + assert(decoded.commitments.active(1).fundingTxIndex == 1) + assert(decoded.commitments.active(1).firstRemoteCommitIndex == 1) + assert(decoded.commitments.active(1).remoteFundingPubKey == PublicKey(hex"02db80bf3ce539911d400ad7c8fc6455f2919171601cdb6dd1eaba9a13ec6fd29d")) + assert(decoded.commitments.active(1).fundingTxId == TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9")) + assert(decoded.commitments.active(1).fundingInput == OutPoint(TxId.fromValidHex("344dfc7fb34cc2c27937fa0cbeb5b15b9cb0dcc692e1177b3dcdc30f0f5c6ea9"), 1)) + assert(decoded.commitments.active(1).fundingAmount == 1_900_000.sat) + assert(decoded.commitments.active(1).commitTxIds == CommitTxIds(TxId.fromValidHex("7ba7b4e5bc38a5539fc4af56886665324dae2388a36062dbc02a3b8ad62abd2b"), TxId.fromValidHex("0edc489971adba4e677a7d5fef263e13620dcd03963aadb7c52bc164a66307ab"), None)) + assert(decoded.commitments.active(1).localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) + assert(decoded.commitments.active(1).localFundingStatus.asInstanceOf[LocalFundingStatus.ConfirmedFundingTx].txOut == Transaction.read("02000000000102296099d8ecfa948512f56c9bd7eafc3874e9b464790301211ff040d3c2bf65d60000000000000000004cb8b39d3b78c2333c7e074acf487458da3682aad6fda9a372255d3cc1514a1f0000000000fdffffff03a086010000000000220020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaae0fd1c00000000002200201af088d9988507e32444ab21a9f81616aea1c3bcd16ad4ccf2a5707255dcd1a9103c1800000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa590140857b2f29ff5384ea1ad85d6d076e9a8ec8258a88367d0d892b565cfca18f810e5d3e301f253012dac79545842f8bb59f91351deda07365ea40d6be71738f5db30400483045022100a4b4da4bef4d9584f68206adef9b493def56cea0de598488c194ff179d8c7b8002206e4b0eb365e4d43e95c349016b2b592a413ec5537c2ded6af0b13216535a9c5501483045022100d5fd961852c5ea9f326ee5696875cb69ed2ac082b93b881aac09fc2dad0ed79c0220392085519a39c9fd48996b2413cf8c816748004e7487ca0ad5bf26b1566182fb014752210210f9b3c19267dd4a2150f041096ab92b59db063fabfcb84bb1c3bf7da09cebe521039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a052ae801a0600").txOut(1)) + assert(decoded.commitments.active(1).remoteFundingStatus == RemoteFundingStatus.NotLocked) + // The initial funding transaction is confirmed and locked. + assert(decoded.commitments.active.last.fundingTxIndex == 0) + assert(decoded.commitments.active.last.firstRemoteCommitIndex == 0) + assert(decoded.commitments.active.last.remoteFundingPubKey == PublicKey(hex"039f967afce64ab9a5da4edbac9f3f0dddb2bdec02d12c124c415690e588eb50a0")) + assert(decoded.commitments.active.last.fundingTxId == TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c")) + assert(decoded.commitments.active.last.fundingInput == OutPoint(TxId.fromValidHex("1f4a51c13c5d2572a3a9fdd6aa8236da587448cf4a077e3c33c2783b9db3b84c"), 0)) + assert(decoded.commitments.active.last.fundingAmount == 1_500_000.sat) + assert(decoded.commitments.active.last.commitTxIds == CommitTxIds(TxId.fromValidHex("7637465daef2054492a18c48e33e4b67a5abb832fb4f36bc0de8006ed23ab72e"), TxId.fromValidHex("7628ac5ffaec424592e2982d06f0d33af3a11a433a49ae2a34d0ffbca4354af5"), None)) + assert(decoded.commitments.active.last.localFundingStatus.isInstanceOf[LocalFundingStatus.ConfirmedFundingTx]) + assert(decoded.commitments.active.last.localFundingStatus.asInstanceOf[LocalFundingStatus.ConfirmedFundingTx].txOut == Transaction.read("02000000000102549d7ace74fc0232a7e14f65cd5dac0dc4c68b9495f921c1493324c52c4bc9f700000000000000000035f42f05a50f23f7e81c8d3110871c8bd2b82d32a3e2c62779397e6df37995400000000000000000000360e31600000000002200206aa3d195b1c5bda9d5bbe9b9b481a9b2e17473fed97702704d21fd6e00f39c55d8760100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59906e0100000000002251209a59d6588f94df3775cc37fcf530dc76580286ad414d9b988331a71552d0fa59014044fcfc2a2d35676ceb83c5217e55db7cfb30129b3350eb23abaf42d583d101817bcf14ce64513375b88acdd25eab5769b58b519e92306f0b2a29c6418aa73e8e0140e3d766cd2476e1c40c520d6a2c68a65ac5f25202a42a6bfd753958ff096aa78ba4aa029d5eabc899bb7828aeb800f8fe622803ebbddb10a882960aa332aa21a9801a0600").txOut(0)) + assert(decoded.commitments.active.last.remoteFundingStatus == RemoteFundingStatus.Locked) + } + test("encode/decode cold origins") { val origins = Map( 13L -> Origin.Cold(Upstream.Cold.Channel(randomBytes32(), 2765, 1863 msat)), @@ -264,7 +291,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(originCodec.decode(trampolineRelayedBin.bits).require.value == trampolineRelayed) } - test("include directed htlc_id for closing channel") { + test("include htlc_id for closing channel") { val closing1 = channelDataCodec.decode(hex"0011015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e701010310100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa0009888a8f1a9231e36da2168b0c3147a097dfab1f4798cc0a31d3e1dce336471cd080000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c0000000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808022a698202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e03b0a410a12070c62d9b401915adead19b3ff2ccdca0e26eb6ef3bfcaf441ea243033429bb2a4d35268c55113ea97544f4e5c74e85d5fc686722b00927572ae1778303e62d2b2f0eb6256482a72e0b3680e5a2ce9f81b09822336c1b253df3fad71e40023b7b1a73bfc39356e17713e070cd4e9d5206cb4a6b2b0c5965bb687c39f5d7d2000000140800000000000000000000000000100802aa6982000000014a00825847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000000000004422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f30000000000000000000000000000000000040000000000000001000200fd05b15847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e7000000000000000300000000047868c0e60afd49d053a0660837e692ca97e6a3a83536bd40a7cce944d0e2c1d2abe7dd00061b10000332c48fe95695da4f2099a3943abf99be9705cbec2f1af961298d701bec53decce487d6910cead042d5a45c37b7009fa196fa2dbc55b44490000a6a60f7a18d7b2fd832253c16a70295a8099a685c42da3a0334248bcfd91417dde694c3a0afd9afdd74f710b6df5a0e1674432670728a221c4eb52b4a4971cfdc8a8205493ce15d3ac666ac8cd6fd579fd62a17b946319c9b67fe2e401c0a5685430a08cf7da7309f54a23cc0d2e5b72d643cdb9a63e8a8f3add3130cca9eac0efc23db0c97da94d1ea8fb512d6b0a2fa47c21b43b034bfb0f2918585c62d6f4af2a95c4ffa27632cd3724033a53fe1d78fe7ae98b81e6e724949182fb1e214765796de6ea9622af13d6e90181c66287ffd2811b4fd8b6b71d50ff45d8ec297906b8d917262eb9572fe92ca181480037d9e0a0c81d6df5894f70e989579a0a363bd5a690d69752d854ae28c79025082e59c01345c2b054f43655d94fc6c088e12be04266f2c1aa6574ede4f20892acbd531f82321a752c65a8760f264e9de46f4462b2bba67192cdeb4e281a371a121595e8389cdd90885cc51b05f3712ec2dfb934ee90709affde926c6b6eb542709cb2d8bced2ac903bd8c15ecf9fc2189929ea031133ff1b4897b26ae5da97e9ed49d25bbf0143a22c0827b0b24b6a020fe2ce5f769939d71c1ddc94b7ebbb84547b1b1f49e90728d19bd55f3730044e583089cea132462184c6ce6d7f7b07b4b2bd6fdf89f1340b65031813a9abe9ce51377885235bffc005346af8218e9d32410c7c0584ae13a4b0e75075b76fddc9ce36ab9ec3dae511cef81760d98653431a464dc81bbef029797896032f9601d9a6b0c45ad790b0f16914c382d698dc46111a6d4aed0f7515e338d1e3668ae27bf06c0b50f14f9ec8e14abc88ac68b0ad3c6c4cc307ce6b6c084ec3399c1b1a7748d61911ee1c425973c737b55d4504f4509e25d5efb5793fc382cd7e0f260bbba79e666f09dad12aded5d402bc99e65098f4ad68f5f88179930cace8b9b23720f3261efa542cb2b0c488fd6d0ddc3566b6896580349b90584ba61bea6c20a1541be43f71f74db167282ab6e19501f477835a4d444717de9b1f70361cbceed8b71b51e91fd939084c53199fe3171a22cf53bbcb8b245a523cf5af1a40bb9ba6e86f8592d9292bfa3efdce8a34583eb32beebb0c8847e051802f977e6e63765c3235fc7b8b3964f3b82ec5686e77f5901cf3887e5653ec5f2b02842454274db8bc9fa1bd73d6fa1ef786bca55fd6813e3016abc4ba87db319ac6ab7ddf8b9ee11cd92f3a506b3af86e825cea498a5ce72dc6a6c763e6392a21edd3722d6a049011530ebfc6f0784430ebdbd5e4fe3c35bed8cfc51d915a101ffb87cf2b269274aaaf8b37ead9451e371ee8b5958ea8300ee568c707c913344f4bb5344e62b095384212b1d8b00873af5feb6e756818e0806e42771875911aa67ab6e0c4077f67a6a8841ca0fc69d57c818152a52d8468f4cd623feb1a30241834a3d02348c103ccefde6997709bc8e7569efabf4c5f712826fb03ba1c01e8f57edb113734e03a4933223149b4ecd844e933210ab4ca366d630b02005a8d0189737a2bff410f0a97aa3ae5811877e27de9b451e35f1c901c379f864e02c823ad6cf3e92db32437f53c108960a012c4d28d3b66f39b66b1b57af170b00fb976fc63b6123cb22ee216cdc48b0a9e8e11bef5fc36f6e5bf3d40eb28f7c0d7c498d7c740c0008a9cade8d147fd99a5b323b92e93fc64ec2599d7d19a997fcf8653f059e082fda5bed8c79ee29c356597a56dc08a5965aa69f44b7d2377d7bf75819a74721eddc5218cf1f16a1ff5f50e37fe14a51d4206d576ce63c2b9cf472e871fa253a36e4c469abfaf17e8e910f966b6ce0075a6088a45cdcf4ef095df06e37ed994b5e0fe0001a1470107fffd05b15847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000000000000000000004c4b40095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c00061b10000281f8622444b2130756cff4fa963aebe1a6591ba779ae258b2c4a4d29bd1f1c5227f7db4ff5d623465d52dae4445a3e4079fb3d10e286e1f51c38e5f336e543ffd11936c2802f5f952689b7f6aab330304fab68e3eb52eaf384291241330c1e397275f0b5c75033f68ac4231db6e9909bba53fe6f5c811f1d08043d47c2c29830cc1765297c6f175dcd0fc66aba6939a5d8572c1e6211f38081b6072ce0baa79f51d5a24b4ad545a57fc40d55b5a6e2f403789635478df97f821b5ac59bade80d3ff7a61619818924fd22397f8eab9ba8b627cc1574f217ed31ab8beb2f14568d2959b859baa7ecbdc7f83d58f24c406c9600081d087a039c01c42e8e998558265318a63c17c8768a4f5aa08c611f24378f8f05b376e51b9cb2d303fc587f25112e2b02fee7783ee6956c1cfc170a3e5e4efe3daed57dbd60d82b3ddb2c3621150a29b043a99aa25ec4e1e6dd8cd3e530fb423b2826a97f7947e73fda2d0334f22282f65306a54d0544fac2b2e1910f3627cfa9cc933fc70f78c70f8d9e7a48988981393355c0170d7ae1b5e3bf0cbac64796ef9780b6bee51394d9dab37f7706318c29432cc28ce7133c6f9ab109e414b1c721bd61acecc903c3cf7018eeac6cd76414905abb2139f32225238d546f70b18164d48f46521b367053fcb3ddf58256c46296f0a38f049e789248bb3b91744206e445444280487eb53e35af868c9a5c097be32413523e25ec589fff8809053c68ac22c312e113c8266f1f4e6648f48ba435d03a5dcd22eae0065e5dd2a4e8054952fefb71630350ea229b6a22e3799713fce8b97e89dfa792a140f3105a32fc3e61b9e536ecf7e0617c4e285eab7a6aa3ea53842ac994e302c1699f0efc2fb9d8ac7ece2179821cb94d8c52079c74fc4178bbce4031137f1c78d2275eae8e7dd43b627329261fe8215421b4aa5e3ad3034686801863132707eb2b36f0510c90ba0cb901c05a9b32d3535669b2ed2a43d594ded9bb4c6ff228b352737fe84bddae6b2f6320d46853e30b14c842aadd9ad2f19d0ef26ee99f1bfad18983b3fd523fd55af06c2a95a8bf9eee832028d23a48667dbb4132cdabad7939dde02e77e3f0848a433591a4e75115623fb29bf03a88a2a744dbbcd6997e079150f1818ef6d31880665ba03ba9e9def8fd468f9fd2ef67f240dbb6c74c2f2ba303ff544db022ad5d034080e6eebaee744500f5d970837658661bd09c6ce4fed4fb828743ea26624b256318e6741f005533692c8a4e67296551f393a922a3576a03a2b1734acfbd4574663dcaeb594d5572b6006b8f593d25b91ba19991c5df5e9861a85206a677beff4c89426b374f150514331864a436f5477f2d1533584ef56dc0b9f2d4c8743e0dcae471a1eafde3b9c353bbbef9fe41305ec332b7e1043282fc7c057d4ebeb15e6c3f2cd774d73842bc20724f4a965c902d13088329ed66783467b03c186a6ed2f5ab19d62a0ac03a01eccdbbd6d1f09fc9d6113b33dabe3140b10bccefeccaa66ea04850818293d44a33829dd6cc80b2be8e9ebbf3061fde598a603d4fe8f4135dee713f4f88459c2ac791d89ad7cb59f0dcbfd96d2ddc954ea735a9aeaee8f47127b7dd4cfd7b08019602e8acc6cd37a7d640e24b589abf2bcb9d1eb4abd19bbe1150938ff90f49b9c5203f1c17c8b85c8a26105995f053fa54f56db9bfdd10f37d9aba6899a2ed375f49197cc3e36cc82839edc36f37d5b661ced3a7528f8f8e438aeb6e8f5c566fa9249fca6966a2dc26f01ae949dabbbdfd03a849b17351dbfa181b219abe93bc97d793f5f72a1d19adaa92043e0ec1200b592c52b3270fd77560f5f2b4340bf936366d79dab9d6c1c7c867019019324b7a74c8d82990e21d2b7bb55ec836f2dbbb2543b77a6f565475ab4fea1822c99a4c1e5885b280fe0001a147010700010000000000000000000000000392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec60a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f00000000002200203ac31ef83f5b594ea58c988be0f62bfef04500ba6d678c11aaeeda1fe662f0c700000000061a8000002a000000000200000000000000070002000000000000000003ff0000000000000000000009c40000000007735940000000002aea5400245847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e7000000002b40420f00000000002200203ac31ef83f5b594ea58c988be0f62bfef04500ba6d678c11aaeeda1fe662f0c7475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aefd013502000000015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4dc886ad207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af21906ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f3000212241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9020000002bf82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b58876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b275685e02000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e4101b0600000000000000000300061b10067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a869ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12111241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000002b803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d418e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b275685e02000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40000000095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c000000000000000000061b10f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d34c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a2100000000000000080002ff0000000000000003000000000000000000000009c4000000002aea54000000000007735940441c3be8f90738b9d65acd2896726cc8b9796b4c5aca307720997082f876d2ee0327e1109720bd3fc2432f24ca56709ce978688310e88141288440730a357f4d9b000000ff02d87e2e97bfb17b65793e748712a44445bfcc14db5337bcbf323e9faaa44e06da0001003d0000fffffffffff80100349e5c6e9d10f61e0ab643d0d82cee98332c306cbb2d825b6d9c4bcb92c1861c0003ffffffffffe000010000000000000003000369b82e7d4d2f49598062820ba3cad8970000061a80160014761879f7b274ce995f87150a02e75cc0c037e8e300000000fffd0212020000000001015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4d0400483045022100881537b143aff4129e0a1918d91ea02c0318d6aa2ee47e9265b6403f5cbe61bc022006f4a18a4bcd58160444daf5bc29f2ae1d460d18bb23076adfa92df656e6e62b0147304402207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af219022006ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f301475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aec886ad20ff241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9040000002b5ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068acec020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd90400000000900000000180c4010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3034730440220710e9165f60bd915b94714b59c8c2062b02adad671d1464385d03204aa885d3402203fab7f3a8987bf55c6ce6cbb95740e7f5c78c818eb1fc05b25ce36c5b2e2136601004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac000000000002241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000ff12241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9020000002bf82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b58876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568fd017d020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e405004730440220067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a8022069ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12183483045022100b9ea29e68afbc2ddc5e3cc2e15bb104e8914daa47154a55fa1540925ac45cea1022021e3440f5fbc93b11b78805d30bb02125b57e45f0acf2fe925ea42e3644d44eb01008876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568101b0600000000000000000300061b10241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd903000000ff11241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000002b803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d418e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b27568fd01a4020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40500483045022100f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d302204c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a21834830450221009f00e2782a17f91a5a7bbda12e8fd24aec9d967341d83c95f5f1f5cd7a39e97a02206591dbc6815ef01826186348db314e51d4a8c2247dc7b514d8147a02502cb2fd01204422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f38e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b275680000000095cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c000000000000000000061b10000224e456c1304d4f950521995e6df1a82ee9bd7b37fb096985ad5c50818180f0d4be000000002b771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068aced02000000000101e456c1304d4f950521995e6df1a82ee9bd7b37fb096985ad5c50818180f0d4be00000000009000000001990b010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e303483045022100e5bc8ad2039c412edfc977397feb1d274d3404995db5f1243a664148db316f6c02203a1b80894a4f5b610810ac75dc9e5ccdb31301e5e6dce4966251f6527ecb27a101004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac0000000024b2911362612c021a5f9e5f3693483960b105ac335ccd7fa4085d9596cdc623a2000000002b9b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e44d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068acec02000000000101b2911362612c021a5f9e5f3693483960b105ac335ccd7fa4085d9596cdc623a200000000009000000001bd1e010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e30347304402200b8ae397cf7c0a30f25c5aa96540ce3600b5157dc52c5767a895a3982bcaf28e02206fc02f2481f5fc145ceba7c2c4cdf59753edd98a64c0a369fbfbc60473bcb51b01004d63210321fe12ac0817cd8b7b32c43e1c37d341edc6b6c0ef2e7a4a193247569ad8ae4467029000b275210262ad7b97170d7443c86d24fc3f4e3fd0710f4eda2d0726f1d953fd49db573e2068ac00000000000112241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9010000002b4a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755ae2821032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6ac736460b2683302000000011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd901000000000000000000000000000000061b100003245847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000fd0212020000000001015847757d05a5e2639d5777a9f46a8dff49fcd5dfba142d93c4de6af54e4620e700000000004c2d0d80064a010000000000002200200f4391a26bcc5afde68e6465183d9b38c91f7b096fb0c3afd8f0561c3a406ea54a01000000000000220020a4bfb70b647bd0ad161401e362f88c00d41151a2b3d4d058a7c23dda3fc755aef82401000000000022002084f49a8cfdec3da99b5faa1459f97c3838223cd8a0ed102ce9365ae154c6d9b5803801000000000022002099f285919d127466e41c9cf5044afbbe12ecb2f6408be9d7cbab3625d6e85d415ed701000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e480fc0a00000000002200200ef18b6f3cdd44b9028e77fd9041c643ba5c5e27de4571405d169fb34ddd7b4d0400483045022100881537b143aff4129e0a1918d91ea02c0318d6aa2ee47e9265b6403f5cbe61bc022006f4a18a4bcd58160444daf5bc29f2ae1d460d18bb23076adfa92df656e6e62b0147304402207b5bf5eb953734dcc0d6a94161f2bf5cdc6fac417dd69b52220c6308768af219022006ca7423d76ca3542a62a581e7ea505e5606583a48cd1f29c4f5462c6a38b1f301475221032991639ef70a233d4d0df569b4d065ed8df5b68ccb507208b8f10984c5a297b6210392de23ee259d18e48e80b3dc7dad83f8cd87a64b4ac0da7dd94871e5ea876ec652aec886ad20241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000fd017d020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd902000000000100000001771e01000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e405004730440220067a0d3a45dfb874cc69c374aa4379885f02326e13e2eb65bf694a3ebcd902a8022069ef62d89258677fced5f4371a9dc2242d31ce84e2f1d65af2ad4ae3a3a1f12183483045022100b9ea29e68afbc2ddc5e3cc2e15bb104e8914daa47154a55fa1540925ac45cea1022021e3440f5fbc93b11b78805d30bb02125b57e45f0acf2fe925ea42e3644d44eb01008876a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c820120876475527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae67a914a397093635d220f06843b4dca27686b9591dc62a88ac6851b27568101b0600241dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd903000000fd01a4020000000001011dd545f83c476102271f04dc690ef02c75d7febda458e9fdb4fb73811705cfd9030000000001000000019b3101000000000022002064a48a683d489888a75d7cf57d5427e6bc332b20acf6abca8085d52da28da7e40500483045022100f4a0c14c421977d0fbfb4da089096f1b41559df84365b7270ad67e8b92fb28d302204c514614affa5ea47eb2b2647457f9d2ea23fb47d449d8ccb9c4b202bac08a21834830450221009f00e2782a17f91a5a7bbda12e8fd24aec9d967341d83c95f5f1f5cd7a39e97a02206591dbc6815ef01826186348db314e51d4a8c2247dc7b514d8147a02502cb2fd01204422f65fd6bb15259a70d2ec79aaa6953d2a58e7d3a4eae779276eab45fe40f38e76a9146e2711de9703f69190ce9c77675c385fa44368838763ac6721027fafdc0dc58427092109a17a7a16a600730db3482352a04edafc99eccf1f802f7c8201208763a914ff7c99067754ff00b64b0c55f7bf392afda34a5788527c2102d6a274c66362e55b5b2ac0a8f63cf7fa61b4374843e8cbbc28017dd7cf3e789c52ae677503101b06b175ac6851b27568000000000000000000".bits).require.value.asInstanceOf[DATA_CLOSING] assert(closing1.localCommitPublished.nonEmpty) val lcp = closing1.localCommitPublished.get diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala new file mode 100644 index 0000000000..37b5e98fda --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala @@ -0,0 +1,71 @@ +package fr.acinq.eclair.wire.internal.channel.version5 + +import fr.acinq.bitcoin.scalacompat.SatoshiLong +import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} +import fr.acinq.eclair.Features.{ChannelRangeQueries, PaymentSecret, VariableLengthOnion} +import fr.acinq.eclair.TestUtils.randomTxId +import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} +import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit +import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat +import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5.Codecs.{dualFundingStatusCodec, remoteChannelParamsCodec} +import fr.acinq.eclair.wire.protocol.{LiquidityAds, TxSignatures} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, UInt64, randomBytes32, randomKey} +import org.scalatest.funsuite.AnyFunSuite +import scodec.bits.ByteVector + +class ChannelCodecs5Spec extends AnyFunSuite { + + test("encode/decode rbf status") { + val channelId = randomBytes32() + val fundingTx = SharedTransaction( + sharedInput_opt = None, + sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat), + localInputs = Nil, remoteInputs = Nil, + localOutputs = Nil, remoteOutputs = Nil, + lockTime = 0 + ) + val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( + InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, randomKey().publicKey, Nil, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, 0, 330 sat, FeeratePerKw(500 sat), RequireConfirmedInputs(forLocal = false, forRemote = false)), + fundingTxIndex = 0, + PartiallySignedSharedTransaction(fundingTx, TxSignatures(channelId, randomTxId(), Nil)), + CommitParams(330 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)), + Left(UnsignedLocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 100_000_000 msat, 75_000_000 msat), randomTxId())), + CommitParams(500 sat, 1000 msat, UInt64.MaxValue, 483, CltvExpiryDelta(144)), + RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 75_000_000 msat, 100_000_000 msat), randomTxId(), randomKey().publicKey), + Some(LiquidityAds.PurchaseBasicInfo(isBuyer = true, 100_000 sat, LiquidityAds.Fees(1000 sat, 500 sat))), + ) + val testCases = Map( + DualFundingStatus.WaitingForConfirmations -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfRequested(CMD_BUMP_FUNDING_FEE(null, FeeratePerKw(750 sat), fundingFeeBudget = 100_000.sat, 0, None)) -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfInProgress(None, null, None) -> DualFundingStatus.WaitingForConfirmations, + DualFundingStatus.RbfWaitingForSigs(waitingForSigs) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs), + DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)) -> DualFundingStatus.RbfWaitingForSigs(waitingForSigs.copy(liquidityPurchase_opt = None)), + DualFundingStatus.RbfAborted -> DualFundingStatus.WaitingForConfirmations, + ) + testCases.foreach { case (status, expected) => + val encoded = dualFundingStatusCodec.encode(status).require + val decoded = dualFundingStatusCodec.decode(encoded).require.value + assert(decoded == expected) + } + } + + test("encode/decode optional shutdown script") { + val remoteParams = RemoteChannelParams( + randomKey().publicKey, + Some(300_000 sat), + randomKey().publicKey, + randomKey().publicKey, + randomKey().publicKey, + randomKey().publicKey, + Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), + None) + assert(remoteChannelParamsCodec.decodeValue(remoteChannelParamsCodec.encode(remoteParams).require).require == remoteParams) + val remoteParams1 = remoteParams.copy(upfrontShutdownScript_opt = Some(ByteVector.fromValidHex("deadbeef"))) + assert(remoteChannelParamsCodec.decodeValue(remoteChannelParamsCodec.encode(remoteParams1).require).require == remoteParams1) + } + +} diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala index d966500b6b..1d6c4ec134 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala @@ -33,6 +33,10 @@ import scala.util.{Failure, Success} */ object Boot extends App with Logging { try { + if (!System.getProperty("eclair.allow-unsafe-startup", "false").toBooleanOption.contains(true)) { + throw new RuntimeException("This version of eclair is unsafe to use: please wait for the next official release to update your node.") + } + val datadir = new File(System.getProperty("eclair.datadir", System.getProperty("user.home") + "/.eclair")) val config = NodeParams.loadConfiguration(datadir) @@ -63,7 +67,7 @@ object Boot extends App with Logging { /** * Starts the http APIs service if enabled in the configuration */ - def startApiServiceIfEnabled(kit: Kit, providers: Seq[RouteProvider] = Nil)(implicit system: ActorSystem, ec: ExecutionContext) = { + private def startApiServiceIfEnabled(kit: Kit, providers: Seq[RouteProvider] = Nil)(implicit system: ActorSystem, ec: ExecutionContext) = { val config = system.settings.config.getConfig("eclair") if (config.getBoolean("api.enabled")) { logger.info(s"json API enabled on port=${config.getInt("api.port")}") @@ -84,7 +88,7 @@ object Boot extends App with Logging { } } - def onError(t: Throwable): Unit = { + private def onError(t: Throwable): Unit = { val errorMsg = if (t.getMessage != null) t.getMessage else t.getClass.getSimpleName System.err.println(s"fatal error: $errorMsg") logger.error(s"fatal error: $errorMsg", t)