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 458c81235a..e0ed61d74c 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 @@ -69,29 +69,28 @@ object CheckBalance { def addLocalClose(lcp: LocalCommitPublished): MainAndHtlcBalance = { // If our main transaction isn't deeply confirmed yet, we count it in our off-chain balance. // Once it confirms, it will be included in our on-chain balance, so we ignore it in our off-chain balance. - val additionalToLocal = lcp.claimMainDelayedOutputTx.map(_.input.outPoint) match { + val additionalToLocal = lcp.localOutput_opt match { case Some(outpoint) if !lcp.irrevocablySpent.contains(outpoint) => lcp.commitTx.txOut(outpoint.index.toInt).amount case _ => 0 sat } - val additionalHtlcs = lcp.htlcTxs.map { - case (outpoint, htlcTx_opt) => + val additionalHtlcs = lcp.htlcs.map { + case (outpoint, directedHtlcId) => val htlcAmount = lcp.commitTx.txOut(outpoint.index.toInt).amount lcp.irrevocablySpent.get(outpoint) match { case Some(spendingTx) => // If the HTLC was spent by us, there will be an entry in our 3rd-stage transactions. // Otherwise it was spent by the remote and we don't have anything to add to our balance. val delayedHtlcOutpoint = OutPoint(spendingTx.txid, 0) - val htlcSpentByUs = lcp.claimHtlcDelayedTxs.map(_.input.outPoint).contains(delayedHtlcOutpoint) + val htlcSpentByUs = lcp.htlcDelayedOutputs.contains(delayedHtlcOutpoint) // If our 3rd-stage transaction isn't confirmed yet, we should count it in our off-chain balance. // Once confirmed, we should ignore it since it will appear in our on-chain balance. val htlcDelayedPending = !lcp.irrevocablySpent.contains(delayedHtlcOutpoint) if (htlcSpentByUs && htlcDelayedPending) htlcAmount else 0 sat case None => // We assume that HTLCs will be fulfilled, so we only count incoming HTLCs in our off-chain balance. - htlcTx_opt match { - case Some(_: HtlcSuccessTx) => htlcAmount - case Some(_: HtlcTimeoutTx) => 0 sat - case None => htlcAmount // incoming HTLC for which we don't have the preimage yet + directedHtlcId match { + case _: IncomingHtlcId => htlcAmount + case _: OutgoingHtlcId => 0 sat } } }.sum @@ -102,16 +101,15 @@ object CheckBalance { def addRemoteClose(rcp: RemoteCommitPublished): MainAndHtlcBalance = { // If our main transaction isn't deeply confirmed yet, we count it in our off-chain balance. // Once it confirms, it will be included in our on-chain balance, so we ignore it in our off-chain balance. - val additionalToLocal = rcp.claimMainOutputTx.map(_.input.outPoint) match { + val additionalToLocal = rcp.localOutput_opt match { case Some(outpoint) if !rcp.irrevocablySpent.contains(outpoint) => rcp.commitTx.txOut(outpoint.index.toInt).amount case _ => 0 sat } // If HTLC transactions are confirmed, they will appear in our on-chain balance if we were the one to claim them. // We only need to include incoming HTLCs that haven't been claimed yet (since we assume that they will be fulfilled). // Note that it is their commitment, so incoming/outgoing are inverted. - val additionalHtlcs = rcp.claimHtlcTxs.map { - case (outpoint, Some(_: ClaimHtlcSuccessTx)) if !rcp.irrevocablySpent.contains(outpoint) => rcp.commitTx.txOut(outpoint.index.toInt).amount - case (outpoint, None) if !rcp.irrevocablySpent.contains(outpoint) => rcp.commitTx.txOut(outpoint.index.toInt).amount // incoming HTLC for which we don't have the preimage yet + val additionalHtlcs = rcp.htlcs.map { + case (outpoint, OutgoingHtlcId(_)) if !rcp.irrevocablySpent.contains(outpoint) => rcp.commitTx.txOut(outpoint.index.toInt).amount case _ => 0 sat }.sum MainAndHtlcBalance(toLocal = toLocal + additionalToLocal, htlcs = htlcs + additionalHtlcs) @@ -122,15 +120,15 @@ object CheckBalance { // If our main transaction isn't deeply confirmed yet, we count it in our off-chain balance. // Once it confirms, it will be included in our on-chain balance, so we ignore it in our off-chain balance. // We do the same thing for our main penalty transaction claiming their main output. - val additionalToLocal = rvk.claimMainOutputTx.map(_.input.outPoint) match { + val additionalToLocal = rvk.localOutput_opt match { case Some(outpoint) if !rvk.irrevocablySpent.contains(outpoint) => rvk.commitTx.txOut(outpoint.index.toInt).amount case _ => 0 sat } - val additionalToRemote = rvk.mainPenaltyTx.map(_.input.outPoint) match { + val additionalToRemote = rvk.remoteOutput_opt match { case Some(outpoint) if !rvk.irrevocablySpent.contains(outpoint) => rvk.commitTx.txOut(outpoint.index.toInt).amount case _ => 0 sat } - val additionalHtlcs = rvk.htlcPenaltyTxs.map(_.input.outPoint).map(htlcOutpoint => { + val additionalHtlcs = rvk.htlcOutputs.map(htlcOutpoint => { val htlcAmount = rvk.commitTx.txOut(htlcOutpoint.index.toInt).amount rvk.irrevocablySpent.get(htlcOutpoint) match { case Some(spendingTx) => @@ -139,7 +137,7 @@ object CheckBalance { case Some(outputIndex) => // If they managed to get their HTLC transaction confirmed, we published an HTLC-delayed penalty transaction. val delayedHtlcOutpoint = OutPoint(spendingTx.txid, outputIndex) - val htlcSpentByThem = rvk.claimHtlcDelayedPenaltyTxs.map(_.input.outPoint).contains(delayedHtlcOutpoint) + val htlcSpentByThem = rvk.htlcDelayedOutputs.contains(delayedHtlcOutpoint) // If our 3rd-stage transaction isn't confirmed yet, we should count it in our off-chain balance. // Once confirmed, we should ignore it since it will appear in our on-chain balance. val htlcDelayedPending = !rvk.irrevocablySpent.contains(delayedHtlcOutpoint) @@ -194,7 +192,7 @@ object CheckBalance { // In the recovery case, we can only claim our main output, HTLC outputs are lost. // Once our main transaction confirms, the channel will transition to the CLOSED state and our channel funds // will appear in our on-chain balance (minus on-chain fees). - case Some(c: RecoveryClose) => c.remoteCommitPublished.claimMainOutputTx.map(_.input.outPoint) match { + case Some(c: RecoveryClose) => c.remoteCommitPublished.localOutput_opt match { case Some(localOutput) => val localBalance = c.remoteCommitPublished.commitTx.txOut(localOutput.index.toInt).amount this.copy(closing = this.closing.copy(toLocal = this.closing.toLocal + localBalance)) 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 196f5c3497..ca62815b96 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 @@ -308,10 +308,33 @@ final case class RES_GET_CHANNEL_INFO(nodeId: PublicKey, channelId: ByteVector32 case class ClosingTxProposed(unsignedTx: ClosingTx, localClosingSigned: ClosingSigned) +/** When channels are closing, we map each HTLC output to the corresponding HTLC. */ +sealed trait DirectedHtlcId { def htlcId: Long } +case class IncomingHtlcId(htlcId: Long) extends DirectedHtlcId +case class OutgoingHtlcId(htlcId: Long) extends DirectedHtlcId + +/** + * When a commitment is published, we keep track of all outputs that can be spent (even if we don't yet have the data + * to spend them, for example the preimage for received HTLCs). Once all of those outputs have been spent by a confirmed + * transaction, the channel close is complete. + * + * Note that we only store transactions after they have been confirmed: we're using RBF to get transactions confirmed, + * and it would be wasteful to store previous versions of the transactions that have been replaced. + */ sealed trait CommitPublished { /** Commitment tx. */ def commitTx: Transaction - /** Map of relevant outpoints that have been spent and the confirmed transaction that spends them. */ + /** Our main output, if we had some balance in the channel. */ + def localOutput_opt: Option[OutPoint] + /** Our anchor output, if one is available to CPFP the [[commitTx]]. */ + def anchorOutput_opt: Option[OutPoint] + /** + * Outputs corresponding to HTLCs that we may be able to claim (even when we don't have the preimage yet). + * Note that some HTLC outputs of the [[commitTx]] may not be included, if we know that we will never claim them + * (such as HTLCs that we didn't relay or that were failed downstream). + */ + def htlcOutputs: Set[OutPoint] + /** Map of outpoints that have been spent and the confirmed transaction that spends them. */ def irrevocablySpent: Map[OutPoint, Transaction] /** Returns true if the commitment transaction is confirmed. */ def isConfirmed: Boolean = { @@ -320,101 +343,57 @@ sealed trait CommitPublished { // the type of closing. irrevocablySpent.values.exists(tx => tx.txid == commitTx.txid) || irrevocablySpent.keys.exists(_.txid == commitTx.txid) } + /** + * Returns true when all outputs that can be claimed have been spent: we can forget the channel at that point. + * Note that some of those outputs may be claimed by our peer (e.g. HTLCs that reached their expiry). + */ + def isDone: Boolean } /** * Details about a force-close where we published our commitment. * - * @param claimMainDelayedOutputTx tx claiming our main output (if we have one). - * @param htlcTxs txs claiming HTLCs. There will be one entry for each pending HTLC. The value will be - * None only for incoming HTLCs for which we don't have the preimage (we can't claim them yet). - * @param claimHtlcDelayedTxs 3rd-stage txs (spending the output of HTLC txs). - * @param claimAnchorTxs txs spending our anchor output to bump the feerate of the commitment tx (if applicable). + * @param htlcDelayedOutputs when an HTLC transaction confirms, we must claim its output using a 3rd-stage delayed + * transaction. An entry containing the corresponding output must be added to this set to + * ensure that we don't forget the channel too soon, and correctly wait until we've spent it. */ -case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[ClaimLocalDelayedOutputTx], htlcTxs: Map[OutPoint, Option[HtlcTx]], claimHtlcDelayedTxs: List[HtlcDelayedTx], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { - // We previously used a list of anchor transactions because we included the confirmation target, but that's obsolete and should be overridden on updates. - val claimAnchorTx_opt: Option[ClaimAnchorOutputTx] = claimAnchorTxs.headOption - - /** - * A local commit is considered done when: - * - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours) - * - all 3rd stage txs (txs spending htlc txs) have been confirmed - */ - def isDone: Boolean = { - val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet - // is the commitment tx confirmed (we need to check this because we may not have any outputs)? - val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid) - // is our main output confirmed (if we have one)? - val isMainOutputConfirmed = claimMainDelayedOutputTx.forall(tx => irrevocablySpent.contains(tx.input.outPoint)) - // are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)? - val allHtlcsSpent = (htlcTxs.keySet -- irrevocablySpent.keys).isEmpty - // are all outputs from htlc txs spent? - val unconfirmedHtlcDelayedTxs = claimHtlcDelayedTxs.map(_.input.outPoint) - // only the txs which parents are already confirmed may get confirmed (note that this eliminates outputs that have been double-spent by a competing tx) - .filter(input => confirmedTxs.contains(input.txid)) - // has the tx already been confirmed? - .filterNot(input => irrevocablySpent.contains(input)) - isCommitTxConfirmed && isMainOutputConfirmed && allHtlcsSpent && unconfirmedHtlcDelayedTxs.isEmpty +case class LocalCommitPublished(commitTx: Transaction, localOutput_opt: Option[OutPoint], anchorOutput_opt: Option[OutPoint], htlcs: Map[OutPoint, DirectedHtlcId], htlcDelayedOutputs: Set[OutPoint], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { + override val htlcOutputs: Set[OutPoint] = htlcs.keySet + override val isDone: Boolean = { + val mainOutputSpent = localOutput_opt.forall(o => irrevocablySpent.contains(o)) + val allHtlcsSpent = (htlcOutputs -- irrevocablySpent.keySet).isEmpty + val allHtlcTxsSpent = (htlcDelayedOutputs -- irrevocablySpent.keySet).isEmpty + isConfirmed && mainOutputSpent && allHtlcsSpent && allHtlcTxsSpent } } /** - * Details about a force-close where they published their commitment. - * - * @param claimMainOutputTx tx claiming our main output (if we have one). - * @param claimHtlcTxs txs claiming HTLCs. There will be one entry for each pending HTLC. The value will be None - * only for incoming HTLCs for which we don't have the preimage (we can't claim them yet). - * @param claimAnchorTxs txs spending our anchor output to bump the feerate of the commitment tx (if applicable). + * Details about a force-close where they published their commitment (current or next). */ -case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], claimHtlcTxs: Map[OutPoint, Option[ClaimHtlcTx]], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { - // We previously used a list of anchor transactions because we included the confirmation target, but that's obsolete and should be overridden on updates. - val claimAnchorTx_opt: Option[ClaimAnchorOutputTx] = claimAnchorTxs.headOption - - /** - * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed - * (even if the spending tx was not ours). - */ - def isDone: Boolean = { - val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet - // is the commitment tx confirmed (we need to check this because we may not have any outputs)? - val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid) - // is our main output confirmed (if we have one)? - val isMainOutputConfirmed = claimMainOutputTx.forall(tx => irrevocablySpent.contains(tx.input.outPoint)) - // are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)? - val allHtlcsSpent = (claimHtlcTxs.keySet -- irrevocablySpent.keys).isEmpty - isCommitTxConfirmed && isMainOutputConfirmed && allHtlcsSpent +case class RemoteCommitPublished(commitTx: Transaction, localOutput_opt: Option[OutPoint], anchorOutput_opt: Option[OutPoint], htlcs: Map[OutPoint, DirectedHtlcId], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { + override val htlcOutputs: Set[OutPoint] = htlcs.keySet + override val isDone: Boolean = { + val mainOutputSpent = localOutput_opt.forall(o => irrevocablySpent.contains(o)) + val allHtlcsSpent = (htlcOutputs -- irrevocablySpent.keySet).isEmpty + isConfirmed && mainOutputSpent && allHtlcsSpent } } /** * Details about a force-close where they published one of their revoked commitments. + * In that case, we're able to spend every output of the commitment transaction (if economical). * - * @param claimMainOutputTx tx claiming our main output (if we have one). - * @param mainPenaltyTx penalty tx claiming their main output (if they have one). - * @param htlcPenaltyTxs penalty txs claiming every HTLC output. - * @param claimHtlcDelayedPenaltyTxs penalty txs claiming the output of their HTLC txs (if they managed to get them confirmed before our htlcPenaltyTxs). + * @param htlcDelayedOutputs if our peer manages to get some of their HTLC transactions confirmed before our penalty + * transactions, we must spend the output(s) of their HTLC transactions. */ -case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], mainPenaltyTx: Option[MainPenaltyTx], htlcPenaltyTxs: List[HtlcPenaltyTx], claimHtlcDelayedPenaltyTxs: List[ClaimHtlcDelayedOutputPenaltyTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { - /** - * A revoked commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed - * (even if the spending tx was not ours). - */ - def isDone: Boolean = { - val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet - // is the commitment tx confirmed (we need to check this because we may not have any outputs)? - val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid) - // are there remaining spendable outputs from the commitment tx? - val unspentCommitTxOutputs = { - val commitOutputsSpendableByUs = (claimMainOutputTx.toSeq ++ mainPenaltyTx.toSeq ++ htlcPenaltyTxs).map(_.input.outPoint) - commitOutputsSpendableByUs.toSet -- irrevocablySpent.keys - } - // are all outputs from htlc txs spent? - val unconfirmedHtlcDelayedTxs = claimHtlcDelayedPenaltyTxs.map(_.input.outPoint) - // only the txs which parents are already confirmed may get confirmed (note that this eliminates outputs that have been double-spent by a competing tx) - .filter(input => confirmedTxs.contains(input.txid)) - // if one of the tx inputs has been spent, the tx has already been confirmed or a competing tx has been confirmed - .filterNot(input => irrevocablySpent.contains(input)) - isCommitTxConfirmed && unspentCommitTxOutputs.isEmpty && unconfirmedHtlcDelayedTxs.isEmpty +case class RevokedCommitPublished(commitTx: Transaction, localOutput_opt: Option[OutPoint], remoteOutput_opt: Option[OutPoint], htlcOutputs: Set[OutPoint], htlcDelayedOutputs: Set[OutPoint], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished { + // We don't use the anchor output, we can CPFP the commitment with any other output. + override val anchorOutput_opt: Option[OutPoint] = None + override val isDone: Boolean = { + val mainOutputsSpent = (localOutput_opt.toSeq ++ remoteOutput_opt.toSeq).forall(o => irrevocablySpent.contains(o)) + val allHtlcsSpent = (htlcOutputs -- irrevocablySpent.keySet).isEmpty + val allHtlcTxsSpent = (htlcDelayedOutputs -- irrevocablySpent.keySet).isEmpty + isConfirmed && mainOutputsSpent && allHtlcsSpent && allHtlcTxsSpent } } 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 66bc94f722..48d18e3b97 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, Script, 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} @@ -166,8 +166,6 @@ object CommitmentChanges { } } -case class HtlcTxAndRemoteSig(htlcTx: HtlcTx, remoteSig: ByteVector64) - /** * The channel funding output requires signatures from both channel participants to be spent. * Depending on the segwit version used, those signatures have a different format. @@ -184,10 +182,11 @@ object ChannelSpendSignature { case class PartialSignatureWithNonce(partialSig: ByteVector32, nonce: IndividualNonce) extends ChannelSpendSignature } -case class CommitTxAndRemoteSig(commitTx: CommitTx, remoteSig: ChannelSpendSignature) - -/** The local commitment maps to a commitment transaction that we can sign and broadcast if necessary. */ -case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig: CommitTxAndRemoteSig, htlcTxsAndRemoteSigs: List[HtlcTxAndRemoteSig]) +/** + * 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, remoteSig: ChannelSpendSignature, htlcRemoteSigs: List[ByteVector64]) object LocalCommit { def fromCommitSig(params: ChannelParams, commitKeys: LocalCommitmentKeys, fundingTxId: TxId, @@ -201,27 +200,27 @@ object LocalCommit { if (!remoteCommitSigOk) { return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, localCommitIndex, localCommitTx.tx)) } - val commitxTxAndRemoteSig = params.commitmentFormat match { - case _: SegwitV0CommitmentFormat => CommitTxAndRemoteSig(localCommitTx, ChannelSpendSignature.IndividualSignature(commit.signature)) + val commitTxRemoteSig = params.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)) } - val htlcTxsAndRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map { + val htlcRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map { case (htlcTx: HtlcTx, remoteSig) => if (!htlcTx.checkRemoteSig(commitKeys, remoteSig, params.commitmentFormat)) { return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid)) } - HtlcTxAndRemoteSig(htlcTx, remoteSig) + remoteSig } - Right(LocalCommit(localCommitIndex, spec, commitxTxAndRemoteSig, htlcTxsAndRemoteSigs)) + 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) { +case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) { def sign(params: ChannelParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo): CommitSig = { val fundingKey = channelKeys.fundingKey(fundingTxIndex) val commitKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) @@ -267,18 +266,26 @@ case class CommitTxIds(localCommitTxId: TxId, remoteCommitTxId: TxId, nextRemote case class Commitment(fundingTxIndex: Long, firstRemoteCommitIndex: Long, remoteFundingPubKey: PublicKey, + fundingTxOutpoint: OutPoint, + fundingAmount: Satoshi, localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, + format: CommitmentFormat, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { - val commitInput: InputInfo = localCommit.commitTxAndRemoteSig.commitTx.input - val fundingTxId: TxId = commitInput.outPoint.txid - val commitTxIds: CommitTxIds = CommitTxIds(localCommit.commitTxAndRemoteSig.commitTx.tx.txid, remoteCommit.txid, nextRemoteCommit_opt.map(_.commit.txid)) - val capacity: Satoshi = commitInput.txOut.amount + + val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId)) + val fundingTxId: TxId = fundingTxOutpoint.txid + val capacity: Satoshi = fundingAmount /** 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 commitInput(channelKeys: ChannelKeys): InputInfo = + Helpers.Funding.makeFundingInputInfo(fundingTxOutpoint.txid, fundingTxOutpoint.index.toInt, fundingAmount, localFundingKey(channelKeys).publicKey, remoteFundingPubKey, format) + + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = channelKeys.fundingKey(fundingTxIndex) + def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index) def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) @@ -650,7 +657,7 @@ case class Commitment(fundingTxIndex: Long, // 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, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput, spec) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(params, commitKeys, remoteCommit.index + 1, fundingKey, remoteFundingPubKey, commitInput(channelKeys), spec) val htlcSigs = htlcTxs.sortBy(_.input.outPoint.index).map(_.sign(commitKeys, params.commitmentFormat)) // NB: IN/OUT htlcs are inverted because this is the remote commit @@ -682,17 +689,18 @@ case class Commitment(fundingTxIndex: Long, val localCommitIndex = localCommit.index + 1 val fundingKey = channelKeys.fundingKey(fundingTxIndex) 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 => - 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.commitTxAndRemoteSig.commitTx.tx.txid} fundingTxId=$fundingTxId", spec.htlcs.collect(DirectedHtlc.incoming).map(_.id).mkString(","), spec.htlcs.collect(DirectedHtlc.outgoing).map(_.id).mkString(",")) + LocalCommit.fromCommitSig(params, commitKeys, fundingTxId, fundingKey, remoteFundingPubKey, commitInput(channelKeys), commit, localCommitIndex, spec).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) } } /** Return a fully signed commit tx, that can be published as-is. */ - def fullySignedLocalCommitTx(channelKeys: ChannelKeys): Transaction = { - val unsignedCommitTx = localCommit.commitTxAndRemoteSig.commitTx + def fullySignedLocalCommitTx(params: ChannelParams, channelKeys: ChannelKeys): Transaction = { val fundingKey = channelKeys.fundingKey(fundingTxIndex) - localCommit.commitTxAndRemoteSig.remoteSig match { + val commitKeys = localKeys(params, channelKeys) + val (unsignedCommitTx, _) = Commitment.makeLocalTxs(params, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(channelKeys), localCommit.spec) + localCommit.remoteSig match { case remoteSig: ChannelSpendSignature.IndividualSignature => val localSig = unsignedCommitTx.sign(fundingKey, remoteFundingPubKey) unsignedCommitTx.aggregateSigs(fundingKey.publicKey, remoteFundingPubKey, localSig, remoteSig) @@ -700,6 +708,19 @@ 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[(HtlcTx, ByteVector64)] = { + val commitKeys = localKeys(params, channelKeys) + htlcTxs(params, channelKeys, commitKeys) + } + + /** Return the HTLC transactions for our local commit and the corresponding remote signatures. */ + def htlcTxs(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys): Seq[(HtlcTx, ByteVector64)] = { + val fundingKey = channelKeys.fundingKey(fundingTxIndex) + val (_, htlcTxs) = Commitment.makeLocalTxs(params, commitKeys, localCommit.index, fundingKey, remoteFundingPubKey, commitInput(channelKeys), localCommit.spec) + htlcTxs.sortBy(_.input.outPoint.index).zip(localCommit.htlcRemoteSigs) + } + } object Commitment { @@ -738,24 +759,28 @@ case class AnnouncedCommitment(commitment: Commitment, announcement: ChannelAnno } /** Subset of Commitments when we want to work with a single, specific commitment. */ -case class FullCommitment(params: ChannelParams, changes: CommitmentChanges, - fundingTxIndex: Long, - firstRemoteCommitIndex: Long, - remoteFundingPubKey: PublicKey, - localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, - localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { +case class FullCommitment(params: ChannelParams, + changes: CommitmentChanges, + commitment: Commitment) { val channelId: ByteVector32 = params.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 localParams: LocalParams = params.localParams val remoteParams: RemoteParams = params.remoteParams - val commitInput: InputInfo = localCommit.commitTxAndRemoteSig.commitTx.input - val fundingTxId: TxId = commitInput.outPoint.txid - val commitTxIds: CommitTxIds = CommitTxIds(localCommit.commitTxAndRemoteSig.commitTx.tx.txid, remoteCommit.txid, nextRemoteCommit_opt.map(_.commit.txid)) - val capacity: Satoshi = commitInput.txOut.amount - val commitment: Commitment = Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) + val fundingTxOutpoint: OutPoint = commitment.fundingTxOutpoint + val fundingTxId: TxId = commitment.fundingTxOutpoint.txid + val commitTxIds: CommitTxIds = commitment.commitTxIds + val fundingTxIndex: Long = commitment.fundingTxIndex + val capacity: Satoshi = commitment.capacity + val remoteFundingPubKey: PublicKey = commitment.remoteFundingPubKey + val localCommit: LocalCommit = commitment.localCommit + val remoteCommit: RemoteCommit = commitment.remoteCommit + val nextRemoteCommit_opt: Option[NextRemoteCommit] = commitment.nextRemoteCommit_opt + val localFundingStatus: LocalFundingStatus = commitment.localFundingStatus + val remoteFundingStatus: RemoteFundingStatus = commitment.remoteFundingStatus + + def commitInput(channelKeys: ChannelKeys): InputInfo = commitment.commitInput(channelKeys) + + def localFundingKey(channelKeys: ChannelKeys): PrivateKey = commitment.localFundingKey(channelKeys) def localKeys(channelKeys: ChannelKeys): LocalCommitmentKeys = commitment.localKeys(params, channelKeys) @@ -765,7 +790,11 @@ case class FullCommitment(params: ChannelParams, changes: CommitmentChanges, def remoteChannelReserve: Satoshi = commitment.remoteChannelReserve(params) - def fullySignedLocalCommitTx(channelKeys: ChannelKeys): Transaction = commitment.fullySignedLocalCommitTx(channelKeys) + def fullySignedLocalCommitTx(channelKeys: ChannelKeys): Transaction = commitment.fullySignedLocalCommitTx(params, channelKeys) + + def htlcTxs(channelKeys: ChannelKeys): Seq[(HtlcTx, ByteVector64)] = commitment.htlcTxs(params, channelKeys) + + def htlcTxs(channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys): Seq[(HtlcTx, ByteVector64)] = commitment.htlcTxs(params, channelKeys, commitKeys) def specs2String: String = { s"""specs: @@ -827,7 +856,7 @@ case class Commitments(params: ChannelParams, 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(params, 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(params, 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 @@ -1161,16 +1190,18 @@ case class Commitments(params: ChannelParams, def validateSeed(channelKeys: ChannelKeys): Boolean = { active.forall { commitment => + // TODO: the following is circular, need another method val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex).publicKey val remoteFundingKey = commitment.remoteFundingPubKey val redeemInfo = Helpers.Funding.makeFundingScript(localFundingKey, remoteFundingKey, params.commitmentFormat) - commitment.commitInput.txOut.publicKeyScript == redeemInfo.pubkeyScript + val commitInput = commitment.commitInput(channelKeys) + commitInput.txOut.publicKeyScript == redeemInfo.pubkeyScript } } /** This function should be used to ignore a commit_sig that we've already received. */ def ignoreRetransmittedCommitSig(commitSig: CommitSig): Boolean = { - val isLatestSig = latest.localCommit.commitTxAndRemoteSig.remoteSig match { + val isLatestSig = latest.localCommit.remoteSig match { case ChannelSpendSignature.IndividualSignature(latestRemoteSig) => latestRemoteSig == commitSig.signature case ChannelSpendSignature.PartialSignatureWithNonce(_, _) => ??? } @@ -1302,7 +1333,7 @@ case class Commitments(params: 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.fundingTxOutpoint)) } /** 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 857579d2ae..8b3f91bf04 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 @@ -662,9 +662,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.localParams.paysClosingFees, Satoshi(0), Satoshi(0), commitment.localCommit.spec) + val dummyClosingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localParams.paysClosingFees, Satoshi(0), Satoshi(0), commitment.localCommit.spec) val dummyPubkey = commitment.remoteFundingPubKey val dummySig = ChannelSpendSignature.IndividualSignature(Transactions.PlaceHolderSig) val closingWeight = dummyClosingTx.aggregateSigs(dummyPubkey, dummyPubkey, dummySig, dummySig).weight() @@ -672,7 +672,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.params.commitmentFormat match { case DefaultCommitmentFormat => @@ -683,15 +683,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) } @@ -699,7 +699,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.localParams.dustLimit.max(commitment.remoteParams.dustLimit) - val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) + val closingTx = ClosingTx.createUnsignedTx(commitment.commitInput(channelKeys), localScriptPubkey, remoteScriptPubkey, commitment.localParams.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}") @@ -726,7 +726,7 @@ object Helpers { 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 closingFee = { - val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput(channelKeys), commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) dummyClosingTxs.preferred_opt match { case Some(dummyTx) => val dummyPubkey = commitment.remoteFundingPubKey @@ -737,7 +737,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(commitment.commitInput(channelKeys), 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)) @@ -759,7 +759,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 { @@ -883,29 +883,30 @@ object Helpers { /** Claim all the outputs that belong to us in our local commitment transaction. */ def claimCommitTxOutputs(channelKeys: ChannelKeys, commitment: FullCommitment, commitTx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (LocalCommitPublished, SecondStageTransactions) = { - require(commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid == commitTx.txid, "txid mismatch, provided tx is not the current local commit tx") + require(commitment.localCommit.txId == commitTx.txid, "txid mismatch, provided tx is not the current local commit tx") val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val commitmentKeys = commitment.localKeys(channelKeys) val feerateDelayed = onChainFeeConf.getClosingFeerate(feerates) val mainDelayedTx_opt = withTxGenerationLog("local-main-delayed") { ClaimLocalDelayedOutputTx.createSignedTx(commitmentKeys, commitTx, commitment.localParams.dustLimit, commitment.remoteParams.toSelfDelay, finalScriptPubKey, feerateDelayed, commitment.params.commitmentFormat) } - val htlcTxs = claimHtlcOutputs(commitmentKeys, commitment) - val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs - val anchorTx_opt = if (spendAnchors) { + val htlcs = claimHtlcOutputs(channelKeys, commitmentKeys, commitment) + val anchorOutput_opt = ClaimAnchorOutputTx.findInput(commitTx, fundingKey.publicKey, commitmentKeys, commitment.params.commitmentFormat).toOption + val spendAnchor = htlcs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs + val anchorTx_opt = if (spendAnchor) { claimAnchor(fundingKey, commitmentKeys, commitTx, commitment.params.commitmentFormat) } else { None } val lcp = LocalCommitPublished( commitTx = commitTx, - claimMainDelayedOutputTx = mainDelayedTx_opt, - htlcTxs = htlcTxs, - claimHtlcDelayedTxs = Nil, // we will claim these once the htlc txs are confirmed - claimAnchorTxs = anchorTx_opt.toList, + localOutput_opt = mainDelayedTx_opt.map(_.input.outPoint), + anchorOutput_opt = anchorOutput_opt.map(_.outPoint), + htlcs = htlcs.map { case (outpoint, (htlcId, _)) => (outpoint, htlcId) }, + htlcDelayedOutputs = Set.empty, // we will add these once the htlc txs are confirmed irrevocablySpent = Map.empty ) - val txs = SecondStageTransactions(mainDelayedTx_opt, anchorTx_opt, htlcTxs.values.flatten.toSeq) + val txs = SecondStageTransactions(mainDelayedTx_opt, anchorTx_opt, htlcs.values.flatMap(_._2).toSeq) (lcp, txs) } @@ -915,11 +916,17 @@ object Helpers { } } + /** Create outputs of the local commitment transaction, allowing us for example to identify HTLC outputs. */ + def makeLocalCommitTxOutputs(channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys, commitment: FullCommitment): Seq[CommitmentOutput] = { + val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) + makeCommitTxOutputs(fundingKey.publicKey, commitment.remoteFundingPubKey, commitKeys.publicKeys, commitment.localParams.paysCommitTxFees, commitment.localParams.dustLimit, commitment.remoteParams.toSelfDelay, commitment.localCommit.spec, commitment.params.commitmentFormat) + } + /** * Claim the outputs of a local commit tx corresponding to HTLCs. If we don't have the preimage for a received * * HTLC, we still include an entry in the map because we may receive that preimage later. */ - private def claimHtlcOutputs(commitKeys: LocalCommitmentKeys, commitment: FullCommitment)(implicit log: LoggingAdapter): Map[OutPoint, Option[HtlcTx]] = { + private def claimHtlcOutputs(channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys, commitment: FullCommitment)(implicit log: LoggingAdapter): Map[OutPoint, (DirectedHtlcId, Option[HtlcTx])] = { // We collect all the preimages available. val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case u: UpdateFulfillHtlc => Crypto.sha256(u.paymentPreimage) -> u.paymentPreimage @@ -932,15 +939,16 @@ object Helpers { // We collect incoming HTLCs that we haven't relayed: they may have been signed by our peer, but we haven't // received their revocation yet. val nonRelayedIncomingHtlcs: Set[Long] = commitment.changes.remoteChanges.all.collect { case add: UpdateAddHtlc => add.id }.toSet - commitment.localCommit.htlcTxsAndRemoteSigs.collect { - case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, remoteSig) => + commitment.htlcTxs(channelKeys, commitKeys).collect { + case (txInfo: HtlcSuccessTx, remoteSig) => if (preimages.contains(txInfo.paymentHash)) { // We immediately spend incoming htlcs for which we have the preimage. val preimage = preimages(txInfo.paymentHash) - Some(txInfo.input.outPoint -> withTxGenerationLog("htlc-success") { + val htlcTx_opt = withTxGenerationLog("htlc-success") { val localSig = txInfo.sign(commitKeys, commitment.params.commitmentFormat, Map.empty) Right(txInfo.addSigs(commitKeys, localSig, remoteSig, preimage, commitment.params.commitmentFormat)) - }) + } + Some(txInfo.input.outPoint -> (IncomingHtlcId(txInfo.htlcId), htlcTx_opt)) } else if (failedIncomingHtlcs.contains(txInfo.htlcId)) { // We can ignore incoming htlcs that we started failing: our peer will claim them after the timeout. // We don't track those outputs because we want to move to the CLOSED state even if our peer never claims them. @@ -952,39 +960,37 @@ object Helpers { // For all other incoming htlcs, we may receive the preimage later from downstream. We thus want to track // the corresponding outputs to ensure we don't move to the CLOSED state until they've been spent, either // by us if we receive the preimage, or by our peer after the timeout. - Some(txInfo.input.outPoint -> None) + Some(txInfo.input.outPoint -> (IncomingHtlcId(txInfo.htlcId), None)) } - case HtlcTxAndRemoteSig(txInfo: HtlcTimeoutTx, remoteSig) => + case (txInfo: HtlcTimeoutTx, remoteSig) => // We track all outputs that belong to outgoing htlcs. Our peer may or may not have the preimage: if they // claim the output, we will learn the preimage from their transaction, otherwise we will get our funds // back after the timeout. - Some(txInfo.input.outPoint -> withTxGenerationLog("htlc-timeout") { + val htlcTx_opt = withTxGenerationLog("htlc-timeout") { val localSig = txInfo.sign(commitKeys, commitment.params.commitmentFormat, Map.empty) Right(txInfo.addSigs(commitKeys, localSig, remoteSig, commitment.params.commitmentFormat)) - }) + } + Some(txInfo.input.outPoint -> (OutgoingHtlcId(txInfo.htlcId), htlcTx_opt)) }.flatten.toMap } /** Claim the outputs of incoming HTLCs for the payment_hash matching the preimage provided. */ - def claimHtlcsWithPreimage(commitKeys: LocalCommitmentKeys, localCommitPublished: LocalCommitPublished, commitment: FullCommitment, preimage: ByteVector32)(implicit log: LoggingAdapter): (LocalCommitPublished, Seq[TxPublisher.PublishTx]) = { - val (htlcTxs, toPublish) = commitment.localCommit.htlcTxsAndRemoteSigs.collect { - case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, remoteSig) if txInfo.paymentHash == Crypto.sha256(preimage) => + def claimHtlcsWithPreimage(channelKeys: ChannelKeys, localCommitPublished: LocalCommitPublished, commitment: FullCommitment, preimage: ByteVector32)(implicit log: LoggingAdapter): Seq[TxPublisher.PublishTx] = { + val commitKeys = commitment.localKeys(channelKeys) + commitment.htlcTxs(channelKeys, commitKeys).collect { + case (txInfo: HtlcSuccessTx, remoteSig) if txInfo.paymentHash == Crypto.sha256(preimage) => withTxGenerationLog("htlc-success") { val localSig = txInfo.sign(commitKeys, commitment.params.commitmentFormat, Map.empty) Right(txInfo.addSigs(commitKeys, localSig, remoteSig, preimage, commitment.params.commitmentFormat)) }.map(signedTx => { - val toPublish = commitment.params.commitmentFormat match { + commitment.params.commitmentFormat match { case DefaultCommitmentFormat => TxPublisher.PublishFinalTx(signedTx, signedTx.fee, Some(localCommitPublished.commitTx.txid)) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => val confirmationTarget = ConfirmationTarget.Absolute(txInfo.htlcExpiry.blockHeight) TxPublisher.PublishReplaceableTx(ReplaceableHtlcSuccess(signedTx, commitKeys, preimage, remoteSig, localCommitPublished.commitTx, commitment), confirmationTarget) } - (signedTx, toPublish) }) - }.flatten.unzip - val additionalHtlcTxs = htlcTxs.map(tx => tx.input.outPoint -> Some(tx)).toMap[OutPoint, Option[HtlcTx]] - val localCommitPublished1 = localCommitPublished.copy(htlcTxs = localCommitPublished.htlcTxs ++ additionalHtlcTxs) - (localCommitPublished1, toPublish) + }.flatten } /** @@ -998,10 +1004,13 @@ object Helpers { val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case u: UpdateFulfillHtlc => Crypto.sha256(u.paymentPreimage) -> u.paymentPreimage }.toMap - val outpoints = commitment.localCommit.htlcTxsAndRemoteSigs.collect { - case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, _) if txInfo.htlcId == htlcId && !preimages.contains(txInfo.paymentHash) => txInfo.input.outPoint + val htlcsWithPreimage = commitment.localCommit.spec.htlcs.collect { + case IncomingHtlc(add: UpdateAddHtlc) if preimages.contains(add.paymentHash) => add.id + } + val outpoints = localCommitPublished.htlcs.collect { + case (outpoint, IncomingHtlcId(id)) if id == htlcId && !htlcsWithPreimage.contains(id) => outpoint }.toSet - localCommitPublished.copy(htlcTxs = localCommitPublished.htlcTxs -- outpoints) + localCommitPublished.copy(htlcs = localCommitPublished.htlcs -- outpoints) } /** @@ -1011,7 +1020,7 @@ object Helpers { * doing that because it introduces a lot of subtle edge cases. */ def claimHtlcDelayedOutput(localCommitPublished: LocalCommitPublished, channelKeys: ChannelKeys, commitment: FullCommitment, tx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (LocalCommitPublished, ThirdStageTransactions) = { - if (tx.txIn.exists(txIn => localCommitPublished.htlcTxs.contains(txIn.outPoint))) { + if (tx.txIn.exists(txIn => localCommitPublished.htlcOutputs.contains(txIn.outPoint))) { val feerateDelayed = onChainFeeConf.getClosingFeerate(feerates) val commitKeys = commitment.localKeys(channelKeys) // Note that this will return None if the transaction wasn't one of our HTLC transactions, which may happen @@ -1019,13 +1028,22 @@ object Helpers { val htlcDelayedTx_opt = withTxGenerationLog("htlc-delayed") { HtlcDelayedTx.createSignedTx(commitKeys, tx, commitment.localParams.dustLimit, commitment.remoteParams.toSelfDelay, finalScriptPubKey, feerateDelayed, commitment.params.commitmentFormat) } - val localCommitPublished1 = localCommitPublished.copy(claimHtlcDelayedTxs = localCommitPublished.claimHtlcDelayedTxs ++ htlcDelayedTx_opt.toSeq) + val localCommitPublished1 = localCommitPublished.copy(htlcDelayedOutputs = localCommitPublished.htlcDelayedOutputs ++ htlcDelayedTx_opt.map(_.input.outPoint).toSeq) (localCommitPublished1, ThirdStageTransactions(htlcDelayedTx_opt.toSeq)) } else { (localCommitPublished, ThirdStageTransactions(Nil)) } } + /** + * Claim the outputs of all 2nd-stage HTLC transactions that have been confirmed. + */ + def claimHtlcDelayedOutputs(localCommitPublished: LocalCommitPublished, channelKeys: ChannelKeys, commitment: FullCommitment, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { + val confirmedHtlcTxs = localCommitPublished.htlcOutputs.flatMap(htlcOutput => localCommitPublished.irrevocablySpent.get(htlcOutput)) + val htlcDelayedTxs = confirmedHtlcTxs.flatMap(tx => claimHtlcDelayedOutput(localCommitPublished, channelKeys, commitment, tx, feerates, onChainFeeConf, finalScriptPubKey)._2.htlcDelayedTxs) + ThirdStageTransactions(htlcDelayedTxs.toSeq) + } + } object RemoteClose { @@ -1035,25 +1053,26 @@ object Helpers { /** Claim all the outputs that belong to us in the remote commitment transaction (which can be either their current or next commitment). */ def claimCommitTxOutputs(channelKeys: ChannelKeys, commitment: FullCommitment, remoteCommit: RemoteCommit, commitTx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RemoteCommitPublished, SecondStageTransactions) = { - require(remoteCommit.txid == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") + require(remoteCommit.txId == commitTx.txid, "txid mismatch, provided tx is not the current remote commit tx") val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) val mainTx_opt = claimMainOutput(commitment.params, commitKeys, commitTx, feerates, onChainFeeConf, finalScriptPubKey) - val htlcTxs = claimHtlcOutputs(channelKeys, commitKeys, commitment, remoteCommit, finalScriptPubKey) - val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs - val anchorTx_opt = if (spendAnchors) { + val htlcs = claimHtlcOutputs(channelKeys, commitKeys, commitment, remoteCommit, finalScriptPubKey) + val anchorOutput_opt = ClaimAnchorOutputTx.findInput(commitTx, fundingKey.publicKey, commitKeys, commitment.params.commitmentFormat).toOption + val spendAnchor = htlcs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs + val anchorTx_opt = if (spendAnchor) { claimAnchor(fundingKey, commitKeys, commitTx, commitment.params.commitmentFormat) } else { None } val rcp = RemoteCommitPublished( commitTx = commitTx, - claimMainOutputTx = mainTx_opt, - claimHtlcTxs = htlcTxs, - claimAnchorTxs = anchorTx_opt.toList, + localOutput_opt = mainTx_opt.map(_.input.outPoint), + anchorOutput_opt = anchorOutput_opt.map(_.outPoint), + htlcs = htlcs.map { case (outpoint, (htlcId, _)) => (outpoint, htlcId) }, irrevocablySpent = Map.empty ) - val txs = SecondStageTransactions(mainTx_opt, anchorTx_opt, htlcTxs.values.flatten.toSeq) + val txs = SecondStageTransactions(mainTx_opt, anchorTx_opt, htlcs.values.flatMap(_._2).toSeq) (rcp, txs) } @@ -1086,13 +1105,12 @@ object Helpers { * Claim the outputs of a remote commit tx corresponding to HTLCs. If we don't have the preimage for a received * * HTLC, we still include an entry in the map because we may receive that preimage later. */ - private def claimHtlcOutputs(channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, commitment: FullCommitment, remoteCommit: RemoteCommit, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Map[OutPoint, Option[ClaimHtlcTx]] = { + private def claimHtlcOutputs(channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, commitment: FullCommitment, remoteCommit: RemoteCommit, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Map[OutPoint, (DirectedHtlcId, Option[ClaimHtlcTx])] = { val outputs = makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) - val remoteCommitTx = makeCommitTx(commitment.commitInput, remoteCommit.index, commitment.params.remoteParams.paymentBasepoint, commitKeys.ourPaymentBasePoint, !commitment.params.localParams.isChannelOpener, outputs) - require(remoteCommitTx.tx.txid == remoteCommit.txid, "txid mismatch, cannot recompute the current remote commit tx") + val remoteCommitTx = makeCommitTx(commitment.commitInput(channelKeys), remoteCommit.index, commitment.params.remoteParams.paymentBasepoint, commitKeys.ourPaymentBasePoint, !commitment.params.localParams.isChannelOpener, outputs) + require(remoteCommitTx.tx.txid == remoteCommit.txId, "txid mismatch, cannot recompute the current remote commit tx") // The feerate will be set by the publisher actor based on the HTLC expiry, we don't care which feerate is used here. val feerate = FeeratePerKw(FeeratePerByte(1 sat)) - // We collect all the preimages available. val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case u: UpdateFulfillHtlc => Crypto.sha256(u.paymentPreimage) -> u.paymentPreimage @@ -1105,7 +1123,6 @@ object Helpers { // We collect incoming HTLCs that we haven't relayed: they may have been signed by our peer, but they haven't // sent their revocation yet. val nonRelayedIncomingHtlcs: Set[Long] = commitment.changes.remoteChanges.all.collect { case add: UpdateAddHtlc => add.id }.toSet - // Remember we are looking at the remote commitment so IN for them is really OUT for us and vice versa. remoteCommit.spec.htlcs.collect { case OutgoingHtlc(add: UpdateAddHtlc) => @@ -1114,7 +1131,7 @@ object Helpers { val preimage = preimages(add.paymentHash) withTxGenerationLog("claim-htlc-success") { ClaimHtlcSuccessTx.createSignedTx(commitKeys, remoteCommitTx.tx, commitment.localParams.dustLimit, outputs, finalScriptPubKey, add, preimage, feerate, commitment.params.commitmentFormat) - }.map(claimHtlcTx => claimHtlcTx.input.outPoint -> Some(claimHtlcTx)) + }.map(claimHtlcTx => claimHtlcTx.input.outPoint -> (OutgoingHtlcId(add.id), Some(claimHtlcTx))) } else if (failedIncomingHtlcs.contains(add.id)) { // We can ignore incoming htlcs that we started failing: our peer will claim them after the timeout. // We don't track those outputs because we want to move to the CLOSED state even if our peer never claims them. @@ -1126,7 +1143,7 @@ object Helpers { // For all other incoming htlcs, we may receive the preimage later from downstream. We thus want to track // the corresponding outputs to ensure we don't move to the CLOSED state until they've been spent, either // by us if we receive the preimage, or by our peer after the timeout. - ClaimHtlcSuccessTx.findInput(remoteCommitTx.tx, outputs, add).map(input => input.outPoint -> None) + ClaimHtlcSuccessTx.findInput(remoteCommitTx.tx, outputs, add).map(input => input.outPoint -> (OutgoingHtlcId(add.id), None)) } case IncomingHtlc(add: UpdateAddHtlc) => // We track all outputs that belong to outgoing htlcs. Our peer may or may not have the preimage: if they @@ -1134,17 +1151,17 @@ object Helpers { // back after the timeout. withTxGenerationLog("claim-htlc-timeout") { ClaimHtlcTimeoutTx.createSignedTx(commitKeys, remoteCommitTx.tx, commitment.localParams.dustLimit, outputs, finalScriptPubKey, add, feerate, commitment.params.commitmentFormat) - }.map(claimHtlcTx => claimHtlcTx.input.outPoint -> Some(claimHtlcTx)) + }.map(claimHtlcTx => claimHtlcTx.input.outPoint -> (IncomingHtlcId(add.id), Some(claimHtlcTx))) }.flatten.toMap } /** Claim the outputs of incoming HTLCs for the payment_hash matching the preimage provided. */ - def claimHtlcsWithPreimage(channelKeys: ChannelKeys, remoteCommitPublished: RemoteCommitPublished, commitment: FullCommitment, remoteCommit: RemoteCommit, preimage: ByteVector32, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RemoteCommitPublished, Seq[TxPublisher.PublishReplaceableTx]) = { + def claimHtlcsWithPreimage(channelKeys: ChannelKeys, remoteCommitPublished: RemoteCommitPublished, commitment: FullCommitment, remoteCommit: RemoteCommit, preimage: ByteVector32, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Seq[TxPublisher.PublishReplaceableTx] = { val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) val outputs = makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) // The feerate will be set by the publisher actor based on the HTLC expiry, we don't care which feerate is used here. val feerate = FeeratePerKw(FeeratePerByte(1 sat)) - val toPublish = remoteCommit.spec.htlcs.collect { + remoteCommit.spec.htlcs.collect { // Remember we are looking at the remote commitment so IN for them is really OUT for us and vice versa. case OutgoingHtlc(add: UpdateAddHtlc) if add.paymentHash == Crypto.sha256(preimage) => withTxGenerationLog("claim-htlc-success") { @@ -1154,9 +1171,6 @@ object Helpers { TxPublisher.PublishReplaceableTx(ReplaceableClaimHtlcSuccess(signedTx, commitKeys, preimage, remoteCommitPublished.commitTx, commitment), confirmationTarget) } }.flatten.toSeq - val additionalHtlcTxs = toPublish.map(p => p.input -> Some(p.tx.txInfo.asInstanceOf[ClaimHtlcSuccessTx])).toMap[OutPoint, Option[ClaimHtlcTx]] - val remoteCommitPublished1 = remoteCommitPublished.copy(claimHtlcTxs = remoteCommitPublished.claimHtlcTxs ++ additionalHtlcTxs) - (remoteCommitPublished1, toPublish) } /** @@ -1165,19 +1179,19 @@ object Helpers { * We stop tracking the corresponding output because we want to move to the CLOSED state even if our peer never * claims it (which may happen if the HTLC amount is low and on-chain fees are high). */ - def ignoreFailedIncomingHtlc(channelKeys: ChannelKeys, htlcId: Long, remoteCommitPublished: RemoteCommitPublished, commitment: FullCommitment, remoteCommit: RemoteCommit): RemoteCommitPublished = { + def ignoreFailedIncomingHtlc(htlcId: Long, remoteCommitPublished: RemoteCommitPublished, commitment: FullCommitment, remoteCommit: RemoteCommit): RemoteCommitPublished = { // If we have the preimage (e.g. for partially fulfilled multi-part payments), we keep the HTLC-success tx. val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case u: UpdateFulfillHtlc => Crypto.sha256(u.paymentPreimage) -> u.paymentPreimage }.toMap - val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) - val outputs = makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) - val outpoints = remoteCommit.spec.htlcs.collect { - // Remember we are looking at the remote commitment so IN for them is really OUT for us and vice versa. - case OutgoingHtlc(add: UpdateAddHtlc) if add.id == htlcId && !preimages.contains(add.paymentHash) => - ClaimHtlcSuccessTx.findInput(remoteCommitPublished.commitTx, outputs, add).map(_.outPoint) - }.flatten - remoteCommitPublished.copy(claimHtlcTxs = remoteCommitPublished.claimHtlcTxs -- outpoints) + // Remember we are looking at the remote commitment so IN for them is really OUT for us and vice versa. + val htlcsWithPreimage = remoteCommit.spec.htlcs.collect { + case OutgoingHtlc(add: UpdateAddHtlc) if preimages.contains(add.paymentHash) => add.id + } + val outpoints = remoteCommitPublished.htlcs.collect { + case (outpoint, OutgoingHtlcId(id)) if id == htlcId && !htlcsWithPreimage.contains(id) => outpoint + }.toSet + remoteCommitPublished.copy(htlcs = remoteCommitPublished.htlcs -- outpoints) } } @@ -1257,10 +1271,10 @@ object Helpers { val rvk = RevokedCommitPublished( commitTx = commitTx, - claimMainOutputTx = mainTx_opt, - mainPenaltyTx = mainPenaltyTx_opt, - htlcPenaltyTxs = htlcPenaltyTxs.toList, - claimHtlcDelayedPenaltyTxs = Nil, // we will generate and spend those if they publish their HtlcSuccessTx or HtlcTimeoutTx + localOutput_opt = mainTx_opt.map(_.input.outPoint), + remoteOutput_opt = mainPenaltyTx_opt.map(_.input.outPoint), + htlcOutputs = htlcPenaltyTxs.map(_.input.outPoint).toSet, + htlcDelayedOutputs = Set.empty, // we will generate and spend those if their HtlcSuccessTx or HtlcTimeoutTx confirms irrevocablySpent = Map.empty ) val txs = SecondStageTransactions(mainTx_opt, mainPenaltyTx_opt, htlcPenaltyTxs) @@ -1285,26 +1299,14 @@ object Helpers { // 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 htlcOutputs = revokedCommitPublished.htlcPenaltyTxs.map(_.input.outPoint).toSet - val spendsHtlcOutput = htlcTx.txIn.exists(txIn => htlcOutputs.contains(txIn.outPoint)) + val spendsHtlcOutput = htlcTx.txIn.exists(txIn => revokedCommitPublished.htlcOutputs.contains(txIn.outPoint)) if (spendsHtlcOutput) { getRemotePerCommitmentSecret(params, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { case (_, remotePerCommitmentSecret) => val commitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentSecret.publicKey) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) - // 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 penaltyTxs = ClaimHtlcDelayedOutputPenaltyTx.createSignedTxs(commitmentKeys, revocationKey, htlcTx, params.localParams.dustLimit, params.localParams.toSelfDelay, finalScriptPubKey, feeratePenalty, params.commitmentFormat).flatMap(claimHtlcDelayedOutputPenaltyTx => { - withTxGenerationLog("htlc-delayed-penalty") { - claimHtlcDelayedOutputPenaltyTx.map(signedTx => { - // We need to make sure that the tx is indeed valid. - Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - log.warning("txId={} is a 2nd level htlc tx spending revoked commit txId={}: publishing htlc-penalty txId={}", htlcTx.txid, revokedCommitPublished.commitTx.txid, signedTx.tx.txid) - signedTx - }) - } - }) - val revokedCommitPublished1 = revokedCommitPublished.copy(claimHtlcDelayedPenaltyTxs = revokedCommitPublished.claimHtlcDelayedPenaltyTxs ++ penaltyTxs) + val penaltyTxs = claimHtlcTxOutputs(params, commitmentKeys, revocationKey, htlcTx, feerates, finalScriptPubKey) + val revokedCommitPublished1 = revokedCommitPublished.copy(htlcDelayedOutputs = revokedCommitPublished.htlcDelayedOutputs ++ penaltyTxs.map(_.input.outPoint)) val txs = ThirdStageTransactions(penaltyTxs) (revokedCommitPublished1, txs) }.getOrElse((revokedCommitPublished, ThirdStageTransactions(Nil))) @@ -1313,6 +1315,32 @@ object Helpers { } } + private def claimHtlcTxOutputs(params: ChannelParams, commitmentKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, htlcTx: Transaction, 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 + ClaimHtlcDelayedOutputPenaltyTx.createSignedTxs(commitmentKeys, revocationKey, htlcTx, params.localParams.dustLimit, params.localParams.toSelfDelay, finalScriptPubKey, feeratePenalty, params.commitmentFormat).flatMap(penaltyTx => { + withTxGenerationLog("htlc-delayed-penalty") { + penaltyTx.map(signedTx => { + // We need to make sure that the tx is indeed valid. + Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + log.warning("txId={} is a 2nd level htlc tx spending revoked commit txId={}: publishing htlc-penalty txId={}", htlcTx.txid, signedTx.input.outPoint.txid, signedTx.tx.txid) + signedTx + }) + } + }) + } + + /** + * 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) + 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)) + ThirdStageTransactions(penaltyTxs.toSeq) + } + } /** @@ -1357,27 +1385,25 @@ object Helpers { * * @return a set of htlcs that need to be failed upstream */ - def trimmedOrTimedOutHtlcs(commitmentFormat: CommitmentFormat, localCommit: LocalCommit, localDustLimit: Satoshi, confirmedTx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = { - val untrimmedHtlcs = Transactions.trimOfferedHtlcs(localDustLimit, localCommit.spec, commitmentFormat).map(_.add) - if (confirmedTx.txid == localCommit.commitTxAndRemoteSig.commitTx.tx.txid) { + def trimmedOrTimedOutHtlcs(channelKeys: ChannelKeys, commitment: FullCommitment, localCommit: LocalCommit, confirmedTx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = { + if (confirmedTx.txid == localCommit.txId) { // The commitment tx is confirmed: we can immediately fail all dust htlcs (they don't have an output in the tx). + val untrimmedHtlcs = Transactions.trimOfferedHtlcs(commitment.localParams.dustLimit, localCommit.spec, commitment.params.commitmentFormat).map(_.add) localCommit.spec.htlcs.collect(outgoing) -- untrimmedHtlcs - } else { - // Maybe this is a timeout tx: in that case we can resolve and fail the corresponding htlc. - confirmedTx.txIn.flatMap(txIn => localCommit.htlcTxsAndRemoteSigs.map(_.htlcTx).find(_.input.outPoint == txIn.outPoint) match { + } else if (confirmedTx.txIn.exists(_.outPoint.txid == localCommit.txId)) { + // The transaction spends the commitment tx: maybe it is a timeout tx, in which case we can resolve and fail the + // corresponding htlc. + val outputs = LocalClose.makeLocalCommitTxOutputs(channelKeys, commitment.localKeys(channelKeys), commitment) + confirmedTx.txIn.filter(_.outPoint.txid == localCommit.txId).flatMap(txIn => outputs(txIn.outPoint.index.toInt) match { // This may also be our peer claiming the HTLC by revealing the preimage: in that case we have already // extracted the preimage with [[extractPreimages]] and relayed it upstream. - case Some(htlcTimeoutTx: HtlcTimeoutTx) if Scripts.extractPreimagesFromClaimHtlcSuccess(confirmedTx).isEmpty => - untrimmedHtlcs.find(_.id == htlcTimeoutTx.htlcId) match { - case Some(htlc) => - log.info("htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", htlcTimeoutTx.htlcId, htlc.paymentHash, confirmedTx.lockTime, confirmedTx) - Some(htlc) - case None => - log.error("could not find htlc #{} for htlc-timeout tx={}", htlcTimeoutTx.htlcId, confirmedTx) - None - } + case CommitmentOutput.OutHtlc(htlc, _, _) if Scripts.extractPreimagesFromClaimHtlcSuccess(confirmedTx).isEmpty => + log.info("htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", htlc.add.id, htlc.add.paymentHash, htlc.add.cltvExpiry, confirmedTx) + Some(htlc.add) case _ => None }).toSet + } else { + Set.empty } } @@ -1389,16 +1415,16 @@ object Helpers { * @return a set of htlcs that need to be failed upstream */ def trimmedOrTimedOutHtlcs(channelKeys: ChannelKeys, commitment: FullCommitment, remoteCommit: RemoteCommit, confirmedTx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = { - if (confirmedTx.txid == remoteCommit.txid) { + if (confirmedTx.txid == remoteCommit.txId) { // The commitment tx is confirmed: we can immediately fail all dust htlcs (they don't have an output in the tx). val untrimmedHtlcs = Transactions.trimReceivedHtlcs(commitment.remoteParams.dustLimit, remoteCommit.spec, commitment.params.commitmentFormat).map(_.add) remoteCommit.spec.htlcs.collect(incoming) -- untrimmedHtlcs - } else if (confirmedTx.txIn.exists(_.outPoint.txid == remoteCommit.txid)) { + } else if (confirmedTx.txIn.exists(_.outPoint.txid == remoteCommit.txId)) { // The transaction spends the commitment tx: maybe it is a timeout tx, in which case we can resolve and fail the // corresponding htlc. val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) val outputs = RemoteClose.makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) - confirmedTx.txIn.filter(_.outPoint.txid == remoteCommit.txid).flatMap(txIn => outputs(txIn.outPoint.index.toInt) match { + confirmedTx.txIn.filter(_.outPoint.txid == remoteCommit.txId).flatMap(txIn => outputs(txIn.outPoint.index.toInt) match { // This may also be our peer claiming the HTLC by revealing the preimage: in that case we have already // extracted the preimage with [[extractPreimages]] and relayed it upstream. // Note: we're looking at the remote commitment, so it's an incoming HTLC for them (outgoing for us). @@ -1419,11 +1445,11 @@ object Helpers { * @param tx a transaction that is sufficiently buried in the blockchain */ def onChainOutgoingHtlcs(localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[RemoteCommit], tx: Transaction): Set[UpdateAddHtlc] = { - if (localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid) { + if (localCommit.txId == tx.txid) { localCommit.spec.htlcs.collect(outgoing) - } else if (remoteCommit.txid == tx.txid) { + } else if (remoteCommit.txId == tx.txid) { remoteCommit.spec.htlcs.collect(incoming) - } else if (nextRemoteCommit_opt.map(_.txid).contains(tx.txid)) { + } else if (nextRemoteCommit_opt.map(_.txId).contains(tx.txid)) { nextRemoteCommit_opt.get.spec.htlcs.collect(incoming) } else { Set.empty @@ -1441,7 +1467,7 @@ object Helpers { val nextRemoteCommit_opt = d.commitments.latest.nextRemoteCommit_opt.map(_.commit) // NB: from the p.o.v of remote, their incoming htlcs are our outgoing htlcs. val outgoingHtlcs = localCommit.spec.htlcs.collect(outgoing) ++ (remoteCommit.spec.htlcs ++ nextRemoteCommit_opt.map(_.spec.htlcs).getOrElse(Set.empty)).collect(incoming) - if (localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid) { + if (localCommit.txId == tx.txid) { // Our commit got confirmed: any htlc that is *not* in our commit will never reach the chain. outgoingHtlcs -- localCommit.spec.htlcs.collect(outgoing) } else if (d.revokedCommitPublished.map(_.commitTx.txid).contains(tx.txid)) { @@ -1452,10 +1478,10 @@ object Helpers { // upstream. In the best case scenario, we already fulfilled upstream, then the fail will be a no-op and we // will pocket the htlc amount. outgoingHtlcs - } else if (remoteCommit.txid == tx.txid) { + } else if (remoteCommit.txId == tx.txid) { // Their current commit got confirmed: any htlc that is *not* in their current commit will never reach the chain. outgoingHtlcs -- remoteCommit.spec.htlcs.collect(incoming) - } else if (nextRemoteCommit_opt.map(_.txid).contains(tx.txid)) { + } else if (nextRemoteCommit_opt.map(_.txId).contains(tx.txid)) { // Their next commit got confirmed: any htlc that is *not* in their next commit will never reach the chain. outgoingHtlcs -- nextRemoteCommit_opt.map(_.spec.htlcs).getOrElse(Set.empty).collect(incoming) } else { @@ -1483,7 +1509,7 @@ object Helpers { val spendsTheCommitTx = localCommitPublished.commitTx.txid == outPoint.txid // is the tx one of our 3rd stage delayed txs? (a 3rd stage tx is a tx spending the output of an htlc tx, which // is itself spending the output of the commitment tx) - val is3rdStageDelayedTx = localCommitPublished.claimHtlcDelayedTxs.map(_.input.outPoint).contains(outPoint) + val is3rdStageDelayedTx = localCommitPublished.htlcDelayedOutputs.contains(outPoint) isCommitTx || spendsTheCommitTx || is3rdStageDelayedTx }) // then we add the relevant outpoints to the map keeping track of which txid spends which outpoint @@ -1534,7 +1560,7 @@ object Helpers { val spendsTheCommitTx = revokedCommitPublished.commitTx.txid == outPoint.txid // is the tx one of our 3rd stage delayed txs? (a 3rd stage tx is a tx spending the output of an htlc tx, which // is itself spending the output of the commitment tx) - val is3rdStageDelayedTx = revokedCommitPublished.claimHtlcDelayedPenaltyTxs.map(_.input.outPoint).contains(outPoint) + val is3rdStageDelayedTx = revokedCommitPublished.htlcDelayedOutputs.contains(outPoint) isCommitTx || spendsTheCommitTx || is3rdStageDelayedTx }) // then we add the relevant outpoints to the map keeping track of which txid spends which outpoint 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 5a82f46aef..afc0ff25fd 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 @@ -321,11 +321,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall watchFundingConfirmed(fundingTx.tx.txid, Some(nodeParams.channelConf.minDepth), herdDelay_opt) case _: LocalFundingStatus.ConfirmedFundingTx => data match { - case closing: DATA_CLOSING if Closing.nothingAtStake(closing) || Closing.isClosingTypeAlreadyKnown(closing).isDefined => - // no need to do anything - () + case closing: DATA_CLOSING if Closing.nothingAtStake(closing) => () + // No need to watch the funding tx, it has already been spent and the spending tx has already reached mindepth. + case closing: DATA_CLOSING if Closing.isClosingTypeAlreadyKnown(closing).isDefined => () + // In all other cases we need to be ready for any type of closing. case closing: DATA_CLOSING => - // in all other cases we need to be ready for any type of closing watchFundingSpent(commitment, closing.spendingTxs.map(_.txid).toSet, herdDelay_opt) case negotiating: DATA_NEGOTIATING => val closingTxs = negotiating.closingTxProposed.flatten.map(_.unsignedTx.tx.txid).toSet @@ -350,39 +350,63 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall goto(CLOSED) using closing case closing: DATA_CLOSING => val localPaysClosingFees = closing.commitments.params.localParams.paysClosingFees - // we don't put back the WatchSpent if the commitment tx has already been published and the spending tx already reached mindepth val closingType_opt = Closing.isClosingTypeAlreadyKnown(closing) log.info(s"channel is closing (closingType=${closingType_opt.map(c => EventType.Closed(c).label).getOrElse("UnknownYet")})") - // if the closing type is known: - // - there is no need to watch the funding tx because it has already been spent and the spending tx has already reached mindepth - // - there is no need to attempt to publish transactions for other type of closes - // - there is a single commitment, the others have all been invalidated + // If the closing type is known: + // - there is no need to attempt to publish transactions for other type of closes + // - there may be 3rd-stage transactions to publish + // - there is a single commitment, the others have all been invalidated + val commitment = closing.commitments.latest closingType_opt match { case Some(c: Closing.MutualClose) => doPublish(c.tx, localPaysClosingFees) case Some(c: Closing.LocalClose) => - val secondStageTransactions = Closing.LocalClose.SecondStageTransactions(c.localCommitPublished.claimMainDelayedOutputTx, c.localCommitPublished.claimAnchorTx_opt, c.localCommitPublished.htlcTxs.values.flatten.toSeq) - doPublish(c.localCommitPublished, secondStageTransactions, closing.commitments.latest) - val thirdStageTransactions = Closing.LocalClose.ThirdStageTransactions(c.localCommitPublished.claimHtlcDelayedTxs) + val (_, secondStageTransactions) = Closing.LocalClose.claimCommitTxOutputs(channelKeys, commitment, c.localCommitPublished.commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(c.localCommitPublished, secondStageTransactions, commitment) + val thirdStageTransactions = Closing.LocalClose.claimHtlcDelayedOutputs(c.localCommitPublished, channelKeys, commitment, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) doPublish(c.localCommitPublished, thirdStageTransactions) case Some(c: Closing.RemoteClose) => - val secondStageTransactions = Closing.RemoteClose.SecondStageTransactions(c.remoteCommitPublished.claimMainOutputTx, c.remoteCommitPublished.claimAnchorTx_opt, c.remoteCommitPublished.claimHtlcTxs.values.flatten.toSeq) - doPublish(c.remoteCommitPublished, secondStageTransactions, closing.commitments.latest) + val (_, secondStageTransactions) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, c.remoteCommit, c.remoteCommitPublished.commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(c.remoteCommitPublished, secondStageTransactions, commitment, closing.finalScriptPubKey) case Some(c: Closing.RecoveryClose) => - val secondStageTransactions = Closing.RemoteClose.SecondStageTransactions(c.remoteCommitPublished.claimMainOutputTx, c.remoteCommitPublished.claimAnchorTx_opt, c.remoteCommitPublished.claimHtlcTxs.values.flatten.toSeq) - doPublish(c.remoteCommitPublished, secondStageTransactions, closing.commitments.latest) + // We cannot do anything in that case: we've already published our recovery transaction before restarting, + // and must wait for it to confirm. + doPublish(c.remoteCommitPublished, Closing.RemoteClose.SecondStageTransactions(None, None, Nil), commitment, closing.finalScriptPubKey) case Some(c: Closing.RevokedClose) => - val secondStageTransactions = Closing.RevokedClose.SecondStageTransactions(c.revokedCommitPublished.claimMainOutputTx, c.revokedCommitPublished.mainPenaltyTx, c.revokedCommitPublished.htlcPenaltyTxs) - doPublish(c.revokedCommitPublished, secondStageTransactions) - val thirdStageTransactions = Closing.RevokedClose.ThirdStageTransactions(c.revokedCommitPublished.claimHtlcDelayedPenaltyTxs) - doPublish(c.revokedCommitPublished, thirdStageTransactions) + Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.params, channelKeys, closing.commitments.remotePerCommitmentSecrets, c.revokedCommitPublished.commitTx).foreach { + case (commitmentNumber, remotePerCommitmentSecret) => + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.params, channelKeys, c.revokedCommitPublished.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(c.revokedCommitPublished, secondStageTransactions) + val thirdStageTransactions = Closing.RevokedClose.claimHtlcTxsOutputs(closing.commitments.params, channelKeys, remotePerCommitmentSecret, c.revokedCommitPublished, nodeParams.currentBitcoinCoreFeerates, closing.finalScriptPubKey) + doPublish(c.revokedCommitPublished, thirdStageTransactions) + } case None => + // The closing type isn't known yet: + // - we publish transactions for all types of closes that we detected + // - there may be other commitments, but we'll adapt if we receive WatchAlternativeCommitTxConfirmedTriggered + // - there cannot be 3rd-stage transactions yet, no need to re-compute them closing.mutualClosePublished.foreach(mcp => doPublish(mcp, localPaysClosingFees)) - closing.localCommitPublished.foreach(lcp => doPublish(lcp, Closing.LocalClose.SecondStageTransactions(lcp.claimMainDelayedOutputTx, lcp.claimAnchorTx_opt, lcp.htlcTxs.values.flatten.toSeq), closing.commitments.latest)) - closing.remoteCommitPublished.foreach(rcp => doPublish(rcp, Closing.RemoteClose.SecondStageTransactions(rcp.claimMainOutputTx, rcp.claimAnchorTx_opt, rcp.claimHtlcTxs.values.flatten.toSeq), closing.commitments.latest)) - closing.nextRemoteCommitPublished.foreach(rcp => doPublish(rcp, Closing.RemoteClose.SecondStageTransactions(rcp.claimMainOutputTx, rcp.claimAnchorTx_opt, rcp.claimHtlcTxs.values.flatten.toSeq), closing.commitments.latest)) - closing.revokedCommitPublished.foreach(rvk => doPublish(rvk, Closing.RevokedClose.SecondStageTransactions(rvk.claimMainOutputTx, rvk.mainPenaltyTx, rvk.htlcPenaltyTxs))) - closing.futureRemoteCommitPublished.foreach(rcp => doPublish(rcp, Closing.RemoteClose.SecondStageTransactions(rcp.claimMainOutputTx, rcp.claimAnchorTx_opt, rcp.claimHtlcTxs.values.flatten.toSeq), closing.commitments.latest)) + closing.localCommitPublished.foreach(lcp => { + val (_, secondStageTransactions) = Closing.LocalClose.claimCommitTxOutputs(channelKeys, commitment, lcp.commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(lcp, secondStageTransactions, commitment) + }) + closing.remoteCommitPublished.foreach(rcp => { + val (_, secondStageTransactions) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, commitment.remoteCommit, rcp.commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(rcp, secondStageTransactions, commitment, closing.finalScriptPubKey) + }) + closing.nextRemoteCommitPublished.foreach(rcp => { + val remoteCommit = commitment.nextRemoteCommit_opt.get.commit + val (_, secondStageTransactions) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, remoteCommit, rcp.commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(rcp, secondStageTransactions, commitment, closing.finalScriptPubKey) + }) + closing.revokedCommitPublished.foreach(rvk => { + Closing.RevokedClose.getRemotePerCommitmentSecret(closing.commitments.params, channelKeys, closing.commitments.remotePerCommitmentSecrets, rvk.commitTx).foreach { + case (commitmentNumber, remotePerCommitmentSecret) => + val (_, secondStageTransactions) = Closing.RevokedClose.claimCommitTxOutputs(closing.commitments.params, channelKeys, rvk.commitTx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closing.finalScriptPubKey) + doPublish(rvk, secondStageTransactions) + } + }) + closing.futureRemoteCommitPublished.foreach(rcp => doPublish(rcp, Closing.RemoteClose.SecondStageTransactions(None, None, Nil), commitment, closing.finalScriptPubKey)) } // no need to go OFFLINE, we can directly switch to CLOSING goto(CLOSING) using closing @@ -1048,7 +1072,7 @@ 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(Multisig2of2Input(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = Nil, lockTime = msg.lockTime, @@ -1092,7 +1116,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall isInitiator = true, localContribution = spliceInit.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + sharedInput_opt = Some(Multisig2of2Input(channelKeys, parentCommitment)), remoteFundingPubKey = msg.fundingPubKey, localOutputs = cmd.spliceOutputs, lockTime = spliceInit.lockTime, @@ -1152,7 +1176,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) @@ -1168,7 +1192,7 @@ 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(Multisig2of2Input(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, lockTime = msg.lockTime, @@ -1209,7 +1233,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) @@ -1222,7 +1246,7 @@ 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(Multisig2of2Input(channelKeys, rbf.parentCommitment)), remoteFundingPubKey = rbf.latestFundingTx.fundingParams.remoteFundingPubKey, localOutputs = rbf.latestFundingTx.fundingParams.localOutputs, lockTime = txInitRbf.lockTime, @@ -1345,7 +1369,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall d.spliceStatus match { case SpliceStatus.SpliceWaitingForSigs(signingSession) => // we have not yet sent our tx_signatures - signingSession.receiveTxSigs(channelKeys, msg, nodeParams.currentBlockHeight) match { + signingSession.receiveTxSigs(d.commitments.params, channelKeys, msg, nodeParams.currentBlockHeight) match { case Left(f) => rollbackFundingAttempt(signingSession.fundingTx.tx, previousTxs = Seq.empty) // no splice rbf yet stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, f.getMessage) @@ -1717,7 +1741,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.params.localParams.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}") @@ -1744,7 +1768,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)) } @@ -1870,27 +1894,16 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall log.info("htlc #{} with payment_hash={} was fulfilled downstream, recalculating htlc-success transactions", c.id, c.r) // We may be able to publish HTLC-success transactions for which we didn't have the preimage. // We are already watching the corresponding outputs: no need to set additional watches. - val lcp1 = d.localCommitPublished.map(lcp => { - val (lcp1, toPublish) = Closing.LocalClose.claimHtlcsWithPreimage(commitment.localKeys(channelKeys), lcp, commitment, c.r) - toPublish.foreach(publishTx => txPublisher ! publishTx) - lcp1 - }) - val rcp1 = d.remoteCommitPublished.map(rcp => { - val (rcp1, toPublish) = Closing.RemoteClose.claimHtlcsWithPreimage(channelKeys, rcp, commitment, commitment.remoteCommit, c.r, d.finalScriptPubKey) - toPublish.foreach(publishTx => txPublisher ! publishTx) - rcp1 - }) - val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => { - val (nrcp1, toPublish) = Closing.RemoteClose.claimHtlcsWithPreimage(channelKeys, nrcp, commitment, commitment.nextRemoteCommit_opt.get.commit, c.r, d.finalScriptPubKey) - toPublish.foreach(publishTx => txPublisher ! publishTx) - nrcp1 - }) - d.copy(commitments = commitments1, localCommitPublished = lcp1, remoteCommitPublished = rcp1, nextRemoteCommitPublished = nrcp1) + val localTxs = d.localCommitPublished.map(lcp => Closing.LocalClose.claimHtlcsWithPreimage(channelKeys, lcp, commitment, c.r)).getOrElse(Nil) + val remoteTxs = d.remoteCommitPublished.map(rcp => Closing.RemoteClose.claimHtlcsWithPreimage(channelKeys, rcp, commitment, commitment.remoteCommit, c.r, d.finalScriptPubKey)).getOrElse(Nil) + val nextRemoteTxs = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.claimHtlcsWithPreimage(channelKeys, nrcp, commitment, commitment.nextRemoteCommit_opt.get.commit, c.r, d.finalScriptPubKey)).getOrElse(Nil) + (localTxs ++ remoteTxs ++ nextRemoteTxs).foreach(publishTx => txPublisher ! publishTx) + d.copy(commitments = commitments1) case _: CMD_FAIL_HTLC | _: CMD_FAIL_MALFORMED_HTLC => log.info("htlc #{} was failed downstream, recalculating watched htlc outputs", c.id) val lcp1 = d.localCommitPublished.map(lcp => Closing.LocalClose.ignoreFailedIncomingHtlc(c.id, lcp, commitment)) - val rcp1 = d.remoteCommitPublished.map(rcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(channelKeys, c.id, rcp, commitment, commitment.remoteCommit)) - val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(channelKeys, c.id, nrcp, commitment, commitment.nextRemoteCommit_opt.get.commit)) + val rcp1 = d.remoteCommitPublished.map(rcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(c.id, rcp, commitment, commitment.remoteCommit)) + val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(c.id, nrcp, commitment, commitment.nextRemoteCommit_opt.get.commit)) d.copy(commitments = commitments1, localCommitPublished = lcp1, remoteCommitPublished = rcp1, nextRemoteCommitPublished = nrcp1) } handleCommandSuccess(c, d1) storing() @@ -1960,10 +1973,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall } else if (d.futureRemoteCommitPublished.exists(_.commitTx.txid == tx.txid)) { // this is because WatchSpent watches never expire and we are notified multiple times stay() - } else if (tx.txid == d.commitments.latest.remoteCommit.txid) { + } else if (tx.txid == d.commitments.latest.remoteCommit.txId) { // counterparty may attempt to spend its last commit tx at any time handleRemoteSpentCurrent(tx, d) - } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txid == tx.txid)) { + } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid)) { // counterparty may attempt to spend its last commit tx at any time handleRemoteSpentNext(tx, d) } else if (tx.txIn.map(_.outPoint.txid).contains(d.commitments.latest.fundingTxId)) { @@ -2010,12 +2023,12 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We reset the state to match the commitment that confirmed. val d1 = d.copy(commitments = commitments1) // This commitment may be revoked: we need to verify that its index matches our latest known index before overwriting our previous commitments. - if (commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid) { + if (commitment.localCommit.txId == tx.txid) { // Our local commit has been published from the outside, it's unexpected but let's deal with it anyway. spendLocalCurrent(d1) - } else if (commitment.remoteCommit.txid == tx.txid && commitment.remoteCommit.index == d.commitments.remoteCommitIndex) { + } else if (commitment.remoteCommit.txId == tx.txid && commitment.remoteCommit.index == d.commitments.remoteCommitIndex) { handleRemoteSpentCurrent(tx, d1) - } else if (commitment.nextRemoteCommit_opt.exists(_.commit.txid == tx.txid) && commitment.remoteCommit.index == d.commitments.remoteCommitIndex && d.commitments.remoteNextCommitInfo.isLeft) { + } else if (commitment.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid) && commitment.remoteCommit.index == d.commitments.remoteCommitIndex && d.commitments.remoteNextCommitInfo.isLeft) { handleRemoteSpentNext(tx, d1) } else { // Our counterparty is trying to broadcast a revoked commit tx (cheating attempt). @@ -2119,7 +2132,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall } // we may need to fail some htlcs in case a commitment tx was published and they have reached the timeout threshold val timedOutHtlcs = Closing.isClosingTypeAlreadyKnown(d1) match { - case Some(c: Closing.LocalClose) => Closing.trimmedOrTimedOutHtlcs(d.commitments.params.commitmentFormat, c.localCommit, d.commitments.params.localParams.dustLimit, tx) + case Some(c: Closing.LocalClose) => Closing.trimmedOrTimedOutHtlcs(channelKeys, d.commitments.latest, c.localCommit, tx) case Some(c: Closing.RemoteClose) => Closing.trimmedOrTimedOutHtlcs(channelKeys, d.commitments.latest, c.remoteCommit, tx) case Some(_: Closing.RevokedClose) => Set.empty[UpdateAddHtlc] // revoked commitments are handled using [[overriddenOutgoingHtlcs]] below case Some(_: Closing.RecoveryClose) => Set.empty[UpdateAddHtlc] // we lose htlc outputs in dataloss protection scenarios (future remote commit) @@ -2402,7 +2415,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.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys)) 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 @@ -2430,7 +2443,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.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys)) 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) @@ -2502,7 +2515,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.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(d.commitments.params, channelKeys, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput(channelKeys)) sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs } else { log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2881,11 +2894,11 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall if (d.commitments.all.map(_.fundingTxId).contains(tx.txid)) { // if the spending tx is itself a funding tx, this is a splice and there is nothing to do stay() - } else if (tx.txid == d.commitments.latest.remoteCommit.txid) { + } else if (tx.txid == d.commitments.latest.remoteCommit.txId) { handleRemoteSpentCurrent(tx, d) - } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txid == tx.txid)) { + } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid)) { handleRemoteSpentNext(tx, d) - } else if (tx.txid == d.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid) { + } else if (tx.txid == d.commitments.latest.localCommit.txId) { log.warning(s"processing local commit spent from the outside") spendLocalCurrent(d) } else if (tx.txIn.map(_.outPoint.txid).contains(d.commitments.latest.fundingTxId)) { @@ -3275,7 +3288,7 @@ 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 = Multisig2of2Input(channelKeys, parentCommitment), spliceInAmount = cmd.additionalLocalFunding, spliceOut = cmd.spliceOutputs, targetFeerate = targetFeerate) 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 96d87085b1..36a4814d00 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 @@ -400,7 +400,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case Event(msg: InteractiveTxMessage, d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) => msg match { case txSigs: TxSignatures => - d.signingSession.receiveTxSigs(channelKeys, txSigs, nodeParams.currentBlockHeight) match { + d.signingSession.receiveTxSigs(d.channelParams, channelKeys, txSigs, nodeParams.currentBlockHeight) match { case Left(f) => rollbackFundingAttempt(d.signingSession.fundingTx.tx, Nil) goto(CLOSED) sending Error(d.channelId, f.getMessage) @@ -468,7 +468,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case _: FullySignedSharedTransaction => d.status match { case DualFundingStatus.RbfWaitingForSigs(signingSession) => - signingSession.receiveTxSigs(channelKeys, txSigs, nodeParams.currentBlockHeight) match { + signingSession.receiveTxSigs(d.channelParams, channelKeys, txSigs, nodeParams.currentBlockHeight) match { case Left(f) => rollbackRbfAttempt(signingSession, d) stay() using d.copy(status = DualFundingStatus.RbfAborted) sending TxAbort(d.channelId, f.getMessage) @@ -548,7 +548,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) @@ -606,7 +606,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) 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 ef3895dca0..c2c6d138b6 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 @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel.fsm import akka.actor.Status import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.pattern.pipe -import fr.acinq.bitcoin.scalacompat.{SatoshiLong, Script} +import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Script} import fr.acinq.eclair.blockchain.OnChainWallet.MakeFundingTxResponse import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel.Helpers.Funding @@ -285,9 +285,12 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { fundingTxIndex = 0, firstRemoteCommitIndex = 0, remoteFundingPubKey = remoteFundingPubKey, + fundingTxOutpoint = localCommitTx.input.outPoint, + fundingAmount = localCommitTx.input.txOut.amount, localFundingStatus = SingleFundedUnconfirmedFundingTx(None), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, ChannelSpendSignature.IndividualSignature(remoteSig)), htlcTxsAndRemoteSigs = Nil), + format = params.commitmentFormat, + localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), nextRemoteCommit_opt = None) val commitments = Commitments( @@ -330,9 +333,12 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { fundingTxIndex = 0, firstRemoteCommitIndex = 0, remoteFundingPubKey = remoteFundingPubKey, + fundingTxOutpoint = localCommitTx.input.outPoint, + fundingAmount = localCommitTx.input.txOut.amount, localFundingStatus = SingleFundedUnconfirmedFundingTx(Some(fundingTx)), remoteFundingStatus = RemoteFundingStatus.NotLocked, - localCommit = LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, ChannelSpendSignature.IndividualSignature(remoteSig)), htlcTxsAndRemoteSigs = Nil), + format = params.commitmentFormat, + localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, ChannelSpendSignature.IndividualSignature(remoteSig), htlcRemoteSigs = Nil), remoteCommit = 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 539361019e..ed5ee7a4fb 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,8 +44,8 @@ trait CommonFundingHandlers extends CommonHandlers { * @param delay_opt optional delay to reduce herd effect at startup. */ def watchFundingSpent(commitment: Commitment, additionalKnownSpendingTxs: Set[TxId], delay_opt: Option[FiniteDuration]): Unit = { - val knownSpendingTxs = Set(commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid, commitment.remoteCommit.txid) ++ commitment.nextRemoteCommit_opt.map(_.commit.txid).toSet ++ additionalKnownSpendingTxs - val watch = WatchFundingSpent(self, commitment.commitInput.outPoint.txid, commitment.commitInput.outPoint.index.toInt, knownSpendingTxs) + val knownSpendingTxs = commitment.commitTxIds.txIds ++ additionalKnownSpendingTxs + val watch = WatchFundingSpent(self, commitment.fundingTxOutpoint.txid, commitment.fundingTxOutpoint.index.toInt, knownSpendingTxs) delay_opt match { case Some(delay) => context.system.scheduler.scheduleOnce(delay, blockchain.toClassic, watch) case None => blockchain ! watch @@ -85,7 +85,7 @@ 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 scid = RealShortChannelId(w.blockHeight, w.txIndex, c.fundingTxOutpoint.index.toInt) val fundingStatus = ConfirmedFundingTx(w.tx, 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 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 cddc81545d..4202928a6c 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 @@ -18,20 +18,22 @@ package fr.acinq.eclair.channel.fsm import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.{ActorRef, FSM} +import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, OutPoint, SatoshiLong, Transaction} import fr.acinq.eclair.NotificationsLogger import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{WatchOutputSpent, WatchTxConfirmed} -import fr.acinq.eclair.blockchain.fee.ConfirmationTarget +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel.UnhandledExceptionStrategy import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx} import fr.acinq.eclair.channel.publish._ -import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} -import fr.acinq.eclair.transactions.Transactions +import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReestablish, Error, OpenChannel, UpdateFulfillHtlc, Warning} +import fr.acinq.eclair.transactions.{IncomingHtlc, OutgoingHtlc, Transactions} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReestablish, Error, OpenChannel, UpdateAddHtlc, UpdateFulfillHtlc, Warning} +import scodec.bits.ByteVector import java.sql.SQLException @@ -230,7 +232,7 @@ trait ErrorHandlers extends CommonHandlers { def doPublish(lcp: LocalCommitPublished, txs: Closing.LocalClose.SecondStageTransactions, commitment: FullCommitment): Unit = { val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val commitKeys = commitment.localKeys(channelKeys) - val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, lcp.commitTx, commitment.localParams.paysCommitTxFees), None) + val publishCommitTx = PublishFinalTx(lcp.commitTx, commitment.fundingTxOutpoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput(channelKeys), lcp.commitTx, commitment.localParams.paysCommitTxFees), None) val publishAnchorTx_opt = txs.anchorTx_opt match { case Some(anchorTx) if !lcp.isConfirmed => val confirmationTarget = Closing.confirmationTarget(commitment.localCommit, commitment.localParams.dustLimit, commitment.params.commitmentFormat, nodeParams.onChainFeeConf) @@ -238,7 +240,7 @@ trait ErrorHandlers extends CommonHandlers { case _ => None } val publishMainDelayedTx_opt = txs.mainDelayedTx_opt.map(tx => PublishFinalTx(tx, tx.fee, None)) - val publishHtlcTxs = redeemableHtlcTxs(lcp.commitTx, commitKeys, commitment) + val publishHtlcTxs = redeemableHtlcTxs(lcp.commitTx, channelKeys, commitKeys, commitment) val publishQueue = Seq(publishCommitTx) ++ publishAnchorTx_opt ++ publishMainDelayedTx_opt ++ publishHtlcTxs publishIfNeeded(publishQueue, lcp.irrevocablySpent) @@ -251,7 +253,7 @@ trait ErrorHandlers extends CommonHandlers { // we will watch for its confirmation. This ensures that we detect double-spends that could come from: // - our own RBF attempts // - remote transactions for outputs that both parties may spend (e.g. HTLCs) - val watchSpentQueue = lcp.claimMainDelayedOutputTx.map(_.input.outPoint) ++ lcp.claimAnchorTx_opt.map(_.input.outPoint) ++ lcp.htlcTxs.keys.toSeq + val watchSpentQueue = lcp.localOutput_opt ++ lcp.anchorOutput_opt ++ lcp.htlcOutputs.toSeq watchSpentIfNeeded(lcp.commitTx, watchSpentQueue, lcp.irrevocablySpent) } @@ -263,12 +265,12 @@ trait ErrorHandlers extends CommonHandlers { txs.htlcDelayedTxs.foreach(tx => watchSpentIfNeeded(tx.input, lcp.irrevocablySpent)) } - private def redeemableHtlcTxs(commitTx: Transaction, commitKeys: LocalCommitmentKeys, commitment: FullCommitment): Iterable[PublishTx] = { + private def redeemableHtlcTxs(commitTx: Transaction, channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys, commitment: FullCommitment): Iterable[PublishTx] = { val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case fulfill: UpdateFulfillHtlc => Crypto.sha256(fulfill.paymentPreimage) -> fulfill.paymentPreimage }.toMap - commitment.localCommit.htlcTxsAndRemoteSigs.flatMap { - case HtlcTxAndRemoteSig(htlcTx, remoteSig) => + commitment.htlcTxs(channelKeys, commitKeys).flatMap { + case (htlcTx, remoteSig) => val preimage_opt = preimages.get(htlcTx.paymentHash) commitment.params.commitmentFormat match { case Transactions.DefaultCommitmentFormat => @@ -294,9 +296,9 @@ trait ErrorHandlers extends CommonHandlers { def handleRemoteSpentCurrent(commitTx: Transaction, d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest log.warning(s"they published their current commit in txid=${commitTx.txid}") - require(commitTx.txid == commitments.remoteCommit.txid, "txid mismatch") + 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.params.localParams.paysCommitTxFees), "remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput(channelKeys), commitTx, d.commitments.params.localParams.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)) @@ -304,7 +306,7 @@ trait ErrorHandlers extends CommonHandlers { case negotiating: DATA_NEGOTIATING_SIMPLE => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = negotiating.proposedClosingTxs.flatMap(_.all), mutualClosePublished = negotiating.publishedClosingTxs, remoteCommitPublished = Some(remoteCommitPublished)) case _ => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = Nil, remoteCommitPublished = Some(remoteCommitPublished)) } - goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, commitments) + goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, commitments, finalScriptPubKey) } def handleRemoteSpentNext(commitTx: Transaction, d: ChannelDataWithCommitments) = { @@ -312,10 +314,10 @@ trait ErrorHandlers extends CommonHandlers { log.warning(s"they published their next commit in txid=${commitTx.txid}") require(commitment.nextRemoteCommit_opt.nonEmpty, "next remote commit must be defined") val remoteCommit = commitment.nextRemoteCommit_opt.get.commit - require(commitTx.txid == remoteCommit.txid, "txid mismatch") + 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.params.localParams.paysCommitTxFees), "next-remote-commit")) + context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput(channelKeys), commitTx, d.commitments.params.localParams.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)) @@ -324,14 +326,14 @@ trait ErrorHandlers extends CommonHandlers { // NB: if there is a next commitment, we can't be in DATA_WAIT_FOR_FUNDING_CONFIRMED so we don't have the case where fundingTx is defined case _ => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = Nil, nextRemoteCommitPublished = Some(remoteCommitPublished)) } - goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, commitment) + goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, commitment, finalScriptPubKey) } /** Publish 2nd-stage transactions for the remote commitment (no need for 3rd-stage transactions in that case). */ - def doPublish(rcp: RemoteCommitPublished, txs: Closing.RemoteClose.SecondStageTransactions, commitment: FullCommitment): Unit = { + def doPublish(rcp: RemoteCommitPublished, txs: Closing.RemoteClose.SecondStageTransactions, commitment: FullCommitment, finalScriptPubKey: ByteVector): Unit = { val fundingKey = channelKeys.fundingKey(commitment.fundingTxIndex) val remoteCommit = commitment.nextRemoteCommit_opt match { - case Some(c) if rcp.commitTx.txid == c.commit.txid => c.commit + case Some(c) if rcp.commitTx.txid == c.commit.txId => c.commit case _ => commitment.remoteCommit } val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint) @@ -342,7 +344,7 @@ trait ErrorHandlers extends CommonHandlers { case _ => None } val publishMainTx_opt = txs.mainTx_opt.map(tx => PublishFinalTx(tx, tx.fee, None)) - val publishHtlcTxs = redeemableClaimHtlcTxs(rcp, commitKeys, commitment) + val publishHtlcTxs = if (txs.htlcTxs.nonEmpty) redeemableClaimHtlcTxs(rcp.commitTx, remoteCommit, commitKeys, commitment, finalScriptPubKey) else Nil val publishQueue = publishAnchorTx_opt ++ publishMainTx_opt ++ publishHtlcTxs publishIfNeeded(publishQueue, rcp.irrevocablySpent) @@ -355,35 +357,42 @@ trait ErrorHandlers extends CommonHandlers { // we will watch for its confirmation. This ensures that we detect double-spends that could come from: // - our own RBF attempts // - remote transactions for outputs that both parties may spend (e.g. HTLCs) - val watchSpentQueue = rcp.claimMainOutputTx.map(_.input.outPoint) ++ rcp.claimAnchorTx_opt.map(_.input.outPoint) ++ rcp.claimHtlcTxs.keys.toSeq + val watchSpentQueue = rcp.localOutput_opt ++ rcp.anchorOutput_opt ++ rcp.htlcOutputs.toSeq watchSpentIfNeeded(rcp.commitTx, watchSpentQueue, rcp.irrevocablySpent) } - private def redeemableClaimHtlcTxs(remoteCommitPublished: RemoteCommitPublished, commitKeys: RemoteCommitmentKeys, commitment: FullCommitment): Iterable[PublishReplaceableTx] = { + private def redeemableClaimHtlcTxs(commitTx: Transaction, remoteCommit: RemoteCommit, commitKeys: RemoteCommitmentKeys, commitment: FullCommitment, finalScriptPubKey: ByteVector): Iterable[PublishReplaceableTx] = { + // The feerate will be set by the publisher actor based on the HTLC expiry, we don't care which feerate is used here. + val feerate = FeeratePerKw(FeeratePerByte(1 sat)) + val dustLimit = commitment.localParams.dustLimit + val outputs = Closing.RemoteClose.makeRemoteCommitTxOutputs(channelKeys, commitKeys, commitment, remoteCommit) val preimages = (commitment.changes.localChanges.all ++ commitment.changes.remoteChanges.all).collect { case fulfill: UpdateFulfillHtlc => Crypto.sha256(fulfill.paymentPreimage) -> fulfill.paymentPreimage }.toMap - remoteCommitPublished.claimHtlcTxs.values.flatten.flatMap { claimHtlcTx => - val confirmationTarget = ConfirmationTarget.Absolute(claimHtlcTx.htlcExpiry.blockHeight) - val preimage_opt = preimages.get(claimHtlcTx.paymentHash) - val replaceableTx_opt = (claimHtlcTx, preimage_opt) match { - case (claimHtlcTx: ClaimHtlcSuccessTx, Some(preimage)) => Some(ReplaceableClaimHtlcSuccess(claimHtlcTx, commitKeys, preimage, remoteCommitPublished.commitTx, commitment)) - case (claimHtlcTx: ClaimHtlcTimeoutTx, _) => Some(ReplaceableClaimHtlcTimeout(claimHtlcTx, commitKeys, remoteCommitPublished.commitTx, commitment)) - case _ => None - } - replaceableTx_opt.map(tx => PublishReplaceableTx(tx, confirmationTarget)) - } + remoteCommit.spec.htlcs.collect { + case OutgoingHtlc(add: UpdateAddHtlc) => + val confirmationTarget = ConfirmationTarget.Absolute(add.cltvExpiry.blockHeight) + for { + preimage <- preimages.get(add.paymentHash) + signedTx <- ClaimHtlcSuccessTx.createSignedTx(commitKeys, commitTx, dustLimit, outputs, finalScriptPubKey, add, preimage, feerate, commitment.params.commitmentFormat).toOption + } yield PublishReplaceableTx(ReplaceableClaimHtlcSuccess(signedTx, commitKeys, preimage, commitTx, commitment), confirmationTarget) + case IncomingHtlc(add: UpdateAddHtlc) => + val confirmationTarget = ConfirmationTarget.Absolute(add.cltvExpiry.blockHeight) + for { + signedTx <- ClaimHtlcTimeoutTx.createSignedTx(commitKeys, commitTx, dustLimit, outputs, finalScriptPubKey, add, feerate, commitment.params.commitmentFormat).toOption + } yield PublishReplaceableTx(ReplaceableClaimHtlcTimeout(signedTx, commitKeys, commitTx, commitment), confirmationTarget) + }.flatten } def handleRemoteSpentOther(tx: Transaction, d: ChannelDataWithCommitments) = { val commitment = d.commitments.latest - log.warning(s"funding tx spent in txid=${tx.txid}") + log.warning("funding tx spent by txid={}", tx.txid) val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) Closing.RevokedClose.getRemotePerCommitmentSecret(d.commitments.params, channelKeys, d.commitments.remotePerCommitmentSecrets, tx) match { case Some((commitmentNumber, remotePerCommitmentSecret)) => val (revokedCommitPublished, closingTxs) = Closing.RevokedClose.claimCommitTxOutputs(d.commitments.params, channelKeys, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) - log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") - context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput, tx, d.commitments.params.localParams.paysCommitTxFees), "revoked-commit")) + 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(channelKeys), tx, d.commitments.params.localParams.paysCommitTxFees), "revoked-commit")) val exc = FundingTxSpent(d.channelId, tx.txid) val error = Error(d.channelId, exc.getMessage) val nextData = d match { @@ -396,20 +405,21 @@ trait ErrorHandlers extends CommonHandlers { goto(CLOSING) using nextData storing() calling doPublish(revokedCommitPublished, closingTxs) sending error case None => d match { case d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT => - log.warning(s"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.localParams.paysCommitTxFees), "future-remote-commit")) + 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(channelKeys), tx, d.commitments.latest.localParams.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.params, commitKeys, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + mainTx_opt.foreach(tx => log.warning("publishing our recovery transaction: tx={}", tx.toString)) val remoteCommitPublished = RemoteCommitPublished( commitTx = tx, - claimMainOutputTx = mainTx_opt, - claimHtlcTxs = Map.empty, - claimAnchorTxs = List.empty, + localOutput_opt = mainTx_opt.map(_.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map.empty) val closingTxs = Closing.RemoteClose.SecondStageTransactions(mainTx_opt, anchorTx_opt = None, htlcTxs = Nil) val nextData = DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) - goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, d.commitments.latest) + goto(CLOSING) using nextData storing() calling doPublish(remoteCommitPublished, closingTxs, d.commitments.latest, finalScriptPubKey) case _ => // the published tx doesn't seem to be a valid commitment transaction log.error(s"couldn't identify txid=${tx.txid}, something very bad is going on!!!") @@ -430,7 +440,7 @@ trait ErrorHandlers extends CommonHandlers { } // We watch outputs of the commitment tx that both parties may spend, or that we may RBF. - val watchSpentQueue = (rvk.claimMainOutputTx ++ rvk.mainPenaltyTx ++ rvk.htlcPenaltyTxs).map(_.input.outPoint) + val watchSpentQueue = rvk.localOutput_opt ++ rvk.remoteOutput_opt ++ rvk.htlcOutputs.toSeq watchSpentIfNeeded(rvk.commitTx, watchSpentQueue, rvk.irrevocablySpent) } 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 8b18e06b6a..d3e0897f17 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.{CommitTx, HtlcTx, InputInfo, SegwitV0CommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{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} @@ -118,8 +118,8 @@ object InteractiveTxBuilder { } object Multisig2of2Input { - def apply(commitment: Commitment): Multisig2of2Input = Multisig2of2Input( - info = commitment.commitInput, + def apply(channelKeys: ChannelKeys, commitment: Commitment): Multisig2of2Input = Multisig2of2Input( + info = commitment.commitInput(channelKeys), fundingTxIndex = commitment.fundingTxIndex, remoteFundingPubkey = commitment.remoteFundingPubKey ) @@ -856,16 +856,16 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon val localSigOfRemoteTx = remoteCommitTx.sign(localFundingKey, fundingParams.remoteFundingPubKey).sig val htlcSignatures = sortedHtlcTxs.map(_.sign(remoteCommitmentKeys, channelParams.commitmentFormat)).toList val localCommitSig = CommitSig(fundingParams.channelId, localSigOfRemoteTx, htlcSignatures) - val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx, htlcTxs = Nil) + 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) + signFundingTx(completeTx, localCommitTx.input, localCommitSig, localCommit, remoteCommit) case _: SimpleTaprootChannelCommitmentFormat => ??? } } } - private def signFundingTx(completeTx: SharedTransaction, commitSig: CommitSig, localCommit: UnsignedLocalCommit, remoteCommit: RemoteCommit): Behavior[Command] = { + private def signFundingTx(completeTx: SharedTransaction, commitInput: InputInfo, commitSig: CommitSig, localCommit: UnsignedLocalCommit, remoteCommit: RemoteCommit): Behavior[Command] = { signTx(completeTx) Behaviors.receiveMessagePartial { case SignTransactionResult(signedTx) => @@ -897,6 +897,7 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon fundingParams, purpose.fundingTxIndex, signedTx, + commitInput, Left(localCommit), remoteCommit, liquidityPurchase_opt.map(_.basicInfo(isBuyer = fundingParams.isInitiator)) @@ -1020,7 +1021,7 @@ object InteractiveTxSigningSession { // +-------+ +-------+ /** A local commitment for which we haven't received our peer's signatures. */ - case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, commitTx: CommitTx, htlcTxs: List[HtlcTx]) + 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) @@ -1092,10 +1093,11 @@ object InteractiveTxSigningSession { case class WaitingForSigs(fundingParams: InteractiveTxParams, fundingTxIndex: Long, fundingTx: PartiallySignedSharedTransaction, + commitInput: InputInfo, localCommit: Either[UnsignedLocalCommit, LocalCommit], remoteCommit: RemoteCommit, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession { - val commitInput: InputInfo = localCommit.fold(_.commitTx.input, _.commitTxAndRemoteSig.commitTx.input) + val fundingTxOutpoint: OutPoint = OutPoint(fundingTx.txId, fundingTx.tx.buildUnsignedTx().txOut.indexWhere(_.publicKeyScript == fundingTx.tx.sharedOutput.pubkeyScript)) 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 { @@ -1111,7 +1113,7 @@ object InteractiveTxSigningSession { LocalCommit.fromCommitSig(channelParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, commitInput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec).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, fundingParams.remoteFundingPubKey, fundingTxOutpoint, fundingTx.tx.sharedOutput.amount, fundingStatus, RemoteFundingStatus.NotLocked, channelParams.commitmentFormat, signedLocalCommit, remoteCommit, None) SendingSigs(fundingStatus, commitment, fundingTx.localSigs) } else { this.copy(localCommit = Right(signedLocalCommit)) @@ -1123,7 +1125,7 @@ object InteractiveTxSigningSession { } } - def receiveTxSigs(channelKeys: ChannelKeys, remoteTxSigs: TxSignatures, currentBlockHeight: BlockHeight)(implicit log: LoggingAdapter): Either[ChannelException, SendingSigs] = { + def receiveTxSigs(channelParams: ChannelParams, channelKeys: ChannelKeys, remoteTxSigs: TxSignatures, currentBlockHeight: BlockHeight)(implicit log: LoggingAdapter): Either[ChannelException, SendingSigs] = { localCommit match { case Left(_) => log.info("received tx_signatures before commit_sig") @@ -1136,7 +1138,7 @@ object InteractiveTxSigningSession { 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 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, fundingParams.remoteFundingPubKey, fundingTxOutpoint, fundingTx.tx.sharedOutput.amount, fundingStatus, RemoteFundingStatus.NotLocked, channelParams.commitmentFormat, signedLocalCommit, remoteCommit, None) Right(SendingSigs(fundingStatus, commitment, fullySignedTx.localSigs)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTx.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTx.scala index ff7ea3da80..69f10bfaba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTx.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTx.scala @@ -39,7 +39,7 @@ sealed trait ReplaceableTx { def commitment: FullCommitment def commitmentFormat: CommitmentFormat = commitment.params.commitmentFormat def dustLimit: Satoshi = commitment.localParams.dustLimit - def commitFee: Satoshi = commitment.commitInput.txOut.amount - commitTx.txOut.map(_.amount).sum + def commitFee: Satoshi = commitment.capacity - commitTx.txOut.map(_.amount).sum def concurrentCommitTxs: Set[TxId] = commitment.commitTxIds.txIds - commitTx.txid // @formatter:on } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala index 79fca97e4e..ce78a5aced 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala @@ -94,7 +94,7 @@ object ReplaceableTxFunder { case _: ReplaceableHtlc => tx.txInfo.amountIn case _: ReplaceableClaimHtlc => tx.txInfo.amountIn case _: ReplaceableAnchor => - val htlcBalance = tx.commitment.localCommit.htlcTxsAndRemoteSigs.map(_.htlcTx.amountIn).sum + val htlcBalance = tx.commitment.localCommit.spec.htlcs.map(_.add.amountMsat).sum.truncateToSatoshi val mainBalance = tx.commitment.localCommit.spec.toLocal.truncateToSatoshi // If there are no HTLCs or a low HTLC amount, we still want to get back our main balance. // In that case, we spend at most 5% of our balance in fees, with a hard cap configured by the node operator. @@ -141,7 +141,7 @@ object ReplaceableTxFunder { // @formatter:off sealed trait AdjustPreviousTxOutputResult - object AdjustPreviousTxOutputResult { + private object AdjustPreviousTxOutputResult { case class Skip(reason: String) extends AdjustPreviousTxOutputResult case class AddWalletInputs(previousTx: ReplaceableTxWithWalletInputs) extends AdjustPreviousTxOutputResult case class TxOutputAdjusted(updatedTx: ReplaceableTx) extends AdjustPreviousTxOutputResult @@ -152,7 +152,7 @@ object ReplaceableTxFunder { * Adjust the outputs of a transaction that was previously published at a lower feerate. * If the current set of inputs doesn't let us reach the target feerate, we will request new wallet inputs from bitcoind. */ - def adjustPreviousTxOutput(previousTx: FundedTx, targetFeerate: FeeratePerKw): AdjustPreviousTxOutputResult = { + private def adjustPreviousTxOutput(previousTx: FundedTx, targetFeerate: FeeratePerKw): AdjustPreviousTxOutputResult = { val targetFee = previousTx.tx match { case anchorTx: ReplaceableAnchor => val totalWeight = previousTx.signedTx.weight() + anchorTx.commitTx.weight() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala index 7f588cf692..cd34acc589 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala @@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{BlockId, ByteVector32, ByteVector64, Crypto} import fr.acinq.eclair.TimestampMilli import fr.acinq.eclair.blockchain.NewBlock -import fr.acinq.eclair.channel.ChannelSignatureReceived +import fr.acinq.eclair.channel.{ChannelSignatureReceived, ChannelSpendSignature} import fr.acinq.eclair.io.PeerConnected import fr.acinq.eclair.payment.ChannelPaymentRelayed import fr.acinq.eclair.router.NodeUpdated @@ -50,7 +50,7 @@ object WeakEntropyPool { private case class WrappedNewBlock(blockId: BlockId) extends Command private case class WrappedPaymentRelayed(paymentHash: ByteVector32, relayedAt: TimestampMilli) extends Command private case class WrappedPeerConnected(nodeId: PublicKey) extends Command - private case class WrappedChannelSignature(wtxid: ByteVector32) extends Command + private case class WrappedChannelSignature(sig: ChannelSpendSignature) extends Command private case class WrappedNodeUpdated(sig: ByteVector64) extends Command // @formatter:on @@ -60,7 +60,7 @@ object WeakEntropyPool { context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[ChannelPaymentRelayed](e => WrappedPaymentRelayed(e.paymentHash, e.timestamp))) context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[PeerConnected](e => WrappedPeerConnected(e.nodeId))) context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[NodeUpdated](e => WrappedNodeUpdated(e.ann.signature))) - context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[ChannelSignatureReceived](e => WrappedChannelSignature(e.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.wtxid))) + context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[ChannelSignatureReceived](e => WrappedChannelSignature(e.commitments.latest.localCommit.remoteSig))) Behaviors.withTimers { timers => timers.startTimerWithFixedDelay(FlushEntropy, 30 seconds) collecting(collector, None) @@ -87,7 +87,10 @@ object WeakEntropyPool { case WrappedNodeUpdated(sig) => collecting(collector, collect(entropy_opt, sig ++ ByteVector.fromLong(System.currentTimeMillis()))) - case WrappedChannelSignature(wtxid) => collecting(collector, collect(entropy_opt, wtxid ++ ByteVector.fromLong(System.currentTimeMillis()))) + case WrappedChannelSignature(sig) => sig match { + case ChannelSpendSignature.IndividualSignature(sig) => collecting(collector, collect(entropy_opt, sig ++ ByteVector.fromLong(System.currentTimeMillis()))) + case ChannelSpendSignature.PartialSignatureWithNonce(partialSig, _) => collecting(collector, collect(entropy_opt, partialSig ++ ByteVector.fromLong(System.currentTimeMillis()))) + } } } 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 2abb7c6453..ac82d4b4f2 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 @@ -316,17 +316,6 @@ object ColorSerializer extends MinimalSerializer({ case c: Color => JString(c.toString) }) -// @formatter:off -private case class CommitTxAndRemoteSigJson(commitTx: CommitTx, remoteSig: ByteVector64) -private case class CommitTxAndRemotePartialSigJson(commitTx: CommitTx, remoteSig: ChannelSpendSignature.PartialSignatureWithNonce) -object CommitTxAndRemoteSigSerializer extends ConvertClassSerializer[CommitTxAndRemoteSig]( - i => i.remoteSig match { - case f: ChannelSpendSignature.IndividualSignature => CommitTxAndRemoteSigJson(i.commitTx, f.sig) - case p: ChannelSpendSignature.PartialSignatureWithNonce => CommitTxAndRemotePartialSigJson(i.commitTx, p) - } -) -// @formatter:on - // @formatter:off private sealed trait HopJson private case class ChannelHopJson(nodeId: PublicKey, nextNodeId: PublicKey, source: HopRelayParams) extends HopJson @@ -566,8 +555,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, fundingTxOutpoint: OutPoint, fundingAmount: Satoshi, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit]) +object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.fundingTxOutpoint, c.fundingAmount, c.localFundingStatus, c.remoteFundingStatus, c.localCommit, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit))) // @formatter:on // @formatter:off @@ -725,7 +714,6 @@ object JsonSerializers { OpenChannelResponseSerializer + CommandResponseSerializer + InputInfoSerializer + - CommitTxAndRemoteSigSerializer + ColorSerializer + ThrowableSerializer + FailureMessageSerializer + diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala index 52db0a055a..1b517d4c1e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala @@ -430,7 +430,7 @@ object PostRestartHtlcCleaner { val params = d.commitments.params val channelKeys = nodeParams.channelKeyManager.channelKeys(params.channelConfig, params.localParams.fundingKeyPath) val timedOutHtlcs: Set[Long] = (closingType_opt match { - case Some(c: Closing.LocalClose) => confirmedTxs.flatMap(tx => Closing.trimmedOrTimedOutHtlcs(params.commitmentFormat, c.localCommit, params.localParams.dustLimit, tx)) + case Some(c: Closing.LocalClose) => confirmedTxs.flatMap(tx => Closing.trimmedOrTimedOutHtlcs(channelKeys, d.commitments.latest, c.localCommit, tx)) case Some(c: Closing.RemoteClose) => confirmedTxs.flatMap(tx => Closing.trimmedOrTimedOutHtlcs(channelKeys, d.commitments.latest, c.remoteCommit, tx)) case Some(_: Closing.RevokedClose) => Set.empty // revoked commitments are handled using [[overriddenOutgoingHtlcs]] above case Some(_: Closing.RecoveryClose) => Set.empty // we lose htlc outputs in dataloss protection scenarios (future remote commit) 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 103b7628ee..ec5510db2e 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 @@ -781,11 +781,19 @@ object Transactions { } object ClaimAnchorOutputTx { - def redeemInfo(fundingKey: PublicKey, commitKeys: LocalCommitmentKeys, commitmentFormat: CommitmentFormat): RedeemInfo = - redeemInfo(fundingKey, commitKeys.publicKeys, toLocal = true, commitmentFormat) + def redeemInfo(fundingKey: PublicKey, commitKeys: LocalCommitmentKeys, commitmentFormat: CommitmentFormat): RedeemInfo = redeemInfo(fundingKey, commitKeys.publicKeys, toLocal = true, commitmentFormat) - def redeemInfo(fundingKey: PublicKey, commitKeys: RemoteCommitmentKeys, commitmentFormat: CommitmentFormat): RedeemInfo = - redeemInfo(fundingKey, commitKeys.publicKeys, toLocal = false, commitmentFormat) + def findInput(commitTx: Transaction, fundingKey: PublicKey, commitKeys: LocalCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { + val pubKeyScript = redeemInfo(fundingKey, commitKeys, commitmentFormat).pubkeyScript + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + } + + def redeemInfo(fundingKey: PublicKey, commitKeys: RemoteCommitmentKeys, commitmentFormat: CommitmentFormat): RedeemInfo = redeemInfo(fundingKey, commitKeys.publicKeys, toLocal = false, commitmentFormat) + + def findInput(commitTx: Transaction, fundingKey: PublicKey, commitKeys: RemoteCommitmentKeys, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, InputInfo] = { + val pubKeyScript = redeemInfo(fundingKey, commitKeys, commitmentFormat).pubkeyScript + findPubKeyScriptIndex(commitTx, pubKeyScript).map(outputIndex => InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty)) + } /** * @@ -806,26 +814,22 @@ object Transactions { } } - private def createUnsignedTx(redeemInfo: RedeemInfo, commitTx: Transaction): Either[TxGenerationSkipped, ClaimAnchorOutputTx] = { - val pubkeyScript = redeemInfo.pubkeyScript - findPubKeyScriptIndex(commitTx, pubkeyScript).map { outputIndex => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), ByteVector.empty) - val unsignedTx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil, - txOut = Nil, // anchor is only used to bump fees, the output will be added later depending on available inputs - lockTime = 0 - ) - ClaimAnchorOutputTx(input, unsignedTx) - } + private def createUnsignedTx(input: InputInfo): ClaimAnchorOutputTx = { + val unsignedTx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil, + txOut = Nil, // anchor is only used to bump fees, the output will be added later depending on available inputs + lockTime = 0 + ) + ClaimAnchorOutputTx(input, unsignedTx) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: LocalCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimAnchorOutputTx] = { - createUnsignedTx(redeemInfo(fundingKey.publicKey, commitKeys, commitmentFormat), commitTx) + findInput(commitTx, fundingKey.publicKey, commitKeys, commitmentFormat).map(input => createUnsignedTx(input)) } def createUnsignedTx(fundingKey: PrivateKey, commitKeys: RemoteCommitmentKeys, commitTx: Transaction, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimAnchorOutputTx] = { - createUnsignedTx(redeemInfo(fundingKey.publicKey, commitKeys, commitmentFormat), commitTx) + findInput(commitTx, fundingKey.publicKey, commitKeys, commitmentFormat).map(input => createUnsignedTx(input)) } } 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 d23bc3c37b..4b558ef66f 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 @@ -23,7 +23,7 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.CommitSig -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, UInt64, channel} +import fr.acinq.eclair.{CltvExpiryDelta, Features, InitFeature, MilliSatoshi, UInt64, channel, randomKey} import scodec.bits.{BitVector, ByteVector} private[channel] object ChannelTypes0 { @@ -41,14 +41,6 @@ private[channel] object ChannelTypes0 { // - the `htlcId` in htlc txs is used to detect timed out htlcs and relay them upstream, but it can be safely set to // 0 because the `timedOutHtlcs` in `Helpers.scala` explicitly handle the case where this information is unavailable. - private def getPartialInputInfo(parentTx: Transaction, childTx: Transaction): InputInfo = { - // When using the default commitment format, spending txs have a single input. These txs are fully signed and never - // modified: we don't use the InputInfo in closing business logic, so we don't need to fill everything (this part - // assumes that we only have standard channels, no anchor output channels - which was the case before version2). - val input = childTx.txIn.head.outPoint - InputInfo(input, parentTx.txOut(input.index.toInt), ByteVector.empty) - } - case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) { def migrate(): channel.LocalCommitPublished = { val htlcTxs = htlcSuccessTxs ++ htlcTimeoutTxs @@ -56,16 +48,11 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainDelayedOutputTxNew = claimMainDelayedOutputTx.map(tx => ClaimLocalDelayedOutputTx(getPartialInputInfo(commitTx, tx), tx, CltvExpiryDelta(0))) - val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, CltvExpiry(0))) - val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, CltvExpiry(0))) - val htlcTxsNew = (htlcSuccessTxsNew ++ htlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap - val claimHtlcDelayedTxsNew = claimHtlcDelayedTxs.map(tx => { - val htlcTx = htlcTxs.find(_.txid == tx.txIn.head.outPoint.txid) - require(htlcTx.nonEmpty, s"3rd-stage htlc tx doesn't spend one of our htlc txs: claim-htlc-tx=$tx, htlc-txs=${htlcTxs.mkString(",")}") - HtlcDelayedTx(getPartialInputInfo(htlcTx.get, tx), tx, CltvExpiryDelta(0)) - }) - channel.LocalCommitPublished(commitTx, claimMainDelayedOutputTxNew, htlcTxsNew, claimHtlcDelayedTxsNew, Nil, irrevocablySpentNew) + val localOutput_opt = claimMainDelayedOutputTx.map(_.txIn.head.outPoint) + val htlcSuccessOutputs = htlcSuccessTxs.map(tx => tx.txIn.head.outPoint -> IncomingHtlcId(0)).toMap + val htlcTimeoutOutputs = htlcTimeoutTxs.map(tx => tx.txIn.head.outPoint -> OutgoingHtlcId(0)).toMap + val htlcDelayedOutputs = claimHtlcDelayedTxs.map(_.txIn.head.outPoint).toSet + channel.LocalCommitPublished(commitTx, localOutput_opt, anchorOutput_opt = None, htlcSuccessOutputs ++ htlcTimeoutOutputs, htlcDelayedOutputs, irrevocablySpentNew) } } @@ -76,11 +63,10 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx)) - val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => ClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, CltvExpiry(0))) - val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, CltvExpiry(0))) - val claimHtlcTxsNew = (claimHtlcSuccessTxsNew ++ claimHtlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap - channel.RemoteCommitPublished(commitTx, claimMainOutputTxNew, claimHtlcTxsNew, Nil, irrevocablySpentNew) + val localOutput_opt = claimMainOutputTx.map(_.txIn.head.outPoint) + val htlcSuccessOutputs = claimHtlcSuccessTxs.map(tx => tx.txIn.head.outPoint -> OutgoingHtlcId(0)).toMap + val htlcTimeoutOutputs = claimHtlcTimeoutTxs.map(tx => tx.txIn.head.outPoint -> IncomingHtlcId(0)).toMap + channel.RemoteCommitPublished(commitTx, localOutput_opt, anchorOutput_opt = None, htlcSuccessOutputs ++ htlcTimeoutOutputs, irrevocablySpentNew) } } @@ -90,14 +76,11 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx)) - val mainPenaltyTxNew = mainPenaltyTx.map(tx => MainPenaltyTx(getPartialInputInfo(commitTx, tx), tx, CltvExpiryDelta(0))) - val htlcPenaltyTxsNew = htlcPenaltyTxs.map(tx => HtlcPenaltyTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, CltvExpiry(0))) - val claimHtlcDelayedPenaltyTxsNew = claimHtlcDelayedPenaltyTxs.map(tx => { - // We don't have all the `InputInfo` data, but it's ok: we only use the tx that is fully signed. - ClaimHtlcDelayedOutputPenaltyTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), ByteVector.empty), tx, CltvExpiryDelta(0)) - }) - channel.RevokedCommitPublished(commitTx, claimMainOutputTxNew, mainPenaltyTxNew, htlcPenaltyTxsNew, claimHtlcDelayedPenaltyTxsNew, irrevocablySpentNew) + val localOutput_opt = claimMainOutputTx.map(_.txIn.head.outPoint) + val remoteOutput_opt = mainPenaltyTx.map(_.txIn.head.outPoint) + val htlcOutputs = htlcPenaltyTxs.map(_.txIn.head.outPoint).toSet + val htlcDelayedOutputs = claimHtlcDelayedPenaltyTxs.map(_.txIn.head.outPoint).toSet + channel.RevokedCommitPublished(commitTx, localOutput_opt, remoteOutput_opt, htlcOutputs, htlcDelayedOutputs, irrevocablySpentNew) } } @@ -119,22 +102,13 @@ private[channel] object ChannelTypes0 { case class PublishableTxs(commitTx: CommitTx, htlcTxsAndSigs: List[HtlcTxAndSigs]) // 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 unsigned txs and remote - // signatures. + // 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 = { val remoteSig = extractRemoteSig(publishableTxs.commitTx, remoteFundingPubKey) val unsignedCommitTx = publishableTxs.commitTx.copy(tx = removeWitnesses(publishableTxs.commitTx.tx)) - val commitTxAndRemoteSig = CommitTxAndRemoteSig(unsignedCommitTx, remoteSig) - val htlcTxsAndRemoteSigs = publishableTxs.htlcTxsAndSigs map { - case HtlcTxAndSigs(htlcTx: HtlcSuccessTx, _, remoteSig) => - val unsignedHtlcTx = htlcTx.copy(tx = removeWitnesses(htlcTx.tx)) - HtlcTxAndRemoteSig(unsignedHtlcTx, remoteSig) - case HtlcTxAndSigs(htlcTx: HtlcTimeoutTx, _, remoteSig) => - val unsignedHtlcTx = htlcTx.copy(tx = removeWitnesses(htlcTx.tx)) - HtlcTxAndRemoteSig(unsignedHtlcTx, remoteSig) - } - channel.LocalCommit(index, spec, commitTxAndRemoteSig, htlcTxsAndRemoteSigs) + val htlcRemoteSigs = publishableTxs.htlcTxsAndSigs.map(_.remoteSig) + channel.LocalCommit(index, spec, unsignedCommitTx.tx.txid, remoteSig, htlcRemoteSigs) } private def extractRemoteSig(commitTx: CommitTx, remoteFundingPubKey: PublicKey): ChannelSpendSignature.IndividualSignature = { @@ -247,10 +221,13 @@ private[channel] object ChannelTypes0 { fundingTxIndex = 0, firstRemoteCommitIndex = 0, remoteFundingPubKey = remoteParams.fundingPubKey, + fundingTxOutpoint = localCommit.publishableTxs.commitTx.input.outPoint, + fundingAmount = localCommit.publishableTxs.commitTx.input.txOut.amount, // 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, + format = channelFeatures.commitmentFormat, localCommit.migrate(remoteParams.fundingPubKey), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( 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 0b337d8dd6..dbdc7c838e 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 @@ -251,14 +251,14 @@ private[channel] object ChannelCodecs2 { ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) :: ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxCodec)) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[LocalCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) :: ("claimHtlcTxs" | mapCodec(outPointCodec, optional(bool8, claimHtlcTxCodec))) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[RemoteCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( ("commitTx" | txCodec) :: @@ -266,7 +266,7 @@ private[channel] object ChannelCodecs2 { ("mainPenaltyTx" | optional(bool8, mainPenaltyTxCodec)) :: ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxCodec)) :: ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxCodec)) :: - ("spent" | spentMapCodec)).as[RevokedCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly val DATA_WAIT_FOR_FUNDING_CONFIRMED_00_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentsCodec) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala new file mode 100644 index 0000000000..474788164d --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelTypes2.scala @@ -0,0 +1,71 @@ +/* + * 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.version2 + +import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction} +import fr.acinq.eclair.channel +import fr.acinq.eclair.channel.{IncomingHtlcId, OutgoingHtlcId} +import fr.acinq.eclair.transactions.Transactions._ + +private[channel] object ChannelTypes2 { + + case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[ClaimLocalDelayedOutputTx], htlcTxs: Map[OutPoint, Option[HtlcTx]], claimHtlcDelayedTxs: List[HtlcDelayedTx], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) { + def migrate(): channel.LocalCommitPublished = channel.LocalCommitPublished( + commitTx = commitTx, + localOutput_opt = claimMainDelayedOutputTx.map(_.input.outPoint), + anchorOutput_opt = claimAnchorTxs.headOption.map(_.input.outPoint), + htlcs = htlcTxs.map { + case (outpoint, Some(htlcTx: HtlcSuccessTx)) => outpoint -> IncomingHtlcId(htlcTx.htlcId) + case (outpoint, Some(htlcTx: HtlcTimeoutTx)) => outpoint -> OutgoingHtlcId(htlcTx.htlcId) + // This case only happens for a received HTLC for which we don't yet have the preimage. + // We cannot easily find the htlcId, so we just set it to a high value that won't match existing HTLCs. + // This is fine because it is only used to unwatch HTLC outpoints that were failed downstream, which is just + // an optimization to go to CLOSED more quickly. + case (outpoint, None) => outpoint -> IncomingHtlcId(0x00ffffffffffffffL) + }, + htlcDelayedOutputs = claimHtlcDelayedTxs.map(_.input.outPoint).toSet, + irrevocablySpent = irrevocablySpent + ) + } + + case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], claimHtlcTxs: Map[OutPoint, Option[ClaimHtlcTx]], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) { + def migrate(): channel.RemoteCommitPublished = channel.RemoteCommitPublished( + commitTx = commitTx, + localOutput_opt = claimMainOutputTx.map(_.input.outPoint), + anchorOutput_opt = claimAnchorTxs.headOption.map(_.input.outPoint), + htlcs = claimHtlcTxs.map { + case (outpoint, Some(htlcTx: ClaimHtlcSuccessTx)) => outpoint -> OutgoingHtlcId(htlcTx.htlcId) + case (outpoint, Some(htlcTx: ClaimHtlcTimeoutTx)) => outpoint -> IncomingHtlcId(htlcTx.htlcId) + // Similarly to LocalCommitPublished above, it is fine to ignore this case. + case (outpoint, None) => outpoint -> OutgoingHtlcId(0x00ffffffffffffffL) + }, + irrevocablySpent = irrevocablySpent + ) + } + + case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], mainPenaltyTx: Option[MainPenaltyTx], htlcPenaltyTxs: List[HtlcPenaltyTx], claimHtlcDelayedPenaltyTxs: List[ClaimHtlcDelayedOutputPenaltyTx], irrevocablySpent: Map[OutPoint, Transaction]) { + def migrate(): channel.RevokedCommitPublished = channel.RevokedCommitPublished( + commitTx = commitTx, + localOutput_opt = claimMainOutputTx.map(_.input.outPoint), + remoteOutput_opt = mainPenaltyTx.map(_.input.outPoint), + htlcOutputs = htlcPenaltyTxs.map(_.input.outPoint).toSet, + htlcDelayedOutputs = claimHtlcDelayedPenaltyTxs.map(_.input.outPoint).toSet, + irrevocablySpent = irrevocablySpent + ) + } + +} 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 8552a82575..6c1a15dfa5 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 @@ -18,7 +18,6 @@ package fr.acinq.eclair.wire.internal.channel.version3 import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut} -import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.LocalFundingStatus._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ @@ -26,6 +25,7 @@ import fr.acinq.eclair.crypto.ShaChain 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.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage @@ -205,19 +205,19 @@ private[channel] object ChannelCodecs3 { .typecase(0x02, claimHtlcTimeoutTxNoConfirmCodec) .typecase(0x03, claimHtlcSuccessTxNoConfirmCodec) - val htlcTxsAndRemoteSigsCodec: Codec[HtlcTxAndRemoteSig] = ( + val htlcTxsAndRemoteSigsCodec: Codec[ChannelTypes3.HtlcTxAndRemoteSig] = ( ("txinfo" | htlcTxCodec) :: - ("remoteSig" | bytes64)).as[HtlcTxAndRemoteSig] + ("remoteSig" | bytes64)).as[ChannelTypes3.HtlcTxAndRemoteSig] - val commitTxAndRemoteSigCodec: Codec[CommitTxAndRemoteSig] = ( + val commitTxAndRemoteSigCodec: Codec[ChannelTypes3.CommitTxAndRemoteSig] = ( ("commitTx" | commitTxCodec) :: - ("remoteSig" | bytes64.as[ChannelSpendSignature.IndividualSignature].upcast[ChannelSpendSignature])).as[CommitTxAndRemoteSig] + ("remoteSig" | bytes64.as[ChannelSpendSignature.IndividualSignature])).as[ChannelTypes3.CommitTxAndRemoteSig] - val localCommitCodec: Codec[LocalCommit] = ( + val localCommitCodec: Codec[ChannelTypes3.LocalCommit] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTxAndRemoteSig" | commitTxAndRemoteSigCodec) :: - ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[LocalCommit] + ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[ChannelTypes3.LocalCommit] val remoteCommitCodec: Codec[RemoteCommit] = ( ("index" | uint64overflow) :: @@ -321,14 +321,14 @@ private[channel] object ChannelCodecs3 { ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) :: ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxCodec)) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[LocalCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) :: ("claimHtlcTxs" | mapCodec(outPointCodec, optional(bool8, claimHtlcTxCodec))) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[RemoteCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( ("commitTx" | txCodec) :: @@ -336,7 +336,7 @@ private[channel] object ChannelCodecs3 { ("mainPenaltyTx" | optional(bool8, mainPenaltyTxCodec)) :: ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxCodec)) :: ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxCodec)) :: - ("spent" | spentMapCodec)).as[RevokedCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly private val shortids: Codec[ShortIdAliases] = ( ("real_opt" | optional(bool8, realshortchannelid)) :: @@ -449,8 +449,7 @@ private[channel] object ChannelCodecs3 { ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map { case commitments :: fundingTx_opt :: waitingSince :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil => val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt)) - val closing = DATA_CLOSING(commitments1, waitingSince, commitments1.params.localParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished) - ChannelTypes3.fillForceCloseTxData(closing) + DATA_CLOSING(commitments1, waitingSince, commitments1.params.localParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished) }.decodeOnly val unconfirmedFundingTxCodec: Codec[UnconfirmedFundingTx] = discriminated[UnconfirmedFundingTx].by(uint8) @@ -471,8 +470,7 @@ private[channel] object ChannelCodecs3 { ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).map { case commitments :: fundingTx_opt :: waitingSince :: _ :: mutualCloseProposed :: mutualClosePublished :: localCommitPublished :: remoteCommitPublished :: nextRemoteCommitPublished :: futureRemoteCommitPublished :: revokedCommitPublished :: HNil => val commitments1 = ChannelTypes0.setFundingStatus(commitments, SingleFundedUnconfirmedFundingTx(fundingTx_opt.flatMap(_.signedTx_opt))) - val closing = DATA_CLOSING(commitments1, waitingSince, commitments.params.localParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished) - ChannelTypes3.fillForceCloseTxData(closing) + DATA_CLOSING(commitments1, waitingSince, commitments.params.localParams.upfrontShutdownScript_opt.get, mutualCloseProposed, mutualClosePublished, localCommitPublished, remoteCommitPublished, nextRemoteCommitPublished, futureRemoteCommitPublished, revokedCommitPublished) }.decodeOnly val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_06_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( 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 a5dc426133..3a4b5014d0 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 @@ -16,13 +16,14 @@ package fr.acinq.eclair.wire.internal.channel.version3 -import fr.acinq.bitcoin.scalacompat.ByteVector32 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.DirectedHtlc -import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcSuccessTx, ClaimHtlcTimeoutTx, HtlcSuccessTx, HtlcTimeoutTx} +import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.transactions.Transactions.{CommitTx, HtlcTx, InputInfo} import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommitSig @@ -30,13 +31,33 @@ private[channel] object ChannelTypes3 { case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) + case class HtlcTxAndRemoteSig(htlcTx: HtlcTx, remoteSig: ByteVector64) + + case class CommitTxAndRemoteSig(commitTx: CommitTx, remoteSig: ChannelSpendSignature.IndividualSignature) + + // 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(): (InputInfo, channel.LocalCommit) = ( + commitTxAndRemoteSig.commitTx.input, + channel.LocalCommit(index, spec, commitTxAndRemoteSig.commitTx.tx.txid, commitTxAndRemoteSig.remoteSig, htlcTxsAndRemoteSigs.map(_.remoteSig)) + ) + } + + case class UnsignedLocalCommit(index: Long, spec: CommitmentSpec, commitTx: CommitTx, htlcTxs: List[HtlcTx]) { + def migrate(): (InputInfo, InteractiveTxSigningSession.UnsignedLocalCommit) = ( + commitTx.input, + 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: LocalParams, remoteParams: ChannelTypes0.RemoteParams, channelFlags: ChannelFlags, - localCommit: LocalCommit, remoteCommit: RemoteCommit, + localCommit: ChannelTypes3.LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, localNextHtlcId: Long, remoteNextHtlcId: Long, originChannels: Map[Long, Origin], @@ -47,7 +68,15 @@ private[channel] object ChannelTypes3 { 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, remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), + Seq(Commitment( + fundingTxIndex = 0, + firstRemoteCommitIndex = 0, + remoteFundingPubKey = remoteParams.fundingPubKey, + fundingTxOutpoint = localCommit.commitTxAndRemoteSig.commitTx.input.outPoint, + fundingAmount = localCommit.commitTxAndRemoteSig.commitTx.input.txOut.amount, + localFundingStatus, remoteFundingStatus, + format = channelFeatures.commitmentFormat, + localCommit.migrate()._2, remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), inactive = Nil, remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), remotePerCommitmentSecrets, @@ -55,92 +84,4 @@ private[channel] object ChannelTypes3 { ) } - // Between version4 and version5 (in https://github.com/ACINQ/eclair/pull/3074), we added more fields to our - // TransactionWithInputInfo instances. We fill those missing fields in case nodes upgrade while a channel is closing. - def fillForceCloseTxData(closing: DATA_CLOSING): DATA_CLOSING = { - closing.copy( - localCommitPublished = closing.localCommitPublished.map(lcp => { - // It is *our* commitment transaction: it uses the to_self_delay that *they* set. - val toLocalDelay = closing.commitments.params.remoteParams.toSelfDelay - val incomingHtlcs = closing.commitments.latest.localCommit.spec.htlcs.collect(DirectedHtlc.incoming).map(add => add.id -> add).toMap - val outgoingHtlcs = closing.commitments.latest.localCommit.spec.htlcs.collect(DirectedHtlc.outgoing).map(add => add.id -> add).toMap - lcp.copy( - claimMainDelayedOutputTx = lcp.claimMainDelayedOutputTx.map(_.copy(toLocalDelay = toLocalDelay)), - htlcTxs = lcp.htlcTxs.map { - case (outpoint, Some(htlcTx: HtlcSuccessTx)) => - val htlcTx1 = incomingHtlcs.get(htlcTx.htlcId) match { - case Some(htlc) => htlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => htlcTx - } - (outpoint, Some(htlcTx1)) - case (outpoint, Some(htlcTx: HtlcTimeoutTx)) => - val htlcTx1 = outgoingHtlcs.get(htlcTx.htlcId) match { - case Some(htlc) => htlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => htlcTx - } - (outpoint, Some(htlcTx1)) - case (outpoint, None) => (outpoint, None) - }, - claimHtlcDelayedTxs = lcp.claimHtlcDelayedTxs.map(_.copy(toLocalDelay = toLocalDelay)), - ) - }), - remoteCommitPublished = closing.remoteCommitPublished.map(rcp => { - val incomingHtlcs = closing.commitments.latest.remoteCommit.spec.htlcs.collect(DirectedHtlc.incoming).map(add => add.id -> add).toMap - val outgoingHtlcs = closing.commitments.latest.remoteCommit.spec.htlcs.collect(DirectedHtlc.outgoing).map(add => add.id -> add).toMap - rcp.copy( - claimHtlcTxs = rcp.claimHtlcTxs.map { - case (outpoint, Some(claimHtlcTx: ClaimHtlcSuccessTx)) => - val claimHtlcTx1 = outgoingHtlcs.get(claimHtlcTx.htlcId) match { - case Some(htlc) => claimHtlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => claimHtlcTx - } - (outpoint, Some(claimHtlcTx1)) - case (outpoint, Some(claimHtlcTx: ClaimHtlcTimeoutTx)) => - val claimHtlcTx1 = incomingHtlcs.get(claimHtlcTx.htlcId) match { - case Some(htlc) => claimHtlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => claimHtlcTx - } - (outpoint, Some(claimHtlcTx1)) - case (outpoint, None) => (outpoint, None) - } - ) - }), - nextRemoteCommitPublished = closing.nextRemoteCommitPublished.map(rcp => { - val incomingHtlcs = closing.commitments.latest.nextRemoteCommit_opt.get.commit.spec.htlcs.collect(DirectedHtlc.incoming).map(add => add.id -> add).toMap - val outgoingHtlcs = closing.commitments.latest.nextRemoteCommit_opt.get.commit.spec.htlcs.collect(DirectedHtlc.outgoing).map(add => add.id -> add).toMap - rcp.copy( - claimHtlcTxs = rcp.claimHtlcTxs.map { - case (outpoint, Some(claimHtlcTx: ClaimHtlcSuccessTx)) => - val claimHtlcTx1 = outgoingHtlcs.get(claimHtlcTx.htlcId) match { - case Some(htlc) => claimHtlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => claimHtlcTx - } - (outpoint, Some(claimHtlcTx1)) - case (outpoint, Some(claimHtlcTx: ClaimHtlcTimeoutTx)) => - val claimHtlcTx1 = incomingHtlcs.get(claimHtlcTx.htlcId) match { - case Some(htlc) => claimHtlcTx.copy(paymentHash = htlc.paymentHash, htlcExpiry = htlc.cltvExpiry) - case None => claimHtlcTx - } - (outpoint, Some(claimHtlcTx1)) - case (outpoint, None) => (outpoint, None) - } - ) - }), - revokedCommitPublished = closing.revokedCommitPublished.map(rvk => { - // It is *their* commitment transaction: it uses the to_self_delay that *we* set. - val toRemoteDelay = closing.commitments.params.localParams.toSelfDelay - rvk.copy( - mainPenaltyTx = rvk.mainPenaltyTx.map(_.copy(toRemoteDelay = toRemoteDelay)), - // Ideally, we should fill the payment_hash and htlc_expiry for HTLC-penalty txs. Unfortunately we cannot - // easily do that: we'd need access to the past HTLCs DB and we'd need to recompute the revocation secret. - // In practice, it is fine if we don't migrate those transactions because: - // - we already have a previously signed version of them that pays a high feerate and will likely confirm - // - if they don't confirm and our peer is able to get their HTLC tx confirmed, we will react and use a - // penalty transaction to claim the output of their HTLC tx - claimHtlcDelayedPenaltyTxs = rvk.claimHtlcDelayedPenaltyTxs.map(_.copy(toRemoteDelay = toRemoteDelay)), - ) - }) - ) - } - } 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 3f5f622ead..ed52b71f1e 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 @@ -12,11 +12,12 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc} +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, BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature, RealShortChannelId, channel} +import fr.acinq.eclair.{Alias, CltvExpiry, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, PermanentChannelFeature, RealShortChannelId, channel} import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -198,13 +199,17 @@ private[channel] object ChannelCodecs4 { .typecase(0x02, claimHtlcTimeoutTxNoConfirmCodec) .typecase(0x03, claimHtlcSuccessTxNoConfirmCodec) - val htlcTxsAndRemoteSigsCodec: Codec[HtlcTxAndRemoteSig] = ( + val htlcTxsAndRemoteSigsCodec: Codec[ChannelTypes3.HtlcTxAndRemoteSig] = ( ("txinfo" | htlcTxCodec) :: - ("remoteSig" | bytes64)).as[HtlcTxAndRemoteSig] - - val commitTxAndRemoteSigCodec: Codec[CommitTxAndRemoteSig] = ( + ("remoteSig" | bytes64)).as[ChannelTypes3.HtlcTxAndRemoteSig] + + val commitTxAndRemoteSigCodec: Codec[ChannelTypes3.CommitTxAndRemoteSig] = ( ("commitTx" | commitTxCodec) :: - ("remoteSig" | bytes64.as[ChannelSpendSignature.IndividualSignature].upcast[ChannelSpendSignature])).as[CommitTxAndRemoteSig] + ("remoteSig" | bytes64.as[ChannelSpendSignature.IndividualSignature])).as[ChannelTypes3.CommitTxAndRemoteSig] + + val channelSpendSignatureCodec: Codec[ChannelSpendSignature] = discriminated[ChannelSpendSignature].by(uint8) + .typecase(0x01, bytes64.as[ChannelSpendSignature.IndividualSignature]) + .typecase(0x02, (("partialSig" | bytes32) :: ("nonce" | publicNonce)).as[ChannelSpendSignature.PartialSignatureWithNonce]) val updateMessageCodec: Codec[UpdateMessage] = lengthDelimited(lightningMessageCodec.narrow[UpdateMessage](f => Attempt.successful(f.asInstanceOf[UpdateMessage]), g => g)) @@ -457,6 +462,13 @@ private[channel] object ChannelCodecs4 { .typecase(0x01, provide(RemoteFundingStatus.NotLocked)) .typecase(0x02, provide(RemoteFundingStatus.Locked)) + val commitmentFormatCodec: Codec[CommitmentFormat] = discriminated[CommitmentFormat].by(uint8) + .typecase(0x00, provide(DefaultCommitmentFormat)) + .typecase(0x01, provide(UnsafeLegacyAnchorOutputsCommitmentFormat)) + .typecase(0x02, provide(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) + .typecase(0x03, provide(LegacySimpleTaprootChannelCommitmentFormat)) + .typecase(0x04, provide(ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)) + val paramsCodec: Codec[ChannelParams] = ( ("channelId" | bytes32) :: ("channelConfig" | channelConfigCodec) :: @@ -474,11 +486,18 @@ private[channel] object ChannelCodecs4 { ("localNextHtlcId" | uint64overflow) :: ("remoteNextHtlcId" | uint64overflow)).as[CommitmentChanges] - private def localCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[LocalCommit] = ( + private def localCommitWithTxsCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[(InputInfo, LocalCommit)] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTxAndRemoteSig" | commitTxAndRemoteSigCodec) :: - ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[LocalCommit] + ("htlcTxsAndRemoteSigs" | listOfN(uint16, htlcTxsAndRemoteSigsCodec))).as[ChannelTypes3.LocalCommit].map(_.migrate()).decodeOnly + + 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) :: @@ -490,22 +509,41 @@ private[channel] object ChannelCodecs4 { ("sig" | lengthDelimited(commitSigCodec)) :: ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit] - private def commitmentCodecWithoutFirstRemoteCommitIndex(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + private def commitmentCodecWithoutFirstRemoteCommitIndex(commitmentFormat: CommitmentFormat, htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | provide(0L)) :: - ("fundingPubKey" | publicKey) :: - ("fundingTxStatus" | fundingTxStatusCodec) :: + ("remoteFundingPubKey" | publicKey) :: + ("localFundingTxStatus" | fundingTxStatusCodec) :: ("remoteFundingStatus" | remoteFundingStatusCodec) :: - ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: + ("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)))))).map { + case fundingTxIndex :: firstRemoteCommitIndex :: remoteFundingPubKey :: localFundingTxStatus :: remoteFundingStatus :: localCommit :: remoteCommit :: nextRemoteCommit_opt :: HNil => + Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localCommit._1.outPoint, localCommit._1.txOut.amount, localFundingTxStatus, remoteFundingStatus, commitmentFormat, localCommit._2, remoteCommit, nextRemoteCommit_opt) + }.decodeOnly + + private def commitmentCodecWithLocalTxs(commitmentFormat: CommitmentFormat, htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( + ("fundingTxIndex" | uint32) :: + ("firstRemoteCommitIndex" | uint64overflow) :: + ("remoteFundingPubKey" | publicKey) :: + ("localFundingTxStatus" | fundingTxStatusCodec) :: + ("remoteFundingStatus" | remoteFundingStatusCodec) :: + ("localCommit" | localCommitWithTxsCodec(minimalCommitmentSpecCodec(htlcs))) :: + ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: + ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).map { + case fundingTxIndex :: firstRemoteCommitIndex :: remoteFundingPubKey :: localFundingTxStatus :: remoteFundingStatus :: localCommit :: remoteCommit :: nextRemoteCommit_opt :: HNil => + Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localCommit._1.outPoint, localCommit._1.txOut.amount, localFundingTxStatus, remoteFundingStatus, commitmentFormat, localCommit._2, remoteCommit, nextRemoteCommit_opt) + }.decodeOnly private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( ("fundingTxIndex" | uint32) :: ("firstRemoteCommitIndex" | uint64overflow) :: - ("fundingPubKey" | publicKey) :: - ("fundingTxStatus" | fundingTxStatusCodec) :: + ("remoteFundingPubKey" | publicKey) :: + ("fundingTxOutpoint" | outPointCodec) :: + ("fundingAmount" | satoshi) :: + ("localFundingTxStatus" | fundingTxStatusCodec) :: ("remoteFundingStatus" | remoteFundingStatusCodec) :: + ("format" | commitmentFormatCodec) :: ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: ("remoteCommit" | remoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))) :: ("nextRemoteCommit_opt" | optional(bool8, nextRemoteCommitCodec(minimalCommitmentSpecCodec(htlcs.map(_.opposite)))))).as[Commitment] @@ -564,16 +602,31 @@ private[channel] object ChannelCodecs4 { } val commitmentsCodecWithoutFirstRemoteCommitIndex: Codec[Commitments] = ( - ("params" | paramsCodec) :: + ("params" | paramsCodec) >>:~ { params => ("changes" | changesCodec) :: (("htlcs" | setCodec(htlcCodec)) >>:~ { htlcs => - ("active" | listOfN(uint16, commitmentCodecWithoutFirstRemoteCommitIndex(htlcs))) :: - ("inactive" | listOfN(uint16, commitmentCodecWithoutFirstRemoteCommitIndex(htlcs))) :: + ("active" | listOfN(uint16, commitmentCodecWithoutFirstRemoteCommitIndex(params.commitmentFormat, htlcs))) :: + ("inactive" | listOfN(uint16, commitmentCodecWithoutFirstRemoteCommitIndex(params.commitmentFormat, htlcs))) :: ("remoteNextCommitInfo" | either(bool8, waitForRevCodec, publicKey)) :: ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("originChannels" | originsMapCodec) :: ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) - })).as[EncodedCommitments].xmap( + })}).as[EncodedCommitments].xmap( + encoded => encoded.toCommitments, + commitments => EncodedCommitments(commitments) + ) + + val commitmentsCodecWithLocalTxs: Codec[Commitments] = ( + ("params" | paramsCodec) >>:~ { params => + ("changes" | changesCodec) :: + (("htlcs" | setCodec(htlcCodec)) >>:~ { htlcs => + ("active" | listOfN(uint16, commitmentCodecWithLocalTxs(params.commitmentFormat, htlcs))) :: + ("inactive" | listOfN(uint16, commitmentCodecWithLocalTxs(params.commitmentFormat, htlcs))) :: + ("remoteNextCommitInfo" | either(bool8, waitForRevCodec, publicKey)) :: + ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: + ("originChannels" | originsMapCodec) :: + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata)) + })}).as[EncodedCommitments].xmap( encoded => encoded.toCommitments, commitments => EncodedCommitments(commitments) ) @@ -594,7 +647,8 @@ private[channel] object ChannelCodecs4 { ) val versionedCommitmentsCodec: Codec[Commitments] = discriminated[Commitments].by(uint8) - .typecase(0x01, commitmentsCodec) + .typecase(0x02, commitmentsCodec) + .typecase(0x01, commitmentsCodecWithLocalTxs) val closingFeeratesCodec: Codec[ClosingFeerates] = ( ("preferred" | feeratePerKw) :: @@ -602,88 +656,147 @@ private[channel] object ChannelCodecs4 { ("max" | feeratePerKw)).as[ClosingFeerates] val closeStatusCodec: Codec[CloseStatus] = discriminated[CloseStatus].by(uint8) - .typecase(0x01, optional(bool8, closingFeeratesCodec).as[CloseStatus.Initiator]) - .typecase(0x02, optional(bool8, closingFeeratesCodec).as[CloseStatus.NonInitiator]) + .typecase(0x01, optional(bool8, closingFeeratesCodec).as[CloseStatus.Initiator]) + .typecase(0x02, optional(bool8, closingFeeratesCodec).as[CloseStatus.NonInitiator]) val closingTxProposedCodec: Codec[ClosingTxProposed] = ( ("unsignedTx" | closingTxCodec) :: ("localClosingSigned" | lengthDelimited(closingSignedCodec))).as[ClosingTxProposed] - private val legacyLocalCommitPublishedCodec: Codec[LocalCommitPublished] = ( + private val directedHtlcIdCodec: Codec[DirectedHtlcId] = discriminated[DirectedHtlcId].by(uint8) + .typecase(0x01, uint64overflow.as[IncomingHtlcId]) + .typecase(0x02, uint64overflow.as[OutgoingHtlcId]) + + private val localCommitPublishedCodec_07: Codec[LocalCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainDelayedOutputTx" | optional(bool8, claimLocalDelayedOutputTxNoToSelfDelayCodec)) :: ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) :: ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxNoToSelfDelayCodec)) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[LocalCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly - val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( + private val localCommitPublishedCodec_1a: Codec[LocalCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainDelayedOutputTx" | optional(bool8, claimLocalDelayedOutputTxCodec)) :: ("htlcTxs" | mapCodec(outPointCodec, optional(bool8, htlcTxCodec))) :: ("claimHtlcDelayedTx" | listOfN(uint16, htlcDelayedTxCodec)) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[LocalCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.LocalCommitPublished].decodeOnly.map[LocalCommitPublished](_.migrate()).decodeOnly - val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( + val localCommitPublishedCodec: Codec[LocalCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("htlcOutputs" | mapCodec(outPointCodec, directedHtlcIdCodec)) :: + ("htlcDelayedOutputs" | setCodec(outPointCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[LocalCommitPublished] + + private val remoteCommitPublishedCodec_07: Codec[RemoteCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) :: ("claimHtlcTxs" | mapCodec(outPointCodec, optional(bool8, claimHtlcTxCodec))) :: ("claimAnchorTxs" | listOfN(uint16, claimAnchorOutputTxCodec)) :: - ("spent" | spentMapCodec)).as[RemoteCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RemoteCommitPublished].decodeOnly.map[RemoteCommitPublished](_.migrate()).decodeOnly - private val legacyRevokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( + val remoteCommitPublishedCodec: Codec[RemoteCommitPublished] = ( + ("commitTx" | txCodec) :: + ("localOutput_opt" | optional(bool8, outPointCodec)) :: + ("anchorOutput_opt" | optional(bool8, outPointCodec)) :: + ("htlcOutputs" | mapCodec(outPointCodec, directedHtlcIdCodec)) :: + ("irrevocablySpent" | spentMapCodec)).as[RemoteCommitPublished] + + private val revokedCommitPublishedCodec_07: Codec[RevokedCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) :: ("mainPenaltyTx" | optional(bool8, mainPenaltyTxNoToSelfDelayCodec)) :: ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxNoPaymentHashCodec)) :: ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxNoToSelfDelayCodec)) :: - ("spent" | spentMapCodec)).as[RevokedCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly - val revokedCommitPublishedCodec: Codec[RevokedCommitPublished] = ( + private val revokedCommitPublishedCodec_1a: Codec[RevokedCommitPublished] = ( ("commitTx" | txCodec) :: ("claimMainOutputTx" | optional(bool8, claimRemoteCommitMainOutputTxCodec)) :: ("mainPenaltyTx" | optional(bool8, mainPenaltyTxCodec)) :: ("htlcPenaltyTxs" | listOfN(uint16, htlcPenaltyTxCodec)) :: ("claimHtlcDelayedPenaltyTxs" | listOfN(uint16, claimHtlcDelayedOutputPenaltyTxCodec)) :: - ("spent" | spentMapCodec)).as[RevokedCommitPublished] + ("spent" | spentMapCodec)).as[ChannelTypes2.RevokedCommitPublished].decodeOnly.map[RevokedCommitPublished](_.migrate()).decodeOnly + + 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] // 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, interactiveTxWaitingForSigsCodec): (Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs]) = { - val unsignedLocalCommitCodec: Codec[UnsignedLocalCommit] = ( + private val (interactiveTxWaitingForSigsWithoutLiquidityPurchaseCodec, interactiveTxWaitingForSigsWithTxsCodec, interactiveTxWaitingForSigsCodec): (Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs], Codec[InteractiveTxSigningSession.WaitingForSigs]) = { + val unsignedLocalCommitWithTxsCodec: Codec[(InputInfo, UnsignedLocalCommit)] = ( ("index" | uint64overflow) :: ("spec" | commitmentSpecCodec) :: ("commitTx" | commitTxCodec) :: - ("htlcTxs" | listOfN(uint16, htlcTxCodec))).as[UnsignedLocalCommit] + ("htlcTxs" | listOfN(uint16, htlcTxCodec))).as[ChannelTypes3.UnsignedLocalCommit].map(_.migrate()).decodeOnly + + val unsignedLocalCommitCodec: Codec[UnsignedLocalCommit] = ( + ("index" | uint64overflow) :: + ("spec" | commitmentSpecCodec) :: + ("txId" | txId)).as[UnsignedLocalCommit] val waitingForSigsWithoutLiquidityPurchaseCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: - ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: + ("localCommit" | either(bool8, unsignedLocalCommitWithTxsCodec, localCommitWithTxsCodec(commitmentSpecCodec))) :: + ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: + ("liquidityPurchase" | provide(Option.empty[LiquidityAds.PurchaseBasicInfo]))) + .map { + case fundingParams :: fundingTxIndex :: fundingTx :: localCommit :: remoteCommit :: liquidityPurchase :: HNil => + val (inputInfo, localCommit1) = localCommit match { + case Left((inputInfo, unsignedLocalCommit)) => (inputInfo, Left(unsignedLocalCommit)) + case Right((inputInfo, localCommit)) => (inputInfo, Right(localCommit)) + } + InteractiveTxSigningSession.WaitingForSigs(fundingParams, fundingTxIndex, fundingTx, inputInfo, localCommit1, remoteCommit, liquidityPurchase) + }.decodeOnly + + val waitingForSigsWithTxsCodec: Codec[InteractiveTxSigningSession.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" | optional(bool8, liquidityPurchaseCodec))) + .map { + case fundingParams :: fundingTxIndex :: fundingTx :: localCommit :: remoteCommit :: liquidityPurchase :: HNil => + val (inputInfo, localCommit1) = localCommit match { + case Left((inputInfo, unsignedLocalCommit)) => (inputInfo, Left(unsignedLocalCommit)) + case Right((inputInfo, localCommit)) => (inputInfo, Right(localCommit)) + } + InteractiveTxSigningSession.WaitingForSigs(fundingParams, fundingTxIndex, fundingTx, inputInfo, localCommit1, remoteCommit, liquidityPurchase) + }.decodeOnly val waitingForSigsCodec: Codec[InteractiveTxSigningSession.WaitingForSigs] = ( ("fundingParams" | fundingParamsCodec) :: ("fundingTxIndex" | uint32) :: ("fundingTx" | partiallySignedSharedTransactionCodec) :: + ("commitInput" | inputInfoCodec) :: ("localCommit" | either(bool8, unsignedLocalCommitCodec, localCommitCodec(commitmentSpecCodec))) :: ("remoteCommit" | remoteCommitCodec(commitmentSpecCodec)) :: ("liquidityPurchase" | optional(bool8, liquidityPurchaseCodec))).as[InteractiveTxSigningSession.WaitingForSigs] - (waitingForSigsWithoutLiquidityPurchaseCodec, waitingForSigsCodec) + (waitingForSigsWithoutLiquidityPurchaseCodec, waitingForSigsWithTxsCodec, waitingForSigsCodec) } val dualFundingStatusCodec: Codec[DualFundingStatus] = discriminated[DualFundingStatus].by(uint8) .\(0x01) { case status: DualFundingStatus if !status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs] => DualFundingStatus.WaitingForConfirmations }(provide(DualFundingStatus.WaitingForConfirmations)) - .\(0x03) { case status: DualFundingStatus.RbfWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[DualFundingStatus.RbfWaitingForSigs]) + .\(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]) val spliceStatusCodec: Codec[SpliceStatus] = discriminated[SpliceStatus].by(uint8) .\(0x01) { case status: SpliceStatus if !status.isInstanceOf[SpliceStatus.SpliceWaitingForSigs] => SpliceStatus.NoSplice }(provide(SpliceStatus.NoSplice)) - .\(0x03) { case status: SpliceStatus.SpliceWaitingForSigs => status }(interactiveTxWaitingForSigsCodec.as[channel.SpliceStatus.SpliceWaitingForSigs]) + .\(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]) private val shortids: Codec[ChannelTypes4.ShortIds] = ( @@ -725,6 +838,14 @@ private[channel] object ChannelCodecs4 { ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_13_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( + ("channelParams" | paramsCodec) :: + ("secondRemotePerCommitmentPoint" | publicKey) :: + ("localPushAmount" | millisatoshi) :: + ("remotePushAmount" | millisatoshi) :: + ("status" | interactiveTxWaitingForSigsWithTxsCodec) :: + ("remoteChannelData_opt" | optional(bool8, varsizebinarydata))).as[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] + + val DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_1c_Codec: Codec[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED] = ( ("channelParams" | paramsCodec) :: ("secondRemotePerCommitmentPoint" | publicKey) :: ("localPushAmount" | millisatoshi) :: @@ -843,6 +964,14 @@ private[channel] object ChannelCodecs4 { ("remoteOnly_opt" | optional(bool8, closingTxCodec))).as[ClosingTxs] val DATA_NEGOTIATING_SIMPLE_17_Codec: Codec[DATA_NEGOTIATING_SIMPLE] = ( + ("commitments" | commitmentsCodecWithLocalTxs) :: + ("lastClosingFeerate" | feeratePerKw) :: + ("localScriptPubKey" | varsizebinarydata) :: + ("remoteScriptPubKey" | varsizebinarydata) :: + ("proposedClosingTxs" | listOfN(uint16, closingTxsCodec)) :: + ("publishedClosingTxs" | listOfN(uint16, closingTxCodec))).as[DATA_NEGOTIATING_SIMPLE] + + val DATA_NEGOTIATING_SIMPLE_1d_Codec: Codec[DATA_NEGOTIATING_SIMPLE] = ( ("commitments" | commitmentsCodec) :: ("lastClosingFeerate" | feeratePerKw) :: ("localScriptPubKey" | varsizebinarydata) :: @@ -856,11 +985,11 @@ private[channel] object ChannelCodecs4 { ("finalScriptPubKey" | lengthDelimited(bytes)) :: ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: - ("localCommitPublished" | optional(bool8, legacyLocalCommitPublishedCodec)) :: - ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("revokedCommitPublished" | listOfN(uint16, legacyRevokedCommitPublishedCodec))).as[DATA_CLOSING].map(closing => ChannelTypes3.fillForceCloseTxData(closing)).decodeOnly + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec_07)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] val DATA_CLOSING_11_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: @@ -868,13 +997,25 @@ private[channel] object ChannelCodecs4 { ("finalScriptPubKey" | lengthDelimited(bytes)) :: ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: - ("localCommitPublished" | optional(bool8, legacyLocalCommitPublishedCodec)) :: - ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec)) :: - ("revokedCommitPublished" | listOfN(uint16, legacyRevokedCommitPublishedCodec))).as[DATA_CLOSING].map(closing => ChannelTypes3.fillForceCloseTxData(closing)).decodeOnly + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec_07)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_07))).as[DATA_CLOSING] val DATA_CLOSING_1a_Codec: Codec[DATA_CLOSING] = ( + ("commitments" | versionedCommitmentsCodec) :: + ("waitingSince" | blockHeight) :: + ("finalScriptPubKey" | lengthDelimited(bytes)) :: + ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: + ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: + ("localCommitPublished" | optional(bool8, localCommitPublishedCodec_1a)) :: + ("remoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("nextRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("futureRemoteCommitPublished" | optional(bool8, remoteCommitPublishedCodec_07)) :: + ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec_1a))).as[DATA_CLOSING] + + val DATA_CLOSING_1b_Codec: Codec[DATA_CLOSING] = ( ("commitments" | versionedCommitmentsCodec) :: ("waitingSince" | blockHeight) :: ("finalScriptPubKey" | lengthDelimited(bytes)) :: @@ -897,6 +1038,9 @@ private[channel] object ChannelCodecs4 { // Order matters! val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) + .typecase(0x1d, Codecs.DATA_NEGOTIATING_SIMPLE_1d_Codec) + .typecase(0x1c, Codecs.DATA_WAIT_FOR_DUAL_FUNDING_SIGNED_1c_Codec) + .typecase(0x1b, Codecs.DATA_CLOSING_1b_Codec) .typecase(0x1a, Codecs.DATA_CLOSING_1a_Codec) .typecase(0x19, Codecs.DATA_SHUTDOWN_19_Codec) .typecase(0x18, Codecs.DATA_NORMAL_18_Codec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 3018c02032..ea9d823b16 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -16,12 +16,14 @@ package fr.acinq.eclair.wire.protocol +import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Satoshi, Transaction, TxHash, TxId} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ShortIdAliases} import fr.acinq.eclair.crypto.Mac32 import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, UnspecifiedShortChannelId} +import fr.acinq.secp256k1.Secp256k1 import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -156,6 +158,11 @@ object CommonCodecs { val publicKey: Codec[PublicKey] = catchAllCodec(bytes(33).xmap(bin => PublicKey(bin), pub => pub.value)) + val publicNonce: Codec[IndividualNonce] = Codec[IndividualNonce]( + (pub: IndividualNonce) => bytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE).encode(ByteVector.view(pub.toByteArray)), + (wire: BitVector) => bytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE).decode(wire).map(_.map(b => new IndividualNonce(b.toArray))) + ) + val rgb: Codec[Color] = bytes(3).xmap(buf => Color(buf(0), buf(1), buf(2)), t => ByteVector(t.r, t.g, t.b)) val txCodec: Codec[Transaction] = bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)) 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 0021f36d05..54f68dee87 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 @@ -85,14 +85,15 @@ "toLocal" : 204739729, "toRemote" : 16572475271 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4", - "tx" : "020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620" - }, - "remoteSig" : "4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d" + "txId" : "e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4", + "input" : { + "outPoint" : "3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1", + "amountSatoshis" : 16777215 + }, + "remoteSig" : { + "sig" : "4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 7779, @@ -102,7 +103,7 @@ "toLocal" : 16572475271, "toRemote" : 204739729 }, - "txid" : "ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be", + "txId" : "ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be", "remotePerCommitmentPoint" : "03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35" } } ], 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 d5fe938497..56a91bdd4a 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 @@ -85,14 +85,15 @@ "toLocal" : 1343316620, "toRemote" : 13656683380 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43", - "tx" : "02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320" - }, - "remoteSig" : "bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f" + "txId" : "65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43", + "input" : { + "outPoint" : "115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0", + "amountSatoshis" : 15000000 + }, + "remoteSig" : { + "sig" : "bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 20024, @@ -102,7 +103,7 @@ "toLocal" : 13656683380, "toRemote" : 1343316620 }, - "txid" : "919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49", + "txId" : "919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49", "remotePerCommitmentPoint" : "02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd" } } ], 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 b723107096..e552713d4a 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 @@ -99,14 +99,15 @@ "toLocal" : 15000000000, "toRemote" : 0 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f", - "tx" : "02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20" - }, - "remoteSig" : "871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b" + "txId" : "fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f", + "input" : { + "outPoint" : "1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0", + "amountSatoshis" : 15000000 + }, + "remoteSig" : { + "sig" : "871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 4, @@ -116,7 +117,7 @@ "toLocal" : 0, "toRemote" : 15000000000 }, - "txid" : "b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8", + "txId" : "b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8", "remotePerCommitmentPoint" : "02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a" } } ], 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 677a22cfdf..aaac6823b9 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 @@ -93,14 +93,15 @@ "toLocal" : 800000000, "toRemote" : 200000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "c6fe6bc0a5a9c149a03a907d2351714aa27fc98a485e981343cea08a1904ee26", - "tx" : "0200000001e917adc681383fe00f779c6144a1bd91135ba2c9862ad1bc5aa8a14d37bae3f40000000000fd11418002400d0300000000001600142276cff9d96f4696d6e504568db62088428706e0b8180c0000000000220020ad1b593fc0780225407ba65c612b974b4b66610e129f21064cb8ada0ffe4c8d2b7262120" - }, - "remoteSig" : "1ea9cffd2af82f6c14251bd59ffa3e876178c7b263f16d12790c33fc093eaed053dd9e0d49f79558aeb3c2fe7fe84f95a91eecbea2679fb65916ad9632df07b4" + "txId" : "c6fe6bc0a5a9c149a03a907d2351714aa27fc98a485e981343cea08a1904ee26", + "input" : { + "outPoint" : "f4e3ba374da1a85abcd12a86c9a25b1391bda144619c770fe03f3881c6ad17e9:0", + "amountSatoshis" : 1000000 + }, + "remoteSig" : { + "sig" : "1ea9cffd2af82f6c14251bd59ffa3e876178c7b263f16d12790c33fc093eaed053dd9e0d49f79558aeb3c2fe7fe84f95a91eecbea2679fb65916ad9632df07b4" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -110,7 +111,7 @@ "toLocal" : 200000000, "toRemote" : 800000000 }, - "txid" : "10e4205393672b61bbde4261441a2cf8d08ae50fdb813df8efaac6c6090ef290", + "txId" : "10e4205393672b61bbde4261441a2cf8d08ae50fdb813df8efaac6c6090ef290", "remotePerCommitmentPoint" : "032a992c123095216f7937a8b0baf442211eeb57942d586854a61a0dc6b01ca6ee" } } ], 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 bf125aac53..19dc177430 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 @@ -94,14 +94,15 @@ "toLocal" : 800000000, "toRemote" : 200000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "a591186503570022767b96fc4e02448e2c2d95f9e83f06c526f810886b02dedd", - "tx" : "020000000183a6dea8adf975b60df17738937034a2f96cb8b784da01e2934e9e172244317d0000000000fc10bb8002400d03000000000016001434b58af5c6bf8472898ef7ed44f65bce2e4a6101b8180c00000000002200206bf6eeedf2e93dee452c92c0c1e2421cc064bf8f70b432c6e36459215576d24decab0320" - }, - "remoteSig" : "8ccca40bbf6c859ceb2d5288153801426e19f8b119c2d1d4ba6d82511c7816aa0eaa8e41eafb179818d1ba7b0cfdce2da5fa002921df00a1fb4e1458d0b9042c" + "txId" : "a591186503570022767b96fc4e02448e2c2d95f9e83f06c526f810886b02dedd", + "input" : { + "outPoint" : "7d314422179e4e93e201da84b7b86cf9a23470933877f10db675f9ada8dea683:0", + "amountSatoshis" : 1000000 + }, + "remoteSig" : { + "sig" : "8ccca40bbf6c859ceb2d5288153801426e19f8b119c2d1d4ba6d82511c7816aa0eaa8e41eafb179818d1ba7b0cfdce2da5fa002921df00a1fb4e1458d0b9042c" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -111,7 +112,7 @@ "toLocal" : 200000000, "toRemote" : 800000000 }, - "txid" : "839b92423077adb38eeaa118dd6ba0e6daf8c1e0add666ddd18f57b517a2d750", + "txId" : "839b92423077adb38eeaa118dd6ba0e6daf8c1e0add666ddd18f57b517a2d750", "remotePerCommitmentPoint" : "0324b50221ad635b97f597802fbe5b2d6414fdf41f224ac1869d3772314e9fbfa5" } } ], 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 702dd3c5b5..78c97bb722 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 @@ -100,14 +100,15 @@ "toLocal" : 1000000000, "toRemote" : 500000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "b543f86a2a9a06f80b7339f9aaa169a779a7754d3523c28845d10dad9b9d7bc9", - "tx" : "0200000001db1c7ef42a989448f5599588b8a3213df36f9ed732a33043a45cab7773274374000000000058951280044a010000000000002200203a52179d307a973d00b2b102a0df754258ba7f0d1011a2a5d9e2844004b76d8a4a01000000000000220020a8b562e803e0523f85ff90ff8f90571a03c016bb61f387016d16db99f539c54e20a107000000000022002084bbfa5dee3d11a165c2ec7c0ce8b66e055fd7b6d90be6f0162dd4887cc25226b2340f00000000002200207fafaeecce02b12fad250d513a7493fc21bc9f4b82c7bbc115de3038cf984f933e9c0820" - }, - "remoteSig" : "5d65b42b0a8f05ca2793360a6b34bd5c1b9d960cf62c300c0e493f9cd83d2ca41f46a663d4fc11891ef268c0200b8fea8fb454d9055d35c2d6f5ba0b14c78b2a" + "txId" : "b543f86a2a9a06f80b7339f9aaa169a779a7754d3523c28845d10dad9b9d7bc9", + "input" : { + "outPoint" : "7443277377ab5ca44330a332d79e6ff33d21a3b8889559f54894982af47e1cdb:0", + "amountSatoshis" : 1500000 + }, + "remoteSig" : { + "sig" : "5d65b42b0a8f05ca2793360a6b34bd5c1b9d960cf62c300c0e493f9cd83d2ca41f46a663d4fc11891ef268c0200b8fea8fb454d9055d35c2d6f5ba0b14c78b2a" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -117,7 +118,7 @@ "toLocal" : 500000000, "toRemote" : 1000000000 }, - "txid" : "990138002f6fc36ae3ea48b88e2302a80eb925c33668e59638a82840f1e633ad", + "txId" : "990138002f6fc36ae3ea48b88e2302a80eb925c33668e59638a82840f1e633ad", "remotePerCommitmentPoint" : "037d0b91e7bf58eec2eddf033d457b17140a341533808a346c869ada9ecea0cec0" } } ], 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 7e8e183d49..bca747bcc4 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 @@ -106,14 +106,15 @@ "toLocal" : 200000000, "toRemote" : 800000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", - "tx" : "02000000017d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d000000000036a2ab8002400d0300000000002200207d4136c71e1c702d6e56ea300e1a56298b70fc8e96d67e72cfab3a27d39c9bf2b8180c0000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3ac678920" - }, - "remoteSig" : "02b8ce0a760ae6052a0d6c5770ae626ae71ea1b64853f97bb2c784839264054c64dc8717789f804342cbd9c5a364b32f4cbd9e9a4f91f48127a58f95014f6663" + "txId" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", + "input" : { + "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "amountSatoshis" : 1000000 + }, + "remoteSig" : { + "sig" : "02b8ce0a760ae6052a0d6c5770ae626ae71ea1b64853f97bb2c784839264054c64dc8717789f804342cbd9c5a364b32f4cbd9e9a4f91f48127a58f95014f6663" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -123,7 +124,7 @@ "toLocal" : 800000000, "toRemote" : 200000000 }, - "txid" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", + "txId" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", "remotePerCommitmentPoint" : "02e813fa8f4480fcbfa92d65037fe2d5d99cb72d6987381cf253d13f071a45f049" } } ], 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 c190f0a5be..bc335c7da4 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 @@ -106,14 +106,15 @@ "toLocal" : 800000000, "toRemote" : 200000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", - "tx" : "02000000017d975ecb75e1497076150f745b83dace95a189eecb6172c20a9a4fe0f91b8d1d000000000036a2ab8002400d030000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3b8180c000000000022002029b18fc4cf2f2b4634802a1b950d9e27e93b9be9ce638351143432fd0db09163ac678920" - }, - "remoteSig" : "8d3c57514dd1c01073b3d3704eddcfd8ca6ac3bb58f145f2c2dd921b77f238e842dce706a711b69b250e9ec3b827a9d9e144720a41fadce820bb18280667bffd" + "txId" : "ad28d946d13d331539d08da9d131311d1bec7308ddf072a59b07b2dc22779c3f", + "input" : { + "outPoint" : "1d8d1bf9e04f9a0ac27261cbee89a195ceda835b740f15767049e175cb5e977d:0", + "amountSatoshis" : 1000000 + }, + "remoteSig" : { + "sig" : "8d3c57514dd1c01073b3d3704eddcfd8ca6ac3bb58f145f2c2dd921b77f238e842dce706a711b69b250e9ec3b827a9d9e144720a41fadce820bb18280667bffd" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -123,7 +124,7 @@ "toLocal" : 200000000, "toRemote" : 800000000 }, - "txid" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", + "txId" : "d873d760728d9cf12d61c43360c383ab83799c9153f38c6012d9d6f6e6026df1", "remotePerCommitmentPoint" : "026b84590050c242d572dd2a45b873ce1e108303e1a54a6492e641f3ec1a70a7aa" } } ], 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 8ef01bebf6..1c8cbee034 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 @@ -108,14 +108,15 @@ "toLocal" : 500000000, "toRemote" : 1000000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", - "tx" : "0200000001934351ffe48993f070a665d6e22a2c023b567ccc2f73489213cfeb6c9a2c05e000000000001a741d80044a010000000000002200209698cbfb709ad73804217ae67e86ea857e8c1b317c82e7740670eb6def38ea334a01000000000000220020d468d18ef4bd63800e3e59bbb204a2921dc75cec9786e363ba2d33d467ae98e820a10700000000002200200e84b31c024b1498353109b6127546c1c1999edf47edb4c772c2108edeb524b4b2340f0000000000220020c38ddef3ceb25118b96983889833cbecd3fdeaf190083eef04ee451b70b47e04a71aae20" - }, - "remoteSig" : "c70d3fc3e720cc4fd1a2e07b97d454392ecfbe27c0ef2eed401538466924f6c24accb834eed784536fe5d30656eb8308a8b0f48bdba2ccc9812ce49a12b9dd80" + "txId" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", + "input" : { + "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "amountSatoshis" : 1500000 + }, + "remoteSig" : { + "sig" : "c70d3fc3e720cc4fd1a2e07b97d454392ecfbe27c0ef2eed401538466924f6c24accb834eed784536fe5d30656eb8308a8b0f48bdba2ccc9812ce49a12b9dd80" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -125,7 +126,7 @@ "toLocal" : 1000000000, "toRemote" : 500000000 }, - "txid" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", + "txId" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", "remotePerCommitmentPoint" : "02df0aedf5ee7eba9dbe640dfd3b2da6e108cf52568e4de3e492ed4366355a33c0" } } ], 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 1d8b1d5a7e..9f22731a67 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 @@ -108,14 +108,15 @@ "toLocal" : 1000000000, "toRemote" : 500000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", - "tx" : "0200000001934351ffe48993f070a665d6e22a2c023b567ccc2f73489213cfeb6c9a2c05e000000000001a741d80044a010000000000002200209698cbfb709ad73804217ae67e86ea857e8c1b317c82e7740670eb6def38ea334a01000000000000220020d468d18ef4bd63800e3e59bbb204a2921dc75cec9786e363ba2d33d467ae98e820a1070000000000220020fbc40c7329e9ed94bb1a94bed8d66eba59e96aba45dc5d59150b6ac6ab24436ab2340f00000000002200201e1e972764d9ad2836c50f22bcb80b031dd989239b89db8d3c62c83e14ad88c0a71aae20" - }, - "remoteSig" : "353c51755a0c852a4c5dd9e2d33d4a9c0934e9a07a43da89f061a3b4499f374d1857dfa513ed714287baf4ac3f4375f06bf435577962f121182ad9ce4b19f549" + "txId" : "c98fac755c41c1688a55f820a3ac9c4c8b2adde1d6687b2a9d843b364211965f", + "input" : { + "outPoint" : "e0052c9a6cebcf139248732fcc7c563b022c2ae2d665a670f09389e4ff514393:0", + "amountSatoshis" : 1500000 + }, + "remoteSig" : { + "sig" : "353c51755a0c852a4c5dd9e2d33d4a9c0934e9a07a43da89f061a3b4499f374d1857dfa513ed714287baf4ac3f4375f06bf435577962f121182ad9ce4b19f549" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -125,7 +126,7 @@ "toLocal" : 500000000, "toRemote" : 1000000000 }, - "txid" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", + "txId" : "800a5eab3f585d54f19d6e574b611d94fa7414af651e17104310bf004ccd5cd6", "remotePerCommitmentPoint" : "03a59c87106cab1b24df7f9a3843a728a45bca74680f65118ca6c5a3d45c1934e6" } } ], 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 40bc72d116..978c58072e 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 @@ -106,14 +106,15 @@ "toLocal" : 800000000, "toRemote" : 200000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "41418383164935b0753fa1f9b128dff2f1b3608e6b50e8beea4525edf7c3959d", - "tx" : "0200000001c380aa11700db0a6d797dfd0be8aecfadad9397e4975f0fa8c9d10db71feac38000000000036a2ab8002400d030000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3b8180c000000000022002010a0e90d648e62c6aa0d9b9883ee391449f1a1ce28926eb26c3bbdf76c25f631ac678920" - }, - "remoteSig" : "62de565629126afb02a76e67ddf18dfc883cf93bfeb7c807826dd6c8d1bd1f4d55528717f0d2303dc254b4512e32ce82dc7947d5d8e4d38647c2407fc53b874a" + "txId" : "41418383164935b0753fa1f9b128dff2f1b3608e6b50e8beea4525edf7c3959d", + "input" : { + "outPoint" : "38acfe71db109d8cfaf075497e39d9dafaec8abed0df97d7a6b00d7011aa80c3:0", + "amountSatoshis" : 1000000 + }, + "remoteSig" : { + "sig" : "62de565629126afb02a76e67ddf18dfc883cf93bfeb7c807826dd6c8d1bd1f4d55528717f0d2303dc254b4512e32ce82dc7947d5d8e4d38647c2407fc53b874a" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -123,7 +124,7 @@ "toLocal" : 200000000, "toRemote" : 800000000 }, - "txid" : "287a0627974d69a85199233f5cb8e86e7d04d8afdbc0faa4e56587833838c5af", + "txId" : "287a0627974d69a85199233f5cb8e86e7d04d8afdbc0faa4e56587833838c5af", "remotePerCommitmentPoint" : "03293b6fcd6474a6c8dff63eeed1ca704c15d5bb80177025c2c45a4f995723393d" } } ], 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 29fb6d0eb3..2239df8f75 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 @@ -105,14 +105,15 @@ "toLocal" : 700000000, "toRemote" : 1800000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "ec1fdbdffd12f20851f33319fb3ab6ecb8528a34a9a2c76e53aaf91cf4a18da4", - "tx" : "020000000101310d33721f5dab9805cd15529a750a0e1d335e986f165306e9ac782b3883880000000000310038800260ae0a0000000000220020a1ca24b5ac3c96e066fa7466d34505e7e62bfe75017116d988328f97f4358160f85a1b0000000000160014b8a6877e8036939b79fcebe6c478d4bb234656b4a0f85620" - }, - "remoteSig" : "52ea4ba9421f4b4ad8a9a1e3041a940b13751504faf7d79338d73bbc1187fb551d9340de809849d6d7dcd745b813e01c07809cb8eacc57a211990b0c021d4c45" + "txId" : "ec1fdbdffd12f20851f33319fb3ab6ecb8528a34a9a2c76e53aaf91cf4a18da4", + "input" : { + "outPoint" : "8883382b78ace90653166f985e331d0e0a759a5215cd0598ab5d1f72330d3101:0", + "amountSatoshis" : 2500000 + }, + "remoteSig" : { + "sig" : "52ea4ba9421f4b4ad8a9a1e3041a940b13751504faf7d79338d73bbc1187fb551d9340de809849d6d7dcd745b813e01c07809cb8eacc57a211990b0c021d4c45" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -122,7 +123,7 @@ "toLocal" : 1800000000, "toRemote" : 700000000 }, - "txid" : "ac57fb7fa3b5493412ad83d7d056f269f54133e69026306d34a9ede545ef145c", + "txId" : "ac57fb7fa3b5493412ad83d7d056f269f54133e69026306d34a9ede545ef145c", "remotePerCommitmentPoint" : "0362028d59fc5fd2598f1fad1df3933b64367fd6a16ba88a90d31d396b588eac2a" } }, { @@ -147,14 +148,15 @@ "toLocal" : 700000000, "toRemote" : 1300000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "27f7218158a68f5bb23c9d8a280f85bfca32d7636529e1db2ba23172241161f4", - "tx" : "020000000105303d3f65cce13af3e5908692cefadae10b3f1205ac9c23eb381088f56c55910000000000310038800260ae0a0000000000220020a1ca24b5ac3c96e066fa7466d34505e7e62bfe75017116d988328f97f4358160d8b9130000000000160014b8a6877e8036939b79fcebe6c478d4bb234656b4a0f85620" - }, - "remoteSig" : "32a246840dd67eb72b59fc170303c508c77ee8666ac6be36abced02bf79dbfbe114cdd4e56600ae6e3573e6e10d3517ddc857fbd89c00e2cbcdf5186c6592f26" + "txId" : "27f7218158a68f5bb23c9d8a280f85bfca32d7636529e1db2ba23172241161f4", + "input" : { + "outPoint" : "91556cf5881038eb239cac05123f0be1daface928690e5f33ae1cc653f3d3005:0", + "amountSatoshis" : 2000000 }, - "htlcTxsAndRemoteSigs" : [ ] + "remoteSig" : { + "sig" : "32a246840dd67eb72b59fc170303c508c77ee8666ac6be36abced02bf79dbfbe114cdd4e56600ae6e3573e6e10d3517ddc857fbd89c00e2cbcdf5186c6592f26" + }, + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -164,7 +166,7 @@ "toLocal" : 1300000000, "toRemote" : 700000000 }, - "txid" : "1f1392e4cee2d3fc5f4fed4afed2b962ea8df1a802dc9274043f337e8767838e", + "txId" : "1f1392e4cee2d3fc5f4fed4afed2b962ea8df1a802dc9274043f337e8767838e", "remotePerCommitmentPoint" : "0362028d59fc5fd2598f1fad1df3933b64367fd6a16ba88a90d31d396b588eac2a" } }, { @@ -189,14 +191,15 @@ "toLocal" : 700000000, "toRemote" : 800000000 }, - "commitTxAndRemoteSig" : { - "commitTx" : { - "txid" : "381ee5a54f127b776114b197b2695c39f2d9b81cb967628a32e12766cdf8f1b5", - "tx" : "02000000015f212d2dd5363ebcf4ff99b4e490fef6436b983182d65fd3129d9a7ccf9812420200000000310038800260ae0a0000000000220020a1ca24b5ac3c96e066fa7466d34505e7e62bfe75017116d988328f97f4358160b8180c0000000000160014b8a6877e8036939b79fcebe6c478d4bb234656b4a0f85620" - }, - "remoteSig" : "47f525be7304ef4fa4d868eccfa2389fe1d8f058d08305b0f1792cebbd14ec7b6b43f98d0f3aa22e60d7ef024785f3576d0c1d74b666c7c5d2da88c7e7f7bd18" + "txId" : "381ee5a54f127b776114b197b2695c39f2d9b81cb967628a32e12766cdf8f1b5", + "input" : { + "outPoint" : "421298cf7c9a9d12d35fd68231986b43f6fe90e4b499fff4bc3e36d52d2d215f:2", + "amountSatoshis" : 1500000 + }, + "remoteSig" : { + "sig" : "47f525be7304ef4fa4d868eccfa2389fe1d8f058d08305b0f1792cebbd14ec7b6b43f98d0f3aa22e60d7ef024785f3576d0c1d74b666c7c5d2da88c7e7f7bd18" }, - "htlcTxsAndRemoteSigs" : [ ] + "htlcRemoteSigs" : [ ] }, "remoteCommit" : { "index" : 0, @@ -206,7 +209,7 @@ "toLocal" : 800000000, "toRemote" : 700000000 }, - "txid" : "ea2430e6f9d234b9ca79d5edbdd0edbe429aa31026ad79ecc207299a62151d33", + "txId" : "ea2430e6f9d234b9ca79d5edbdd0edbe429aa31026ad79ecc207299a62151d33", "remotePerCommitmentPoint" : "0362028d59fc5fd2598f1fad1df3933b64367fd6a16ba88a90d31d396b588eac2a" } } ], 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 207e2f69bf..0191f68402 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 @@ -10,7 +10,7 @@ import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsT import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._ import fr.acinq.eclair.db.pg.PgUtils.using import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcSuccessTx, ClaimHtlcTimeoutTx, HtlcSuccessTx} +import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcSuccessTx, ClaimHtlcTimeoutTx} import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec import fr.acinq.eclair.wire.protocol.{CommitSig, RevokeAndAck} import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} @@ -88,7 +88,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with fulfillHtlc(htlcb.id, rb, alice, bob, alice2bob, bob2alice) // Bob publishes his current commit tx. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) assert(bobCommitTx.txOut.size == 8) // two anchor outputs, two main outputs and 4 pending htlcs alice ! WatchFundingSpentTriggered(bobCommitTx) // In response to that, alice publishes her claim txs. @@ -136,7 +136,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[RevokeAndAck] // Bob publishes his next commit tx. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.last.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) assert(bobCommitTx.txOut.size == 7) // two anchor outputs, two main outputs and 3 pending htlcs alice ! WatchFundingSpentTriggered(bobCommitTx) // In response to that, alice publishes her claim txs @@ -177,7 +177,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchTxConfirmedTriggered(BlockHeight(750_000), 1, localCommitPublished.commitTx) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.exists(_.isConfirmed)) val mainTx = localClosingTxs.mainTx_opt.get - val mainBalance = localCommitPublished.claimMainDelayedOutputTx.map(_.amountIn).get + val mainBalance = localCommitPublished.localOutput_opt.map(o => localCommitPublished.commitTx.txOut(o.index.toInt).amount).get // We already have an off-chain balance from other channels. val balance = OffChainBalance( @@ -198,13 +198,14 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) htlcDelayedTx }) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.exists(_.claimHtlcDelayedTxs.size == 2)) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.exists(_.htlcDelayedOutputs.size == 2)) assert(balance.addChannelBalance(alice.stateData.asInstanceOf[DATA_CLOSING]) == expected1) // Bob claims the remaining incoming HTLC using his HTLC-timeout transaction: we remove it from our balance. val (remoteCommitPublished, remoteClosingTxs) = remoteClose(localCommitPublished.commitTx, bob, bob2blockchain, htlcTimeoutCount = 3) - val bobHtlcTimeoutTx = remoteCommitPublished.claimHtlcTxs - .collectFirst { case (outpoint, Some(txInfo: ClaimHtlcTimeoutTx)) if txInfo.htlcId == htlcb1.id => outpoint } + val bobHtlcTimeoutTx = remoteCommitPublished.htlcs + // Note that this is Alice's commit tx, so incoming and outgoing are inverted. + .collectFirst { case (outpoint, IncomingHtlcId(htlcId)) if htlcId == htlcb1.id => outpoint } .flatMap(outpoint => remoteClosingTxs.htlcTimeoutTxs.find(_.txIn.head.outPoint == outpoint)) .get alice ! WatchTxConfirmedTriggered(BlockHeight(760_010), 0, bobHtlcTimeoutTx) 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 e6208252d4..f1bf165a18 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 @@ -25,7 +25,7 @@ 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.transactions.Transactions.{CommitTx, DefaultCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions} import fr.acinq.eclair.wire.protocol._ import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -496,7 +496,7 @@ object CommitmentsSpec { 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), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), IndividualSignature(ByteVector64.Zeroes)), Nil) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), commitmentInput, 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) @@ -521,7 +521,7 @@ object CommitmentsSpec { 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), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), IndividualSignature(ByteVector64.Zeroes)), Nil) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), commitmentInput, 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) 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 23e6baabd6..f24b0b075f 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 @@ -29,7 +29,7 @@ import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsT import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong, randomKey} +import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong, randomKey} import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits.{ByteVector, HexStringSyntax} @@ -95,32 +95,36 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat awaitCond(alice.stateName == CLOSING) val lcp = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get - lcp.claimAnchorTx_opt.foreach(_ => alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor]) - lcp.claimMainDelayedOutputTx.foreach(_ => alice2blockchain.expectFinalTxPublished("local-main-delayed")) + lcp.anchorOutput_opt.foreach(_ => alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor]) + lcp.localOutput_opt.foreach(_ => alice2blockchain.expectFinalTxPublished("local-main-delayed")) // Alice is missing the preimage for 2 of the HTLCs she received. - assert(lcp.htlcTxs.size == 6) + assert(lcp.htlcOutputs.size == 6) val htlcTxs = (0 until 4).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx]).map(_.tx).collect { case tx: ReplaceableHtlc => tx.sign(Map.empty) } alice2blockchain.expectWatchTxConfirmed(commitTx.tx.txid) val htlcTimeoutTxs = htlcTxs.map(_.txInfo).collect { case tx: HtlcTimeoutTx => tx } assert(htlcTimeoutTxs.length == 3) + assert(lcp.htlcs.collect { case (_, OutgoingHtlcId(htlcId)) => htlcId }.toSet == htlcTimeoutTxs.map(_.htlcId).toSet) val htlcSuccessTxs = htlcTxs.map(_.txInfo).collect { case tx: HtlcSuccessTx => tx } assert(htlcSuccessTxs.length == 1) + assert(lcp.htlcs.collect { case (_, IncomingHtlcId(htlcId)) => htlcId }.toSet.contains(htlcSuccessTxs.head.htlcId)) // Bob detects Alice's force-close. bob ! WatchFundingSpentTriggered(commitTx.tx) awaitCond(bob.stateName == CLOSING) val rcp = bob.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get - rcp.claimAnchorTx_opt.foreach(_ => bob2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor]) - rcp.claimMainOutputTx.foreach(_ => bob2blockchain.expectFinalTxPublished("remote-main-delayed")) + rcp.anchorOutput_opt.foreach(_ => bob2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor]) + rcp.localOutput_opt.foreach(_ => bob2blockchain.expectFinalTxPublished("remote-main-delayed")) // Bob is missing the preimage for 2 of the HTLCs she received. - assert(rcp.claimHtlcTxs.size == 6) + assert(rcp.htlcOutputs.size == 6) val claimHtlcTxs = (0 until 4).map(_ => bob2blockchain.expectMsgType[PublishReplaceableTx]) bob2blockchain.expectWatchTxConfirmed(commitTx.tx.txid) val claimHtlcTimeoutTxs = claimHtlcTxs.map(_.tx.txInfo).collect { case tx: ClaimHtlcTimeoutTx => tx } assert(claimHtlcTimeoutTxs.length == 3) + assert(rcp.htlcs.collect { case (_, IncomingHtlcId(htlcId)) => htlcId }.toSet == claimHtlcTimeoutTxs.map(_.htlcId).toSet) val claimHtlcSuccessTxs = claimHtlcTxs.map(_.tx.txInfo).collect { case tx: ClaimHtlcSuccessTx => tx } assert(claimHtlcSuccessTxs.length == 1) + assert(rcp.htlcs.collect { case (_, OutgoingHtlcId(htlcId)) => htlcId }.toSet.contains(claimHtlcSuccessTxs.head.htlcId)) Fixture(alice, Set(htlca1a, htlca1b, htlca2), htlcSuccessTxs, htlcTimeoutTxs, bob, Set(htlcb1a, htlcb1b, htlcb2), claimHtlcSuccessTxs, claimHtlcTimeoutTxs, probe) } @@ -128,19 +132,19 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat def findTimedOutHtlcs(f: Fixture): Unit = { import f._ - val dustLimit = alice.underlyingActor.nodeParams.channelConf.dustLimit - val localCommit = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.localCommit + val localKeys = alice.underlyingActor.channelKeys + val localCommitment = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest + val localCommit = localCommitment.localCommit val remoteKeys = bob.underlyingActor.channelKeys val remoteCommitment = bob.stateData.asInstanceOf[DATA_CLOSING].commitments.latest val remoteCommit = remoteCommitment.remoteCommit - val commitmentFormat = remoteCommitment.params.commitmentFormat // Claim-HTLC txs can be modified to pay more (or less) fees by changing the output amount. val bobClaimHtlcTimeoutTxsModifiedFees = bobClaimHtlcTimeoutTxs.map(tx => tx.modify(_.tx.txOut).setTo(Seq(tx.tx.txOut.head.copy(amount = 5000 sat)))) val bobClaimHtlcSuccessTxsModifiedFees = bobClaimHtlcSuccessTxs.map(tx => tx.modify(_.tx.txOut).setTo(Seq(tx.tx.txOut.head.copy(amount = 5000 sat)))) val aliceTimedOutHtlcs = aliceHtlcTimeoutTxs.map(htlcTimeout => { - val timedOutHtlcs = Closing.trimmedOrTimedOutHtlcs(commitmentFormat, localCommit, dustLimit, htlcTimeout.tx) + val timedOutHtlcs = Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, htlcTimeout.tx) assert(timedOutHtlcs.size == 1) timedOutHtlcs.head }) @@ -160,14 +164,14 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat }) assert(bobTimedOutHtlcs2.toSet == bobHtlcs) - aliceHtlcSuccessTxs.foreach(htlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(commitmentFormat, localCommit, dustLimit, htlcSuccess.tx).isEmpty)) + aliceHtlcSuccessTxs.foreach(htlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, htlcSuccess.tx).isEmpty)) aliceHtlcSuccessTxs.foreach(htlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(remoteKeys, remoteCommitment, remoteCommit, htlcSuccess.tx).isEmpty)) - bobClaimHtlcSuccessTxs.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(commitmentFormat, localCommit, dustLimit, claimHtlcSuccess.tx).isEmpty)) - bobClaimHtlcSuccessTxsModifiedFees.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(commitmentFormat, localCommit, dustLimit, claimHtlcSuccess.tx).isEmpty)) + bobClaimHtlcSuccessTxs.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, claimHtlcSuccess.tx).isEmpty)) + bobClaimHtlcSuccessTxsModifiedFees.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, claimHtlcSuccess.tx).isEmpty)) bobClaimHtlcSuccessTxs.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(remoteKeys, remoteCommitment, remoteCommit, claimHtlcSuccess.tx).isEmpty)) bobClaimHtlcSuccessTxsModifiedFees.foreach(claimHtlcSuccess => assert(Closing.trimmedOrTimedOutHtlcs(remoteKeys, remoteCommitment, remoteCommit, claimHtlcSuccess.tx).isEmpty)) aliceHtlcTimeoutTxs.foreach(htlcTimeout => assert(Closing.trimmedOrTimedOutHtlcs(remoteKeys, remoteCommitment, remoteCommit, htlcTimeout.tx).isEmpty)) - bobClaimHtlcTimeoutTxs.foreach(claimHtlcTimeout => assert(Closing.trimmedOrTimedOutHtlcs(commitmentFormat, localCommit, dustLimit, claimHtlcTimeout.tx).isEmpty)) + bobClaimHtlcTimeoutTxs.foreach(claimHtlcTimeout => assert(Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, claimHtlcTimeout.tx).isEmpty)) } test("find timed out htlcs (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { @@ -241,10 +245,10 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, tx3.tx, CltvExpiryDelta(720))), - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = Some(tx3.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = None, @@ -263,10 +267,10 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, tx3.tx, CltvExpiryDelta(720))), - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = Some(tx3.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map(tx2.input.outPoint -> tx2.tx) )), remoteCommitPublished = None, @@ -285,17 +289,17 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = None, - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx3.tx, - claimMainOutputTx = None, - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map.empty )), nextRemoteCommitPublished = None, @@ -313,17 +317,17 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = None, - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx3.tx, - claimMainOutputTx = None, - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map(tx3.input.outPoint -> tx3.tx) )), nextRemoteCommitPublished = None, @@ -343,24 +347,24 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = None, - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx3.tx, - claimMainOutputTx = None, - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map.empty )), nextRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = Some(tx5.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map(tx4.input.outPoint -> tx4.tx) )), futureRemoteCommitPublished = None, @@ -380,9 +384,9 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat nextRemoteCommitPublished = None, futureRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimRemoteDelayedOutputTx(tx5.input, tx5.tx)), - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = Some(tx5.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map.empty )), revokedCommitPublished = Nil) @@ -401,9 +405,9 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat nextRemoteCommitPublished = None, futureRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), - claimHtlcTxs = Map.empty, - claimAnchorTxs = Nil, + localOutput_opt = Some(tx5.input.outPoint), + anchorOutput_opt = None, + htlcs = Map.empty, irrevocablySpent = Map(tx4.input.outPoint -> tx4.tx) )), revokedCommitPublished = Nil) @@ -419,10 +423,10 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx1.tx, - claimMainDelayedOutputTx = None, - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = None, @@ -431,26 +435,26 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat revokedCommitPublished = RevokedCommitPublished( commitTx = tx2.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, tx3.tx)), - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = Some(tx3.input.outPoint), + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty ) :: RevokedCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = Some(tx5.input.outPoint), + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty ) :: RevokedCommitPublished( commitTx = tx6.tx, - claimMainOutputTx = None, - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = None, + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty ) :: Nil ) @@ -466,10 +470,10 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx1.tx, - claimMainDelayedOutputTx = None, - htlcTxs = Map.empty, - claimHtlcDelayedTxs = Nil, - claimAnchorTxs = Nil, + localOutput_opt = None, + anchorOutput_opt = None, + htlcs = Map.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty )), remoteCommitPublished = None, @@ -478,26 +482,26 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat revokedCommitPublished = RevokedCommitPublished( commitTx = tx2.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, tx3.tx)), - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = Some(tx3.input.outPoint), + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty ) :: RevokedCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = Some(tx5.input.outPoint), + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map(tx4.input.outPoint -> tx4.tx) ) :: RevokedCommitPublished( commitTx = tx6.tx, - claimMainOutputTx = None, - mainPenaltyTx = None, - htlcPenaltyTxs = Nil, - claimHtlcDelayedPenaltyTxs = Nil, + localOutput_opt = None, + remoteOutput_opt = None, + htlcOutputs = Set.empty, + htlcDelayedOutputs = Set.empty, irrevocablySpent = Map.empty ) :: Nil ) 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 b416b8fc78..4173045e22 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 @@ -188,8 +188,8 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w def closeChannelWithoutHtlcs(f: Fixture, overrideCommitTarget: BlockHeight): (PublishFinalTx, PublishReplaceableTx) = { import f._ - val signedCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.copy(tx = signedCommitTx) + val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest + val commitTx = CommitTx(commitment.commitInput, commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys)) probe.send(alice, CMD_FORCECLOSE(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FORCECLOSE]] @@ -197,7 +197,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val publishCommitTx = alice2blockchain.expectMsg(PublishFinalTx(commitTx, commitTx.fee, None)) // Forward the anchor tx to the publisher. val publishAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideCommitTarget)) - assert(publishAnchor.tx.commitTx == signedCommitTx) + assert(publishAnchor.tx.commitTx == commitTx.tx) assert(publishAnchor.tx.isInstanceOf[ReplaceableLocalCommitAnchor]) (publishCommitTx, publishAnchor) @@ -337,7 +337,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w bob2alice.expectMsgType[RevokeAndAck] bob2alice.expectMsgType[CommitSig] assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.nonEmpty) - val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txid + val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txId val nextRemoteCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) assert(nextRemoteCommit.txid == nextRemoteCommitTxId) @@ -485,7 +485,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w if (nextCommit) { assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.nonEmpty) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.map(_.commit.txid).contains(commitTx.txid)) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.map(_.commit.txId).contains(commitTx.txid)) } val targetFeerate = FeeratePerKw(3000 sat) @@ -1026,7 +1026,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w bob2alice.expectMsgType[RevokeAndAck] bob2alice.expectMsgType[CommitSig] assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.nonEmpty) - val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txid + val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txId // Force-close channel. probe.send(alice, CMD_FORCECLOSE(probe.ref)) @@ -1068,16 +1068,16 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w probe.expectMsgType[CommandSuccess[CMD_FULFILL_HTLC]] // Force-close channel and verify txs sent to watcher. - val signedCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) - val commitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.copy(tx = signedCommitTx) - assert(signedCommitTx.txOut.size == 6) + val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest + val commitTx = CommitTx(commitment.commitInput, commitment.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys)) + assert(commitTx.tx.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, commitTx.fee, None)) - wallet.publishTransaction(signedCommitTx).pipeTo(probe.ref) - probe.expectMsg(signedCommitTx.txid) + wallet.publishTransaction(commitTx.tx).pipeTo(probe.ref) + probe.expectMsg(commitTx.tx.txid) generateBlocks(1) val anchor = alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor] @@ -1086,11 +1086,11 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w assert(htlcSuccess.tx.isInstanceOf[ReplaceableHtlcSuccess]) val htlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) assert(htlcTimeout.tx.isInstanceOf[ReplaceableHtlcTimeout]) - alice2blockchain.expectWatchTxConfirmed(signedCommitTx.txid) + alice2blockchain.expectWatchTxConfirmed(commitTx.tx.txid) alice2blockchain.expectWatchOutputsSpent(Seq(main.input, anchor.txInfo.input.outPoint, htlcSuccess.input, htlcTimeout.input)) alice2blockchain.expectNoMessage(100 millis) - (signedCommitTx, htlcSuccess, htlcTimeout) + (commitTx.tx, htlcSuccess, htlcTimeout) } test("not enough funds to increase htlc tx feerate") { 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 ee76294cec..fb27dfb85a 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 @@ -559,13 +559,6 @@ trait ChannelStateTestsBase extends Assertions with Eventually { } def localClose(s: TestFSMRef[ChannelState, ChannelData, Channel], s2blockchain: TestProbe, htlcSuccessCount: Int = 0, htlcTimeoutCount: Int = 0): (LocalCommitPublished, PublishedForceCloseTxs) = { - // an error occurs and s publishes its commit tx - val localCommit = s.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - // check that we store the local txs without sigs - localCommit.commitTxAndRemoteSig.commitTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull)) - localCommit.htlcTxsAndRemoteSigs.foreach(_.htlcTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull))) - - val commitTx = localCommit.commitTxAndRemoteSig.commitTx.tx s ! Error(ByteVector32.Zeroes, "oops") eventually(assert(s.stateName == CLOSING)) val closingState = s.stateData.asInstanceOf[DATA_CLOSING] @@ -573,26 +566,26 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val localCommitPublished = closingState.localCommitPublished.get // It may be strictly greater if we're waiting for preimages for some of our HTLC-success txs, or if we're ignoring // HTLCs that where failed downstream or not relayed. - assert(localCommitPublished.htlcTxs.size >= htlcSuccessCount + htlcTimeoutCount) + assert(localCommitPublished.htlcs.values.collect { case i: IncomingHtlcId => i }.size >= htlcSuccessCount) + assert(localCommitPublished.htlcs.values.collect { case i: OutgoingHtlcId => i }.size == htlcTimeoutCount) - val publishedLocalCommitTx = s2blockchain.expectFinalTxPublished("commit-tx").tx - assert(publishedLocalCommitTx.txid == commitTx.txid) - assert(publishedLocalCommitTx.wtxid != commitTx.wtxid) + val commitTx = s2blockchain.expectFinalTxPublished("commit-tx").tx + assert(commitTx.txid == closingState.commitments.latest.localCommit.txId) val commitInput = closingState.commitments.latest.commitInput - Transaction.correctlySpends(publishedLocalCommitTx, Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(commitTx, Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val publishedAnchorTx_opt = closingState.commitments.params.commitmentFormat match { case DefaultCommitmentFormat => None case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(s2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor].txInfo.tx) } // if s has a main output in the commit tx (when it has a non-dust balance), it should be claimed - val publishedMainTx_opt = localCommitPublished.claimMainDelayedOutputTx.map(_ => s2blockchain.expectFinalTxPublished("local-main-delayed").tx) + val publishedMainTx_opt = localCommitPublished.localOutput_opt.map(_ => s2blockchain.expectFinalTxPublished("local-main-delayed").tx) val (publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) = closingState.commitments.params.commitmentFormat match { case Transactions.DefaultCommitmentFormat => // all htlcs success/timeout should be published as-is, we cannot RBF val publishedHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => val htlcTx = s2blockchain.expectMsgType[PublishFinalTx] assert(htlcTx.parentTx_opt.contains(commitTx.txid)) - assert(localCommitPublished.htlcTxs.contains(htlcTx.input)) + assert(localCommitPublished.htlcOutputs.contains(htlcTx.input)) assert(htlcTx.desc == "htlc-success" || htlcTx.desc == "htlc-timeout") htlcTx } @@ -603,8 +596,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { // all htlcs success/timeout should be published as replaceable txs val publishedHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => val htlcTx = s2blockchain.expectReplaceableTxPublished[ReplaceableHtlc] - assert(htlcTx.commitTx == publishedLocalCommitTx) - assert(localCommitPublished.htlcTxs.contains(htlcTx.txInfo.input.outPoint)) + assert(htlcTx.commitTx == commitTx) + assert(localCommitPublished.htlcOutputs.contains(htlcTx.txInfo.input.outPoint)) htlcTx } // the publisher actors will sign those transactions before broadcasting them @@ -614,17 +607,17 @@ trait ChannelStateTestsBase extends Assertions with Eventually { } assert(publishedHtlcSuccessTxs.size == htlcSuccessCount) assert(publishedHtlcTimeoutTxs.size == htlcTimeoutCount) - (publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(htlcTx => Transaction.correctlySpends(htlcTx, publishedLocalCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + (publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(htlcTx => Transaction.correctlySpends(htlcTx, commitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) // we're not claiming the outputs of htlc txs yet - assert(localCommitPublished.claimHtlcDelayedTxs.isEmpty) + assert(localCommitPublished.htlcDelayedOutputs.isEmpty) // we watch the confirmation of the commitment transaction s2blockchain.expectWatchTxConfirmed(commitTx.txid) // we watch outputs of the commitment tx that we want to claim - localCommitPublished.claimMainDelayedOutputTx.foreach(tx => s2blockchain.expectWatchOutputSpent(tx.input.outPoint)) - localCommitPublished.claimAnchorTx_opt.foreach(tx => s2blockchain.expectWatchOutputSpent(tx.input.outPoint)) - s2blockchain.expectWatchOutputsSpent(localCommitPublished.htlcTxs.keys.toSeq) + localCommitPublished.localOutput_opt.foreach(outpoint => s2blockchain.expectWatchOutputSpent(outpoint)) + localCommitPublished.anchorOutput_opt.foreach(outpoint => s2blockchain.expectWatchOutputSpent(outpoint)) + s2blockchain.expectWatchOutputsSpent(localCommitPublished.htlcOutputs.toSeq) s2blockchain.expectNoMessage(100 millis) // once our closing transactions are published, we watch for their confirmation @@ -648,8 +641,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually { assert(closingData.localCommitPublished.isEmpty) val remoteCommitPublished = remoteCommitPublished_opt.get // It may be strictly greater if we're waiting for preimages for some of our HTLC-success txs, or if we're ignoring - // HTLCs that where failed downstream or not relayed. - assert(remoteCommitPublished.claimHtlcTxs.size >= htlcSuccessCount + htlcTimeoutCount) + // HTLCs that where failed downstream or not relayed. Note that since this is the remote commit, IN/OUT are inverted. + assert(remoteCommitPublished.htlcs.values.collect { case i: OutgoingHtlcId => i }.size >= htlcSuccessCount) + assert(remoteCommitPublished.htlcs.values.collect { case i: IncomingHtlcId => i }.size == htlcTimeoutCount) // If anchor outputs is used, we use the anchor output to bump the fees if necessary. val publishedAnchorTx_opt = closingData.commitments.params.commitmentFormat match { @@ -657,13 +651,13 @@ trait ChannelStateTestsBase extends Assertions with Eventually { case Transactions.DefaultCommitmentFormat => None } // if s has a main output in the commit tx (when it has a non-dust balance), it should be claimed - val publishedMainTx_opt = remoteCommitPublished.claimMainOutputTx.map(_ => s2blockchain.expectFinalTxPublished("remote-main-delayed").tx) + val publishedMainTx_opt = remoteCommitPublished.localOutput_opt.map(_ => s2blockchain.expectFinalTxPublished("remote-main-delayed").tx) publishedMainTx_opt.foreach(tx => Transaction.correctlySpends(tx, rCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) // all htlcs success/timeout should be claimed val publishedClaimHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => val claimHtlcTx = s2blockchain.expectMsgType[PublishReplaceableTx] assert(claimHtlcTx.tx.commitTx == rCommitTx) - assert(remoteCommitPublished.claimHtlcTxs.contains(claimHtlcTx.input)) + assert(remoteCommitPublished.htlcOutputs.contains(claimHtlcTx.input)) Transaction.correctlySpends(claimHtlcTx.tx.txInfo.tx, rCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) claimHtlcTx } @@ -676,9 +670,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually { s2blockchain.expectWatchTxConfirmed(rCommitTx.txid) // we watch outputs of the commitment tx that we want to claim - remoteCommitPublished.claimMainOutputTx.foreach(tx => s2blockchain.expectWatchOutputSpent(tx.input.outPoint)) - remoteCommitPublished.claimAnchorTx_opt.foreach(tx => s2blockchain.expectWatchOutputSpent(tx.input.outPoint)) - s2blockchain.expectWatchOutputsSpent(remoteCommitPublished.claimHtlcTxs.keys.toSeq) + remoteCommitPublished.localOutput_opt.foreach(outpoint => s2blockchain.expectWatchOutputSpent(outpoint)) + remoteCommitPublished.anchorOutput_opt.foreach(outpoint => s2blockchain.expectWatchOutputSpent(outpoint)) + s2blockchain.expectWatchOutputsSpent(remoteCommitPublished.htlcOutputs.toSeq) s2blockchain.expectNoMessage(100 millis) // once our closing transactions are published, we watch for their confirmation @@ -703,6 +697,10 @@ object ChannelStateTestsBase { implicit class PimpTestFSM(private val channel: TestFSMRef[ChannelState, ChannelData, Channel]) { val nodeParams: NodeParams = channel.underlyingActor.nodeParams + def signCommitTx(): Transaction = channel.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.fullySignedLocalCommitTx(channel.underlyingActor.channelKeys) + + def htlcTxs(): Seq[HtlcTx] = channel.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.htlcTxs(channel.underlyingActor.channelKeys).map(_._1) + def setBitcoinCoreFeerates(feerates: FeeratesPerKw): Unit = channel.underlyingActor.nodeParams.setBitcoinCoreFeerates(feerates) def setBitcoinCoreFeerate(feerate: FeeratePerKw): Unit = setBitcoinCoreFeerates(FeeratesPerKw.single(feerate)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 7751424204..d424d07832 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -24,6 +24,7 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.publish.TxPublisher +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Announcements @@ -252,7 +253,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ // bob publishes his commitment tx - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(tx) alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx] alice2blockchain.expectMsgType[TxPublisher.PublishTx] @@ -270,7 +271,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv Error") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") aliceListener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) @@ -281,7 +282,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushAmount)) { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! Error(ByteVector32.Zeroes, "funding double-spent") bobListener.expectMsgType[ChannelAborted] awaitCond(bob.stateName == CLOSING) @@ -300,7 +301,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv CMD_FORCECLOSE") { f => import f._ val sender = TestProbe() - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! CMD_FORCECLOSE(sender.ref) aliceListener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala index c76bdf3251..b160a669e8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.channel.fsm.Channel.ProcessCurrentBlockHeight import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.FullySignedSharedTransaction import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId import fr.acinq.eclair.channel.publish.{ReplaceableLocalCommitAnchor, ReplaceableRemoteCommitAnchor, TxPublisher} -import fr.acinq.eclair.channel.states.ChannelStateTestsBase.FakeTxPublisherFactory +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.{FakeTxPublisherFactory, PimpTestFSM} import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.transactions.Transactions @@ -749,13 +749,13 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateData.isInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY]) assert(alice.stateName == OFFLINE) // Bob broadcasts his commit tx. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx - alice ! WatchFundingSpentTriggered(bobCommitTx.tx) + val bobCommitTx = bob.signCommitTx() + alice ! WatchFundingSpentTriggered(bobCommitTx) aliceListener.expectMsgType[TransactionPublished] alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] val claimMain = alice2blockchain.expectFinalTxPublished("remote-main-delayed") - Transaction.correctlySpends(claimMain.tx, Seq(bobCommitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - alice2blockchain.expectWatchTxConfirmed(bobCommitTx.tx.txid) + Transaction.correctlySpends(claimMain.tx, Seq(bobCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + alice2blockchain.expectWatchTxConfirmed(bobCommitTx.txid) alice2blockchain.expectWatchOutputSpent(claimMain.input) aliceListener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) @@ -765,10 +765,10 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx - val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx1 = bob.signCommitTx() val fundingTx2 = testBumpFundingFees(f) assert(fundingTx1.txid != fundingTx2.signedTx.txid) - val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx2 = bob.signCommitTx() assert(bobCommitTx1.txid != bobCommitTx2.txid) alice ! INPUT_DISCONNECTED @@ -803,12 +803,13 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture val aliceData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val bobData = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] val fundingTx = aliceData.latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx + val aliceCommitTx = alice.signCommitTx() + val bobCommitTx = bob.signCommitTx() val (alice2, bob2) = restartNodes(f, aliceData, bobData) alice2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx) assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid) - val bobCommitTx = bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx alice2 ! WatchFundingSpentTriggered(bobCommitTx) alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] val claimMainAlice = alice2blockchain.expectFinalTxPublished("remote-main-delayed") @@ -820,7 +821,6 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture bob2 ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx) assert(bob2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid) - val aliceCommitTx = aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx bob2 ! WatchFundingSpentTriggered(aliceCommitTx) bob2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] val claimMainBob = bob2blockchain.expectFinalTxPublished("remote-main-delayed") @@ -834,11 +834,11 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx - val aliceCommitTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx1 = alice.signCommitTx() + val bobCommitTx1 = bob.signCommitTx() val fundingTx2 = testBumpFundingFees(f) assert(fundingTx1.txid != fundingTx2.signedTx.txid) - val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx2 = bob.signCommitTx() assert(bobCommitTx1.txid != bobCommitTx2.txid) val aliceData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] @@ -1099,7 +1099,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "dual funding d34d") awaitCond(alice.stateName == CLOSING) assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) @@ -1109,7 +1109,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture test("recv Error (remote commit published)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "force-closing channel, bye-bye") awaitCond(alice.stateName == CLOSING) aliceListener.expectMsgType[ChannelAborted] @@ -1121,7 +1121,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture alice2blockchain.expectWatchOutputSpent(claimMainLocal.input) alice2blockchain.expectWatchOutputSpent(anchorTxLocal.txInfo.input.outPoint) // Bob broadcasts his commit tx as well. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(bobCommitTx) val anchorTxRemote = alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] val claimMainRemote = alice2blockchain.expectFinalTxPublished("remote-main-delayed") @@ -1136,24 +1136,24 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx - val aliceCommitTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx - val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx - assert(aliceCommitTx1.input.outPoint.txid == fundingTx1.txid) - assert(bobCommitTx1.input.outPoint.txid == fundingTx1.txid) + val aliceCommitTx1 = alice.signCommitTx() + val bobCommitTx1 = bob.signCommitTx() + assert(aliceCommitTx1.txIn.head.outPoint.txid == fundingTx1.txid) + assert(bobCommitTx1.txIn.head.outPoint.txid == fundingTx1.txid) val fundingTx2 = testBumpFundingFees(f) assert(fundingTx1.txid != fundingTx2.signedTx.txid) - val aliceCommitTx2 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx - assert(aliceCommitTx2.input.outPoint.txid == fundingTx2.signedTx.txid) + val aliceCommitTx2 = alice.signCommitTx() + assert(aliceCommitTx2.txIn.head.outPoint.txid == fundingTx2.signedTx.txid) // Alice receives an error and force-closes using the latest funding transaction. alice ! Error(ByteVector32.Zeroes, "dual funding d34d") awaitCond(alice.stateName == CLOSING) aliceListener.expectMsgType[ChannelAborted] - alice2blockchain.expectFinalTxPublished(aliceCommitTx2.tx.txid) + alice2blockchain.expectFinalTxPublished(aliceCommitTx2.txid) val anchorTx2 = alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor] val claimMain2 = alice2blockchain.expectFinalTxPublished("local-main-delayed") - Transaction.correctlySpends(claimMain2.tx, Seq(aliceCommitTx2.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - alice2blockchain.expectWatchTxConfirmed(aliceCommitTx2.tx.txid) + Transaction.correctlySpends(claimMain2.tx, Seq(aliceCommitTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + alice2blockchain.expectWatchTxConfirmed(aliceCommitTx2.txid) alice2blockchain.expectWatchOutputsSpent(Seq(anchorTx2.txInfo.input.outPoint, claimMain2.input)) // A previous funding transaction confirms, so Alice publishes the corresponding commit tx. @@ -1161,27 +1161,27 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1) assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid) alice2blockchain.expectMsg(UnwatchTxConfirmed(fundingTx2.txId)) - alice2blockchain.expectFinalTxPublished(aliceCommitTx1.tx.txid) + alice2blockchain.expectFinalTxPublished(aliceCommitTx1.txid) val anchorTx1 = alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor] val claimMain1 = alice2blockchain.expectFinalTxPublished("local-main-delayed") - Transaction.correctlySpends(claimMain1.tx, Seq(aliceCommitTx1.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - alice2blockchain.expectWatchTxConfirmed(aliceCommitTx1.tx.txid) + Transaction.correctlySpends(claimMain1.tx, Seq(aliceCommitTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + alice2blockchain.expectWatchTxConfirmed(aliceCommitTx1.txid) alice2blockchain.expectWatchOutputsSpent(Seq(anchorTx1.txInfo.input.outPoint, claimMain1.input)) testUnusedInputsUnlocked(wallet, Seq(fundingTx2)) // Bob publishes his commit tx, Alice reacts by spending her remote main output. - alice ! WatchFundingSpentTriggered(bobCommitTx1.tx) + alice ! WatchFundingSpentTriggered(bobCommitTx1) val anchorRemote = alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] val claimMainRemote = alice2blockchain.expectFinalTxPublished("remote-main-delayed") - Transaction.correctlySpends(claimMainRemote.tx, Seq(bobCommitTx1.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - alice2blockchain.expectWatchTxConfirmed(bobCommitTx1.tx.txid) + Transaction.correctlySpends(claimMainRemote.tx, Seq(bobCommitTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + alice2blockchain.expectWatchTxConfirmed(bobCommitTx1.txid) alice2blockchain.expectWatchOutputsSpent(Seq(anchorRemote.txInfo.input.outPoint, claimMainRemote.input)) assert(alice.stateName == CLOSING) } test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.DualFunding), Tag(noFundingContribution)) { f => import f._ - val commitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitTx = bob.signCommitTx() bob ! Error(ByteVector32.Zeroes, "please help me recover my funds") // We have nothing at stake, but we publish our commitment to help our peer recover their funds more quickly. awaitCond(bob.stateName == CLOSING) @@ -1205,7 +1205,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture test("recv CMD_FORCECLOSE", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val sender = TestProbe() - val commitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitTx = alice.signCommitTx() alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) aliceListener.expectMsgType[ChannelAborted] 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 f39290ed27..ea72f10e28 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 @@ -23,11 +23,11 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.FullySignedSharedTransaction -import fr.acinq.eclair.channel.publish.{ReplaceableRemoteCommitAnchor, TxPublisher} import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId +import fr.acinq.eclair.channel.publish.{ReplaceableRemoteCommitAnchor, TxPublisher} +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees -import fr.acinq.eclair.transactions.Transactions.ClaimAnchorOutputTx import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass} import org.scalatest.OptionValues.convertOptionToValuable @@ -268,7 +268,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ // bob publishes his commitment tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(bobCommitTx) assert(alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx].tx.isInstanceOf[ReplaceableRemoteCommitAnchor]) alice2blockchain.expectMsgType[TxPublisher.PublishTx] @@ -287,7 +287,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF test("recv Error", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val commitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "dual funding failure") listener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) @@ -308,7 +308,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF test("recv CMD_FORCECLOSE", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val sender = TestProbe() - val commitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitTx = alice.signCommitTx() alice ! CMD_FORCECLOSE(sender.ref) listener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index d35d398660..88a4c7b116 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -26,6 +26,7 @@ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITCOIN_FUNDING_TIMEOUT} import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.publish.TxPublisher.PublishFinalTx +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Scripts.multiSig2of2 import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, Error, FundingCreated, FundingSigned, Init, OpenChannel, TlvStream} @@ -233,7 +234,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ // bob publishes his commitment tx - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(tx) assert(listener.expectMsgType[TransactionPublished].tx == tx) alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx] @@ -252,7 +253,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("recv Error") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") awaitCond(alice.stateName == CLOSING) listener.expectMsgType[ChannelAborted] @@ -263,7 +264,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushAmount)) { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! Error(ByteVector32.Zeroes, "please help me recover my funds") // We have nothing at stake, but we publish our commitment to help our peer recover their funds more quickly. awaitCond(bob.stateName == CLOSING) @@ -286,7 +287,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("recv CMD_FORCECLOSE") { f => import f._ val sender = TestProbe() - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) listener.expectMsgType[ChannelAborted] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala index d5bdd3e0e5..8458ed6e8d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala @@ -26,11 +26,12 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fund.InteractiveTxBuilder +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.relay.Relayer.RelayForward import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions.HtlcSuccessTx +import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{channel, _} import org.scalatest.Outcome @@ -444,10 +445,11 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL crossSign(alice, bob, alice2bob, bob2alice) initiateQuiescence(f, sendInitialStfu = true) - val aliceCommit = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - val commitTx = aliceCommit.commitTxAndRemoteSig.commitTx.tx - assert(aliceCommit.htlcTxsAndRemoteSigs.size == 1) - val htlcTimeoutTx = aliceCommit.htlcTxsAndRemoteSigs.head.htlcTx + val commitTx = alice.signCommitTx() + val htlcTxs = alice.htlcTxs() + assert(htlcTxs.size == 1) + val htlcTimeoutTx = htlcTxs.head + assert(htlcTimeoutTx.isInstanceOf[HtlcTimeoutTx]) // the HTLC times out, alice needs to close the channel alice ! CurrentBlockHeight(add.cltvExpiry.blockHeight) @@ -468,10 +470,10 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL crossSign(alice, bob, alice2bob, bob2alice) initiateQuiescence(f, sendInitialStfu = true) - val bobCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - val commitTx = bobCommit.commitTxAndRemoteSig.commitTx.tx - assert(bobCommit.htlcTxsAndRemoteSigs.size == 1) - val htlcSuccessTx = bobCommit.htlcTxsAndRemoteSigs.head.htlcTx + val commitTx = bob.signCommitTx() + val htlcTxs = bob.htlcTxs() + assert(htlcTxs.size == 1) + val htlcSuccessTx = htlcTxs.head assert(htlcSuccessTx.isInstanceOf[HtlcSuccessTx]) // bob does not force-close unless there is a pending preimage for the incoming htlc 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 d6b0204481..6e80aafc10 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 @@ -1141,8 +1141,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat))) checkWatchConfirmed(f, fundingTx1) - val commitAlice1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx - val commitBob1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitAlice1 = alice.signCommitTx() + val commitBob1 = bob.signCommitTx() // Bob sees the first splice confirm, but Alice doesn't. bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1) @@ -1153,8 +1153,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik // Alice creates another splice spending the first splice. val fundingTx2 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat))) checkWatchConfirmed(f, fundingTx2) - val commitAlice2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx - val commitBob2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitAlice2 = alice.signCommitTx() + val commitBob2 = bob.signCommitTx() assert(commitAlice1.txid != commitAlice2.txid) assert(commitBob1.txid != commitBob2.txid) @@ -1237,8 +1237,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik // Alice creates another splice spending the first splice. val fundingTx2 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat))) checkWatchConfirmed(f, fundingTx2) - val commitAlice2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx - val commitBob2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx + val commitAlice2 = alice.signCommitTx() + val commitBob2 = bob.signCommitTx() // Alice sees the second splice confirm. alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx2) @@ -2782,8 +2782,10 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2blockchain.expectWatchFundingSpent(fundingTx1.txid) // Bob publishes his commit tx for the first splice transaction (which double-spends the second splice transaction). - val bobCommitment1 = bob.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.active.find(_.fundingTxIndex == 1).get - val bobCommitTx1 = bobCommitment1.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) + val bobCommitments = bob.stateData.asInstanceOf[ChannelDataWithCommitments].commitments + val previousCommitment = bobCommitments.active.find(_.fundingTxIndex == 1).get + val bobCommitTx1 = previousCommitment.fullySignedLocalCommitTx(bobCommitments.params, bob.underlyingActor.channelKeys) + val bobHtlcTxs = previousCommitment.htlcTxs(bobCommitments.params, bob.underlyingActor.channelKeys).map(_._1) Transaction.correctlySpends(bobCommitTx1, Seq(fundingTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice ! WatchFundingSpentTriggered(bobCommitTx1) assert(alice2blockchain.expectMsgType[WatchAlternativeCommitTxConfirmed].txId == bobCommitTx1.txid) @@ -2815,7 +2817,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateName == CLOSING) // Bob's 2nd-stage transactions confirm. - bobCommitment1.localCommit.htlcTxsAndRemoteSigs.foreach(txAndSig => alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, txAndSig.htlcTx.tx)) + bobHtlcTxs.foreach(htlcTx => alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx)) alice2blockchain.expectNoMessage(100 millis) awaitCond(alice.stateName == CLOSED) @@ -2832,7 +2834,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik 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) // remember bob's commitment for later - val bobCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head + val bobRevokedCommitTx = bob.signCommitTx() // The first splice confirms on Bob's side. bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1) @@ -2861,7 +2863,6 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1) alice2blockchain.expectWatchFundingSpent(fundingTx1.txid) // Bob publishes a revoked commitment for fundingTx1! - val bobRevokedCommitTx = bobCommit1.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! WatchFundingSpentTriggered(bobRevokedCommitTx) // Alice watches bob's revoked commit tx, and force-closes with her latest commitment. assert(alice2blockchain.expectMsgType[WatchAlternativeCommitTxConfirmed].txId == bobRevokedCommitTx.txid) @@ -2928,7 +2929,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik crossSign(alice, bob, alice2bob, bob2alice) // remember bob's commitment for later - val bobCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head + val bobCommitTx1 = bob.signCommitTx() + val bobHtlcTxs = bob.htlcTxs() initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat))) val fundingTx2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get @@ -2959,7 +2961,6 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.size == 1) // bob publishes his latest (inactive) commitment for fundingTx1 - val bobCommitTx1 = bobCommit1.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! WatchFundingSpentTriggered(bobCommitTx1) // alice watches bob's commit tx, and force-closes with her latest commitment assert(alice2blockchain.expectMsgType[WatchAlternativeCommitTxConfirmed].txId == bobCommitTx1.txid) @@ -2992,7 +2993,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik claimHtlcTimeout.foreach(htlcTx => alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx)) assert(alice.stateName == CLOSING) // Bob's htlc-timeout txs confirm. - bobCommit1.localCommit.htlcTxsAndRemoteSigs.foreach(txAndSigs => alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, txAndSigs.htlcTx.tx)) + bobHtlcTxs.foreach(htlcTx => alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx)) alice2blockchain.expectNoMessage(100 millis) awaitCond(alice.stateName == CLOSED) @@ -3010,7 +3011,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2blockchain.expectWatchPublished(fundingTx1.txid) bob2blockchain.expectWatchPublished(fundingTx1.txid) // remember bob's commitment for later - val bobCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head + val bobRevokedCommitTx = bob.signCommitTx() // splice 1 gets published alice ! WatchPublishedTriggered(fundingTx1) bob ! WatchPublishedTriggered(fundingTx1) @@ -3061,7 +3062,6 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.size == 1) // bob publishes his latest commitment for fundingTx1, which is now revoked - val bobRevokedCommitTx = bobCommit1.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! WatchFundingSpentTriggered(bobRevokedCommitTx) // alice watches bob's revoked commit tx, and force-closes with her latest commitment assert(alice2blockchain.expectMsgType[WatchAlternativeCommitTxConfirmed].txId == bobRevokedCommitTx.txid) @@ -3223,12 +3223,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik crossSign(bob, alice, bob2alice, alice2bob) val aliceCommitments1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments1.active.foreach { c => - val commitTx = c.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) + val commitTx = c.fullySignedLocalCommitTx(aliceCommitments1.params, alice.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments1.active.foreach { c => - val commitTx = c.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) + val commitTx = c.fullySignedLocalCommitTx(bobCommitments1.params, bob.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } @@ -3237,12 +3237,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik crossSign(alice, bob, alice2bob, bob2alice) val aliceCommitments2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments aliceCommitments2.active.foreach { c => - val commitTx = c.fullySignedLocalCommitTx(alice.underlyingActor.channelKeys) + val commitTx = c.fullySignedLocalCommitTx(aliceCommitments2.params, alice.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } val bobCommitments2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments bobCommitments2.active.foreach { c => - val commitTx = c.fullySignedLocalCommitTx(bob.underlyingActor.channelKeys) + val commitTx = c.fullySignedLocalCommitTx(bobCommitments2.params, bob.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } 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 b7d612633b..0fafc20900 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 @@ -21,7 +21,7 @@ import akka.testkit.TestProbe import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxOut} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, SatoshiLong, Script, Transaction, TxOut} import fr.acinq.eclair.Features.StaticRemoteKey import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ @@ -31,7 +31,7 @@ import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel._ import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx} -import fr.acinq.eclair.channel.publish.{ReplaceableClaimHtlcSuccess, ReplaceableClaimHtlcTimeout, ReplaceableHtlcSuccess, ReplaceableHtlcTimeout, ReplaceableLocalCommitAnchor, ReplaceableRemoteCommitAnchor} +import fr.acinq.eclair.channel.publish._ import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.crypto.Sphinx @@ -633,7 +633,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (unexpected id)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 42, 150000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None) bob ! htlc.copy(id = 0) bob ! htlc.copy(id = 1) @@ -650,7 +650,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (value too small)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, 150 msat, randomBytes32(), cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None) alice2bob.forward(bob, htlc) val error = bob2alice.expectMsgType[Error] @@ -665,7 +665,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (insufficient funds)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None) alice2bob.forward(bob, htlc) val error = bob2alice.expectMsgType[Error] @@ -680,7 +680,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 100000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) @@ -694,7 +694,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 200000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 167600000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) @@ -711,7 +711,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) @@ -727,7 +727,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (over max inflight htlc value)", Tag(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight)) { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice2bob.forward(alice, UpdateAddHtlc(ByteVector32.Zeroes, 0, 151000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) val error = alice2bob.expectMsgType[Error] assert(new String(error.data.toArray) == HtlcValueTooHighInFlight(channelId(alice), maximum = 150000000 msat, actual = 151000000 msat).getMessage) @@ -741,7 +741,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateAddHtlc (over max accepted htlcs)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // Bob accepts a maximum of 30 htlcs for (i <- 0 until 30) { alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, i, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)) @@ -852,7 +852,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f => import f._ val sender = TestProbe() - val add = CMD_ADD_HTLC(sender.ref, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None, localOrigin(sender.ref)) + val add = CMD_ADD_HTLC(sender.ref, 10_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None, localOrigin(sender.ref)) val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose val htlcCount = epsilons.size for (i <- epsilons) { @@ -866,9 +866,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val commitSig = alice2bob.expectMsgType[CommitSig] assert(commitSig.htlcSignatures.toSet.size == htlcCount) alice2bob.forward(bob) - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == htlcCount) - val htlcTxs = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs - val amounts = htlcTxs.map(_.htlcTx.tx.txOut.head.amount.toLong) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == htlcCount) + val htlcTxs = bob.htlcTxs() + val amounts = htlcTxs.map(_.tx.txOut.head.amount.toLong) assert(amounts == amounts.sorted) } @@ -1040,7 +1040,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[CommitSig] awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.htlcs.collect(incoming).exists(_.id == htlc.id)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == initialState.commitments.latest.localCommit.spec.toLocal) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.acked.size == 0) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.signed.size == 1) @@ -1063,7 +1063,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.htlcs.collect(outgoing).exists(_.id == htlc.id)) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 1) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 1) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == initialState.commitments.latest.localCommit.spec.toLocal) } @@ -1091,7 +1091,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice, bobCommitSig) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.index == 1) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 5) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 5) } test("recv CommitSig (multiple htlcs in both directions) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => @@ -1118,7 +1118,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice, bobCommitSig) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.index == 1) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 3) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 3) } test("recv CommitSig (multiple htlcs in both directions) (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -1145,7 +1145,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice, bobCommitSig) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.index == 1) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 5) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 5) } test("recv CommitSig (multiple htlcs in both directions) (without fundingTxId tlv)") { f => @@ -1205,14 +1205,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.htlcs.collect(incoming).exists(_.id == htlc1.id)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 2) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 2) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == initialState.commitments.latest.localCommit.spec.toLocal) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.count(_.amount == 50000.sat) == 2) + assert(bob.signCommitTx().txOut.count(_.amount == 50000.sat) == 2) } ignore("recv CommitSig (no changes)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // signature is invalid but it doesn't matter bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.Zeroes, Nil) val error = bob2alice.expectMsgType[Error] @@ -1228,7 +1228,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CommitSig (invalid signature)") { f => import f._ addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // actual test begins bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.Zeroes, Nil) @@ -1244,7 +1244,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice ! CMD_SIGN() val commitSig = alice2bob.expectMsgType[CommitSig] @@ -1263,7 +1263,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() alice ! CMD_SIGN() val commitSig = alice2bob.expectMsgType[CommitSig] @@ -1370,7 +1370,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv RevokeAndAck (invalid preimage)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) alice ! CMD_SIGN() @@ -1517,7 +1517,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv RevokeAndAck (unexpectedly)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) alice ! RevokeAndAck(ByteVector32.Zeroes, PrivateKey(randomBytes32()), PrivateKey(randomBytes32()).publicKey) alice2bob.expectMsgType[Error] @@ -1589,7 +1589,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with def aliceToRemoteScript(): ByteVector = { val toRemoteAmount = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toRemote - val Some(toRemoteOut) = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.find(_.amount == toRemoteAmount.truncateToSatoshi) + val Some(toRemoteOut) = alice.signCommitTx().txOut.find(_.amount == toRemoteAmount.truncateToSatoshi) toRemoteOut.publicKeyScript } @@ -1767,7 +1767,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectMsgType[CommitSig] // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFulfillHtlc(ByteVector32.Zeroes, htlc.id, r) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -1780,7 +1780,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFulfillHtlc (unknown htlc id)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFulfillHtlc(ByteVector32.Zeroes, 42, ByteVector32.Zeroes) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -1796,7 +1796,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) bob2relayer.expectMsgType[RelayForward] - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() // actual test begins alice ! UpdateFulfillHtlc(ByteVector32.Zeroes, htlc.id, ByteVector32.Zeroes) @@ -2003,7 +2003,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() val fail = UpdateFailMalformedHtlc(ByteVector32.Zeroes, htlc.id, Sphinx.hash(htlc.onionRoutingPacket), 42) alice ! fail val error = alice2bob.expectMsgType[Error] @@ -2022,7 +2022,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectMsgType[CommitSig] // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFailHtlc(ByteVector32.Zeroes, htlc.id, ByteVector.fill(152)(0)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -2035,7 +2035,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFailHtlc (unknown htlc id)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFailHtlc(ByteVector32.Zeroes, 42, ByteVector.fill(152)(0)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -2226,7 +2226,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (when sender is not funder)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -2239,7 +2239,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (sender can't afford it)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100000000 sat)) // we first update the feerates so that we don't trigger a 'fee too different' error bob.setBitcoinCoreFeerate(fee.feeratePerKw) @@ -2256,7 +2256,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (sender can't afford it, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.HighFeerateMismatchTolerance)) { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // This feerate is just above the threshold: (800000 (alice balance) - 20000 (reserve) - 660 (anchors)) / 1124 (commit tx weight) = 693363 bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(693364 sat)) val error = bob2alice.expectMsgType[Error] @@ -2270,9 +2270,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val commitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.feeratePerKw) + val commitTx = bob.signCommitTx() + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.feeratePerKw) alice2bob.send(bob, UpdateFee(ByteVector32.Zeroes, TestConstants.feeratePerKw / 2)) bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC @@ -2318,7 +2317,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (remote feerate is too small)") { f => import f._ val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments - val tx = bobCommitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val expectedFeeratePerKw = bob.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.remoteNodeId, bobCommitments.params.commitmentFormat, bobCommitments.latest.capacity) assert(bobCommitments.latest.localCommit.spec.commitTxFeerate == expectedFeeratePerKw) bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat)) @@ -2343,7 +2342,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments assert(DustExposure.computeExposure(bobCommitments.latest.localCommit.spec, bobCommitments.params.localParams.dustLimit, bobCommitments.params.commitmentFormat) == 0.msat) assert(DustExposure.computeExposure(bobCommitments.latest.remoteCommit.spec, bobCommitments.params.remoteParams.dustLimit, bobCommitments.params.commitmentFormat) == 0.msat) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-closes: bob.setBitcoinCoreFeerate(FeeratePerKw(20000 sat)) @@ -2374,7 +2373,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(DustExposure.computeExposure(bobCommitments.latest.remoteCommit.spec, bobCommitments.params.remoteParams.dustLimit, bobCommitments.params.commitmentFormat) == 0.msat) // A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-close: - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob.setBitcoinCoreFeerate(FeeratePerKw(20000 sat)) bob ! UpdateFee(channelId(bob), FeeratePerKw(20000 sat)) val error = bob2alice.expectMsgType[Error] @@ -2420,7 +2419,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with (1 to 2).foreach(_ => addHtlc(7400.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob)) // A feerate increase makes these HTLCs become dust in one of the commitments but not the other. - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob.setBitcoinCoreFeerate(updatedFeerate) bob ! UpdateFee(channelId(bob), updatedFeerate) val error = bob2alice.expectMsgType[Error] @@ -2860,8 +2859,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) // actual test begins - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val aliceCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! CurrentBlockHeight(BlockHeight(400145)) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) alice2blockchain.expectMsgType[PublishTx] // main delayed @@ -2883,9 +2881,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // * Alice does not react to the fulfill (drops the message for some reason) // * When the HTLC timeout on Alice side is near, Bob needs to close the channel to avoid an on-chain race // condition between his HTLC-success and Alice's HTLC-timeout - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val commitTx = bob.signCommitTx() + val htlcSuccessTx = bob.htlcTxs().head + assert(htlcSuccessTx.isInstanceOf[HtlcSuccessTx]) bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = true) bob2alice.expectMsgType[UpdateFulfillHtlc] @@ -2896,10 +2894,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(isFatal) assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == commitTx.txid) bob2blockchain.expectMsgType[PublishTx] // main delayed - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.txOut) - assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.tx.txOut) + assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(500 millis) } @@ -2917,9 +2915,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // * Alice does not react to the fulfill (drops the message for some reason) // * When the HTLC timeout on Alice side is near, Bob needs to close the channel to avoid an on-chain race // condition between his HTLC-success and Alice's HTLC-timeout - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val commitTx = bob.signCommitTx() + val htlcSuccessTx = bob.htlcTxs().head + assert(htlcSuccessTx.isInstanceOf[HtlcSuccessTx]) bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = false) bob2alice.expectMsgType[UpdateFulfillHtlc] @@ -2930,10 +2928,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(isFatal) assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == commitTx.txid) bob2blockchain.expectMsgType[PublishTx] // main delayed - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.txOut) - assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.tx.txOut) + assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(500 millis) } @@ -2951,9 +2949,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // * Alice acks but doesn't commit // * When the HTLC timeout on Alice side is near, Bob needs to close the channel to avoid an on-chain race // condition between his HTLC-success and Alice's HTLC-timeout - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val commitTx = bob.signCommitTx() + val htlcSuccessTx = bob.htlcTxs().head + assert(htlcSuccessTx.isInstanceOf[HtlcSuccessTx]) bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = true) bob2alice.expectMsgType[UpdateFulfillHtlc] @@ -2968,10 +2966,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(isFatal) assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == commitTx.txid) bob2blockchain.expectMsgType[PublishTx] // main delayed - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.txOut) - assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == initialCommitTx.txid) + assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txOut == htlcSuccessTx.tx.txOut) + assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(500 millis) } @@ -3105,13 +3103,13 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.size == 8) // two anchor outputs, two main outputs and 4 pending htlcs alice ! WatchFundingSpentTriggered(bobCommitTx) awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) val rcp = alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get - assert(rcp.claimHtlcTxs.size == 4) + assert(rcp.htlcOutputs.size == 4) // in response to that, alice publishes her claim txs val claimAnchor = alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] @@ -3119,7 +3117,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // in addition to her main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage val claimHtlcTxs = (1 to 3).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx]) alice2blockchain.expectWatchTxConfirmed(bobCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(Seq(claimAnchor.txInfo.input.outPoint, claimMain.txIn.head.outPoint) ++ rcp.claimHtlcTxs.keys.toSeq) + alice2blockchain.expectWatchOutputsSpent(Seq(claimAnchor.txInfo.input.outPoint, claimMain.txIn.head.outPoint) ++ rcp.htlcOutputs.toSeq) alice2blockchain.expectNoMessage(100 millis) val htlcAmountClaimed = claimHtlcTxs.map(claimHtlcTx => { @@ -3156,7 +3154,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(bobCommitTx) val addSettled = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.ChannelFailureBeforeSigned.type]] assert(addSettled.htlc == htlc1) @@ -3193,7 +3191,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.size == 7) // two anchor outputs, two main outputs and 3 pending htlcs alice ! WatchFundingSpentTriggered(bobCommitTx) awaitCond(alice.stateName == CLOSING) @@ -3206,7 +3204,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // in addition to her main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage val claimHtlcTxs = (1 to 2).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx]) alice2blockchain.expectWatchTxConfirmed(bobCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(Seq(claimAnchor.txInfo.input.outPoint, claimMain.txIn.head.outPoint) ++ rcp.claimHtlcTxs.keys.toSeq) + alice2blockchain.expectWatchOutputsSpent(Seq(claimAnchor.txInfo.input.outPoint, claimMain.txIn.head.outPoint) ++ rcp.htlcOutputs.toSeq) alice2blockchain.expectNoMessage(100 millis) val htlcAmountClaimed = claimHtlcTxs.map(claimHtlcTx => { @@ -3242,7 +3240,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(bobCommitTx) val addSettled = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.ChannelFailureBeforeSigned.type]] assert(addSettled.htlc == htlc2) @@ -3257,7 +3255,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice sends 10 000 sat addHtlc(10_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + bob.signCommitTx() } val txs = (0 until 10).map(_ => send()) @@ -3279,7 +3277,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head - assert(rvk.htlcPenaltyTxs.size == 4) + assert(rvk.htlcOutputs.size == 4) val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed").tx val mainPenaltyTx = alice2blockchain.expectFinalTxPublished("main-penalty").tx @@ -3316,7 +3314,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) // bob will publish this tx after it is revoked - val revokedTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val revokedTx = bob.signCommitTx() alice ! add sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] @@ -3354,7 +3352,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with addHtlc(10_000 msat, alice, bob, alice2bob, bob2alice, sender.ref) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] crossSign(alice, bob, alice2bob, bob2alice) - val bobRevokedCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobRevokedCommitTx = bob.signCommitTx() addHtlc(10_000 msat, alice, bob, alice2bob, bob2alice, sender.ref) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] crossSign(alice, bob, alice2bob, bob2alice) @@ -3401,7 +3399,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout // an error occurs and alice publishes her commit tx - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs @@ -3409,8 +3407,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get assert(localCommitPublished.commitTx.txid == aliceCommitTx.txid) - assert(localCommitPublished.htlcTxs.size == 4) - assert(localCommitPublished.claimHtlcDelayedTxs.isEmpty) + assert(localCommitPublished.htlcOutputs.size == 4) + assert(localCommitPublished.htlcDelayedOutputs.isEmpty) // alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage // so we expect 4 transactions: @@ -3424,7 +3422,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // the main delayed output and htlc txs spend the commitment transaction Seq(claimMain, htlcTx1, htlcTx2, htlcTx3).foreach(tx => Transaction.correctlySpends(tx.tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(claimMain.input +: localCommitPublished.htlcTxs.keys.toSeq) + alice2blockchain.expectWatchOutputsSpent(claimMain.input +: localCommitPublished.htlcOutputs.toSeq) alice2blockchain.expectNoMessage(100 millis) // 3rd-stage txs are published when htlc txs confirm @@ -3436,7 +3434,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(htlcDelayedTx.tx, htlcTx.tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) } - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.length == 3) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 3) alice2blockchain.expectNoMessage(100 millis) } @@ -3460,7 +3458,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with fulfillHtlc(htlcb1.id, rb1, alice, bob, alice2bob, bob2alice) // an error occurs and alice publishes her commit tx - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) assert(aliceCommitTx.txOut.size == 8) // two main outputs, two anchors and 4 pending htlcs @@ -3474,7 +3472,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage val htlcTxs = (0 until 3).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx]) alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(claimMain.input +: localAnchor.input +: localCommitPublished.htlcTxs.keys.toSeq) + alice2blockchain.expectWatchOutputsSpent(claimMain.input +: localAnchor.input +: localCommitPublished.htlcOutputs.toSeq) alice2blockchain.expectNoMessage(100 millis) // alice sets the confirmation target of each htlc transaction to the htlc expiry @@ -3501,25 +3499,22 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ // an error occurs and alice publishes her commit tx - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) assert(aliceCommitTx.txOut.size == 4) // two main outputs and two anchors awaitCond(alice.stateName == CLOSING) + val lcp = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get - if (commitFeeBumpDisabled) { - val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") - alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain.input) - alice2blockchain.expectNoMessage(100 millis) - } else { - // When there are no pending HTLCs, there is no absolute deadline to get the commit tx confirmed, we use priority - val localAnchor = alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor](ConfirmationTarget.Priority(ConfirmationPriority.Medium)) - val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") - alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(Seq(localAnchor.txInfo.input.outPoint, claimMain.input)) - alice2blockchain.expectNoMessage(100 millis) + if (!commitFeeBumpDisabled) { + // When there are no pending HTLCs, there is no absolute deadline to get the commit tx confirmed: we use a medium priority. + alice2blockchain.expectReplaceableTxPublished[ReplaceableLocalCommitAnchor](ConfirmationTarget.Priority(ConfirmationPriority.Medium)) } + + alice2blockchain.expectFinalTxPublished("local-main-delayed") + alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) + alice2blockchain.expectWatchOutputsSpent(lcp.anchorOutput_opt.toSeq ++ lcp.localOutput_opt.toSeq) + alice2blockchain.expectNoMessage(100 millis) } test("recv Error (anchor outputs zero fee htlc txs without htlcs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => @@ -3537,7 +3532,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // have lost its data and need assistance // an error occurs and alice publishes her commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() bob ! Error(ByteVector32.Zeroes, "oops") bob2blockchain.expectFinalTxPublished(bobCommitTx.txid) assert(bobCommitTx.txOut.size == 1) // only one main output diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index e52a89930e..cb1407ae40 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -380,7 +380,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[CommitSig] // we keep track of bob commitment tx for later - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() // we simulate a disconnection disconnect(alice, bob) @@ -425,7 +425,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // the current state contains a pending htlc addHtlc(250_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() // we simulate a disconnection followed by a reconnection disconnect(alice, bob) @@ -458,7 +458,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ // we sign a new commitment to make sure the first one is revoked - val bobRevokedCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobRevokedCommitTx = bob.signCommitTx() addHtlc(250_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) @@ -613,8 +613,8 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelErrorOccurred]) val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val htlcSuccessTx = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val initialCommitTx = bob.signCommitTx() + val htlcSuccessTx = bob.htlcTxs().head assert(htlcSuccessTx.isInstanceOf[HtlcSuccessTx]) disconnect(alice, bob) @@ -673,14 +673,12 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we simulate a disconnection disconnect(alice, bob) - val aliceStateData = alice.stateData.asInstanceOf[DATA_NORMAL] - val aliceCommitTx = aliceStateData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - - val currentFeeratePerKw = aliceStateData.commitments.latest.localCommit.spec.commitTxFeerate + val aliceCommitTx = alice.signCommitTx() + val currentFeerate = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate // we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1 // to ensure the network's feerate is 10% above our threshold). - val networkFeeratePerKw = currentFeeratePerKw * (1.1 / alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Bob.nodeParams.nodeId).ratioLow) - val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw) + val networkFeerate = currentFeerate * (1.1 / alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Bob.nodeParams.nodeId).ratioLow) + val networkFeerates = FeeratesPerKw.single(networkFeerate) // alice is funder alice.setBitcoinCoreFeerates(networkFeerates) @@ -783,14 +781,12 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we simulate a disconnection disconnect(alice, bob) - val bobStateData = bob.stateData.asInstanceOf[DATA_NORMAL] - val bobCommitTx = bobStateData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - - val currentFeeratePerKw = bobStateData.commitments.latest.localCommit.spec.commitTxFeerate + val bobCommitTx = bob.signCommitTx() + val currentFeerate = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate // we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1 // to ensure the network's feerate is 10% above our threshold). - val networkFeeratePerKw = currentFeeratePerKw * (1.1 / bob.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Alice.nodeParams.nodeId).ratioLow) - val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw) + val networkFeerate = currentFeerate * (1.1 / bob.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Alice.nodeParams.nodeId).ratioLow) + val networkFeerates = FeeratesPerKw.single(networkFeerate) // bob is fundee bob.setBitcoinCoreFeerates(networkFeerates) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index e94638e389..fc90ea0b31 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -49,8 +49,8 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit type FixtureParam = SetupFixture - val r1 = randomBytes32() - val r2 = randomBytes32() + val r1: ByteVector32 = randomBytes32() + val r2: ByteVector32 = randomBytes32() override def withFixture(test: OneArgTest): Outcome = { val setup = init(tags = test.tags) @@ -216,7 +216,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFulfillHtlc (unknown htlc id)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() val fulfill = UpdateFulfillHtlc(ByteVector32.Zeroes, 42, ByteVector32.Zeroes) alice ! fulfill alice2bob.expectMsgType[Error] @@ -230,7 +230,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFulfillHtlc (invalid preimage)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFulfillHtlc(ByteVector32.Zeroes, 42, ByteVector32.Zeroes) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -321,7 +321,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFailHtlc (unknown htlc id)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFailHtlc(ByteVector32.Zeroes, 42, ByteVector.fill(152)(0)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -342,7 +342,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFailMalformedHtlc (invalid failure_code)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() val fail = UpdateFailMalformedHtlc(ByteVector32.Zeroes, 1, Crypto.sha256(ByteVector.empty), 42) alice ! fail val error = alice2bob.expectMsgType[Error] @@ -410,7 +410,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CommitSig (no changes)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() // signature is invalid but it doesn't matter bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.Zeroes, Nil) bob2alice.expectMsgType[Error] @@ -422,7 +422,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CommitSig (invalid signature)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! CommitSig(ByteVector32.Zeroes, ByteVector64.Zeroes, Nil) bob2alice.expectMsgType[Error] awaitCond(bob.stateName == CLOSING) @@ -470,7 +470,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv RevokeAndAck (invalid preimage)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! CMD_FULFILL_HTLC(0, r1) bob2alice.expectMsgType[UpdateFulfillHtlc] bob2alice.forward(alice) @@ -490,7 +490,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv RevokeAndAck (unexpectedly)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isRight) alice ! RevokeAndAck(ByteVector32.Zeroes, PrivateKey(randomBytes32()), PrivateKey(randomBytes32()).publicKey) alice2bob.expectMsgType[Error] @@ -581,7 +581,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee (when sender is not funder)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) @@ -594,7 +594,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee (sender can't afford it)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100000000 sat)) // we first update the feerates so that we don't trigger a 'fee too different' error bob.setBitcoinCoreFeerate(fee.feeratePerKw) @@ -609,7 +609,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(65000 sat)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) == "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=10000") @@ -621,7 +621,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee (remote feerate is too small)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) == "remote fee rate is too small: remoteFeeratePerKw=252") @@ -650,8 +650,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CurrentBlockCount (an htlc timed out)") { f => import f._ - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val aliceCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! CurrentBlockHeight(BlockHeight(400145)) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) // commit tx alice2blockchain.expectMsgType[PublishTx] // main delayed @@ -720,13 +719,13 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv WatchFundingSpentTriggered (their commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ // bob publishes his current commit tx, which contains two pending htlcs alice->bob - val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.size == 6) // two main outputs and 2 pending htlcs alice ! WatchFundingSpentTriggered(bobCommitTx) awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) val rcp = alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get - assert(rcp.claimHtlcTxs.size == 2) + assert(rcp.htlcOutputs.size == 2) // in response to that, alice publishes her claim txs val anchorTx = alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] @@ -765,13 +764,13 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit // as far as alice knows, bob currently has two valid unrevoked commitment transactions // bob publishes his current commit tx, which contains one pending htlc alice->bob - val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.size == 5) // two anchor outputs, two main outputs and 1 pending htlc alice ! WatchFundingSpentTriggered(bobCommitTx) awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.isDefined) val rcp = alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get - assert(rcp.claimHtlcTxs.size == 1) + assert(rcp.htlcOutputs.size == 1) // in response to that, alice publishes her claim txs val anchorTx = alice2blockchain.expectReplaceableTxPublished[ReplaceableRemoteCommitAnchor] @@ -796,7 +795,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv WatchFundingSpentTriggered (revoked tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val revokedTx = bob.signCommitTx() // two main outputs + 2 htlc assert(revokedTx.txOut.size == 6) @@ -830,13 +829,13 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv WatchFundingSpentTriggered (revoked tx with updated commitment)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => import f._ - val initialCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val initialCommitTx = bob.signCommitTx() assert(initialCommitTx.txOut.size == 6) // two main outputs + 2 htlc // bob fulfills one of the pending htlc (commitment update while in shutdown state) fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val revokedTx = bob.signCommitTx() assert(revokedTx.txOut.size == 5) // two anchor outputs, two main outputs + 1 htlc // bob fulfills the second pending htlc (and revokes the previous commitment) @@ -910,7 +909,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CMD_FORCECLOSE") { f => import f._ - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs val sender = TestProbe() @@ -920,7 +919,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) val lcp = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get - assert(lcp.htlcTxs.size == 2) + assert(lcp.htlcOutputs.size == 2) val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") val htlc1 = alice2blockchain.expectFinalTxPublished("htlc-timeout") @@ -939,7 +938,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit Transaction.correctlySpends(htlcDelayedTx.tx, htlcTimeoutTx.tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) }) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.length == 2) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 2) alice2blockchain.expectNoMessage(100 millis) } @@ -952,7 +951,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv Error") { f => import f._ - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 8b2b0e4aee..6ddb12b258 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -474,7 +474,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike import f._ aliceClose(f) val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned] - val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.signCommitTx() bob ! aliceCloseSig.copy(signature = ByteVector64.Zeroes) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray).startsWith("invalid close signature")) @@ -951,7 +951,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike import f._ bobClose(f) alice2bob.expectMsgType[ClosingSigned] - val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") awaitCond(alice.stateName == CLOSING) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) 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 ef3b1d327e..06050fd765 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 @@ -50,7 +50,7 @@ import scala.concurrent.duration._ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { - case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, alice2relayer: TestProbe, bob2relayer: TestProbe, channelUpdateListener: TestProbe, txListener: TestProbe, eventListener: TestProbe, bobCommitTxs: List[CommitTxAndRemoteSig]) + case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, alice2relayer: TestProbe, bob2relayer: TestProbe, channelUpdateListener: TestProbe, txListener: TestProbe, eventListener: TestProbe, bobCommitTxs: List[Transaction]) override def withFixture(test: OneArgTest): Outcome = { val setup = init() @@ -117,20 +117,20 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with systemA.eventStream.subscribe(txListener.ref, classOf[TransactionConfirmed]) systemB.eventStream.subscribe(txListener.ref, classOf[TransactionPublished]) systemB.eventStream.subscribe(txListener.ref, classOf[TransactionConfirmed]) - val bobCommitTxs: List[CommitTxAndRemoteSig] = (for (amt <- List(100000000 msat, 200000000 msat, 300000000 msat)) yield { + val bobCommitTxs = List(100_000_000 msat, 200_000_000 msat, 300_000_000 msat).flatMap(amt => { val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) bob2relayer.expectMsgType[RelayForward] - val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig + val bobCommitTx1 = bob.signCommitTx() fulfillHtlc(htlc.id, r, bob, alice, bob2alice, alice2bob) // alice forwards the fulfill upstream alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.Fulfill]] crossSign(bob, alice, bob2alice, alice2bob) // bob confirms that it has forwarded the fulfill to alice awaitCond(bob.nodeParams.db.pendingCommands.listSettlementCommands(htlc.channelId).isEmpty) - val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig + val bobCommitTx2 = bob.signCommitTx() bobCommitTx1 :: bobCommitTx2 :: Nil - }).flatten + }) awaitCond(alice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) @@ -357,7 +357,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingSpentTriggered (local commit)") { f => import f._ // an error occurs and alice publishes her commit tx - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() localClose(alice, alice2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] assert(initialState.localCommitPublished.isDefined) @@ -392,7 +392,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! CMD_FULFILL_HTLC(htlc1.id, preimage) bob2alice.expectMsgType[UpdateFulfillHtlc] // ignored val (lcp, closingTxs) = localClose(alice, alice2blockchain, htlcTimeoutCount = 2) - assert(lcp.htlcTxs.size == 2) + assert(lcp.htlcOutputs.size == 2) assert(closingTxs.htlcTimeoutTxs.size == 2) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] assert(initialState.localCommitPublished.contains(lcp)) @@ -407,7 +407,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } val claimHtlcSuccessTx1 = bob2blockchain.expectReplaceableTxPublished[ReplaceableClaimHtlcSuccess] val claimHtlcSuccessTx2 = bob2blockchain.expectReplaceableTxPublished[ReplaceableClaimHtlcSuccess] - assert(Seq(claimHtlcSuccessTx1, claimHtlcSuccessTx2).map(_.txInfo.input.outPoint).toSet == lcp.htlcTxs.keySet) + assert(Seq(claimHtlcSuccessTx1, claimHtlcSuccessTx2).map(_.txInfo.input.outPoint).toSet == lcp.htlcOutputs) // Alice extracts the preimage and forwards it upstream. alice ! WatchOutputSpentTriggered(htlc1.amountMsat.truncateToSatoshi, claimHtlcSuccessTx1.txInfo.tx) @@ -448,7 +448,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (rcp, closingTxs) = localClose(bob, bob2blockchain, htlcSuccessCount = 2) // Bob claims the htlc outputs from his own commit tx using its preimage. - assert(rcp.htlcTxs.size == 2) + assert(rcp.htlcOutputs.size == 2) assert(closingTxs.htlcSuccessTxs.size == 2) // Alice extracts the preimage and forwards it upstream. @@ -503,7 +503,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob claims the htlc outputs from his previous commit tx using its preimage. val (rcp, closingTxs) = localClose(bob, bob2blockchain, htlcSuccessCount = 2) - assert(rcp.htlcTxs.size == 3) + assert(rcp.htlcOutputs.size == 3) assert(closingTxs.htlcSuccessTxs.size == 2) // Bob doesn't have the preimage for the last HTLC. // Alice prepares Claim-HTLC-timeout transactions for each HTLC. @@ -583,7 +583,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // At that point, the HTLCs are not in Alice's commitment yet. val (rcp, closingTxs) = localClose(bob, bob2blockchain) - assert(rcp.htlcTxs.size == 3) + assert(rcp.htlcOutputs.size == 3) // Bob doesn't have the preimage yet for any of those HTLCs. assert(closingTxs.htlcTxs.isEmpty) // Bob receives the preimage for the first two HTLCs. @@ -687,12 +687,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (closingState, closingTxs) = localClose(alice, alice2blockchain, htlcTimeoutCount = 1) // actual test starts here - assert(closingState.claimMainDelayedOutputTx.isDefined) + assert(closingState.localOutput_opt.isDefined) assert(closingTxs.mainTx_opt.isDefined) - assert(closingState.htlcTxs.size == 1) + assert(closingState.htlcOutputs.size == 1) assert(closingTxs.htlcTimeoutTxs.size == 1) val htlcTimeoutTx = closingTxs.htlcTimeoutTxs.head - assert(closingState.claimHtlcDelayedTxs.isEmpty) + assert(closingState.htlcDelayedOutputs.isEmpty) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, closingState.commitTx) assert(txListener.expectMsgType[TransactionConfirmed].tx == closingState.commitTx) assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == BlockHeight(42) + bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.localParams.toSelfDelay.toInt) @@ -720,7 +720,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchOutputSpentTriggered(htlcDelayedTx.amount, htlcDelayedTx.tx) alice2blockchain.expectWatchTxConfirmed(htlcDelayedTx.tx.txid) alice2blockchain.expectNoMessage(100 millis) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.length == 1) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 1) alice ! WatchTxConfirmedTriggered(BlockHeight(202), 0, htlcDelayedTx.tx) awaitCond(alice.stateName == CLOSED) } @@ -752,11 +752,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (closingState, closingTxs) = localClose(alice, alice2blockchain, htlcTimeoutCount = 4) // actual test starts here - assert(closingState.claimMainDelayedOutputTx.isDefined) + assert(closingState.localOutput_opt.isDefined) assert(closingTxs.mainTx_opt.isDefined) - assert(closingState.htlcTxs.size == 4) + assert(closingState.htlcOutputs.size == 4) assert(closingTxs.htlcTimeoutTxs.size == 4) - assert(closingState.claimHtlcDelayedTxs.isEmpty) + assert(closingState.htlcDelayedOutputs.isEmpty) // if commit tx and htlc-timeout txs end up in the same block, we may receive the htlc-timeout confirmation before the commit tx confirmation alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, closingTxs.htlcTimeoutTxs(0)) @@ -765,7 +765,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchTxConfirmedTriggered(BlockHeight(42), 1, closingState.commitTx) assert(alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc == dust) alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(200), 0, closingState.claimMainDelayedOutputTx.get.tx) + alice ! WatchTxConfirmedTriggered(BlockHeight(200), 0, closingTxs.mainTx_opt.get) alice ! WatchTxConfirmedTriggered(BlockHeight(202), 0, closingTxs.htlcTimeoutTxs(1)) val forwardedFail2 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc alice2relayer.expectNoMessage(100 millis) @@ -783,7 +783,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) htlcDelayedTx }) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.length == 4) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 4) htlcDelayedTxs.foreach(tx => { alice ! WatchOutputSpentTriggered(tx.amount, tx.tx) alice2blockchain.expectWatchTxConfirmed(tx.tx.txid) @@ -796,7 +796,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val listener = TestProbe() systemA.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain]) - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() // alice sends an htlc val (_, htlc) = addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice) // and signs it (but bob doesn't sign it) @@ -808,7 +808,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here channelUpdateListener.expectMsgType[LocalChannelDown] - assert(closingState.htlcTxs.isEmpty && closingState.claimHtlcDelayedTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty && closingState.htlcDelayedOutputs.isEmpty) assert(closingTxs.htlcTxs.isEmpty) // when the commit tx is confirmed, alice knows that the htlc she sent right before the unilateral close will never reach the chain alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, aliceCommitTx) @@ -829,19 +829,19 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice) alice2bob.expectMsgType[RevokeAndAck] alice2relayer.expectNoMessage(100 millis) // the HTLC is not relayed downstream - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 1) - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 1) + val aliceCommitTx = alice.signCommitTx() // Note that alice has not signed the htlc yet! // We make her unilaterally close the channel. val (closingState, closingTxs) = localClose(alice, alice2blockchain) channelUpdateListener.expectMsgType[LocalChannelDown] - assert(closingState.htlcTxs.isEmpty && closingState.claimHtlcDelayedTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty && closingState.htlcDelayedOutputs.isEmpty) assert(closingTxs.htlcTxs.isEmpty) // Alice should ignore the htlc (she hasn't relayed it yet): it is Bob's responsibility to claim it. // Once the commit tx and her main output are confirmed, she can consider the channel closed. alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, aliceCommitTx) - closingState.claimMainDelayedOutputTx.foreach(claimMain => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimMain.tx)) + closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) alice2relayer.expectNoMessage(100 millis) awaitCond(alice.stateName == CLOSED) } @@ -859,18 +859,18 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob) bob2alice.expectMsgType[RevokeAndAck] // not received by Alice alice2relayer.expectNoMessage(100 millis) // the HTLC is not relayed downstream - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcTxsAndRemoteSigs.size == 1) - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 1) + val bobCommitTx = bob.signCommitTx() // We make Bob unilaterally close the channel. val (rcp, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) channelUpdateListener.expectMsgType[LocalChannelDown] - assert(rcp.claimHtlcTxs.isEmpty) + assert(rcp.htlcOutputs.isEmpty) assert(closingTxs.htlcTxs.isEmpty) // Alice should ignore the htlc (she hasn't relayed it yet): it is Bob's responsibility to claim it. // Once the commit tx and her main output are confirmed, she can consider the channel closed. alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - rcp.claimMainOutputTx.foreach(claimMain => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimMain.tx)) + closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) alice2relayer.expectNoMessage(100 millis) awaitCond(alice.stateName == CLOSED) } @@ -881,7 +881,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (r, htlc) = addHtlc(110_000_000 msat, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) assert(alice2relayer.expectMsgType[RelayForward].add == htlc) - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val aliceCommitTx = alice.signCommitTx() assert(aliceCommitTx.txOut.size == 3) // 2 main outputs + 1 htlc // alice fulfills the HTLC but bob doesn't receive the signature @@ -922,7 +922,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here channelUpdateListener.expectMsgType[LocalChannelDown] - assert(closingState.htlcTxs.isEmpty && closingState.claimHtlcDelayedTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty && closingState.htlcDelayedOutputs.isEmpty) assert(closingTxs.htlcTxs.isEmpty) // when the commit tx is confirmed, alice knows that the htlc will never reach the chain alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingState.commitTx) @@ -946,13 +946,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Alice force-closes. val (closingState, closingTxs) = localClose(alice, alice2blockchain) assert(closingState.commitTx.txOut.length == 6) // 2 main outputs + 2 anchor outputs + 2 htlcs - assert(closingState.claimMainDelayedOutputTx.nonEmpty) - assert(closingState.htlcTxs.size == 2) + assert(closingState.localOutput_opt.nonEmpty) + assert(closingState.htlcOutputs.size == 2) assert(closingTxs.htlcTxs.isEmpty) // we don't have the preimage to claim the htlc-success yet // Alice's commitment and main transaction confirm: she waits for the HTLC outputs to be spent. alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingState.commitTx) - closingState.claimMainDelayedOutputTx.foreach(claimMain => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimMain.tx)) + closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) assert(alice.stateName == CLOSING) // Alice receives the preimage for the first HTLC from downstream; she can now claim the corresponding HTLC output. @@ -1035,7 +1035,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchTxConfirmedTriggered(BlockHeight(2702), 0, htlcTimeoutTx) val htlcDelayed = alice2blockchain.expectFinalTxPublished("htlc-delayed") alice2blockchain.expectWatchOutputSpent(htlcDelayed.input) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.nonEmpty) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.nonEmpty) val beforeSecondRestart = alice.stateData.asInstanceOf[DATA_CLOSING] // simulate another node restart @@ -1104,14 +1104,16 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectMsgType[SetChannelId] awaitCond(alice.stateName == CLOSING) - // We re-publish closing transactions. + // We re-publish closing transactions with a higher feerate. val mainTx = alice2blockchain.expectFinalTxPublished("local-main-delayed") + assert(mainTx.tx.txOut.head.amount < closingTxs.mainTx_opt.get.txOut.head.amount) alice2blockchain.expectWatchOutputSpent(mainTx.input) val htlcDelayedTxs = Seq( alice2blockchain.expectFinalTxPublished("htlc-delayed"), alice2blockchain.expectFinalTxPublished("htlc-delayed"), ) assert(htlcDelayedTxs.map(_.input).toSet == Seq(htlcTimeoutDelayedTx, htlcSuccessDelayedTx).map(_.input).toSet) + assert(htlcDelayedTxs.flatMap(_.tx.txOut.map(_.amount)).sum < Seq(htlcTimeoutDelayedTx, htlcSuccessDelayedTx).flatMap(_.tx.txOut.map(_.amount)).sum) alice2blockchain.expectWatchOutputsSpent(htlcDelayedTxs.map(_.input)) // We replay the HTLC fulfillment: nothing happens since we already published a 3rd-stage transaction. @@ -1152,13 +1154,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Alice force-closes. val (closingStateAlice, closingTxsAlice) = localClose(alice, alice2blockchain, htlcSuccessCount = 2, htlcTimeoutCount = 3) - assert(closingStateAlice.htlcTxs.size == 6) + assert(closingStateAlice.htlcOutputs.size == 6) assert(closingTxsAlice.htlcSuccessTxs.size == 2) assert(closingTxsAlice.htlcTimeoutTxs.size == 3) // Bob detects Alice's force-close. val (closingStateBob, closingTxsBob) = remoteClose(closingStateAlice.commitTx, bob, bob2blockchain, htlcSuccessCount = 2, htlcTimeoutCount = 3) - assert(closingStateBob.claimHtlcTxs.size == 6) + assert(closingStateBob.htlcOutputs.size == 6) assert(closingTxsBob.htlcSuccessTxs.size == 2) assert(closingTxsBob.htlcTimeoutTxs.size == 3) @@ -1258,7 +1260,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val listener = TestProbe() systemA.eventStream.subscribe(listener.ref, classOf[PaymentSettlingOnChain]) - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() // alice sends an htlc val (_, htlc) = addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice) // and signs it (but bob doesn't sign it) @@ -1268,8 +1270,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here channelUpdateListener.expectMsgType[LocalChannelDown] - assert(closingState.claimMainOutputTx.isEmpty) - assert(closingState.claimHtlcTxs.isEmpty) + assert(closingState.localOutput_opt.isEmpty) + assert(closingState.htlcOutputs.isEmpty) // when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) // so she fails it @@ -1307,9 +1309,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[RevokeAndAck] // not sent to alice // bob closes the channel using his latest commitment, which doesn't contain any htlc. - val bobCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - assert(bobCommit.htlcTxsAndRemoteSigs.isEmpty) - val commitTx = bobCommit.commitTxAndRemoteSig.commitTx.tx + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.isEmpty) + val commitTx = bob.signCommitTx() alice ! WatchFundingSpentTriggered(commitTx) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, commitTx) // the two HTLCs have been overridden by the on-chain commit @@ -1330,11 +1331,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 2) // two main outputs val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) - assert(closingState.claimMainOutputTx.isEmpty) - assert(closingState.claimHtlcTxs.isEmpty) + assert(closingState.localOutput_opt.isEmpty) + assert(closingState.htlcOutputs.isEmpty) assert(closingTxs.mainTx_opt.isEmpty) assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState) val txPublished = txListener.expectMsgType[TransactionPublished] @@ -1349,7 +1350,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with systemA.eventStream.subscribe(listener.ref, classOf[LocalChannelUpdate]) // bob publishes his commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() remoteClose(bobCommitTx, alice, alice2blockchain) // alice notifies the network that the channel shouldn't be used anymore inside(listener.expectMsgType[LocalChannelUpdate]) { u => assert(!u.channelUpdate.channelFlags.isEnabled) } @@ -1359,9 +1360,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) // Bob publishes his last current commit tx, the one it had when entering NEGOTIATING state. - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) - assert(closingState.claimHtlcTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty) assert(closingTxs.mainTx_opt.isEmpty) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) @@ -1371,9 +1372,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CMD_BUMP_FORCE_CLOSE_FEE (remote commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) - assert(closingState.claimAnchorTxs.nonEmpty) + assert(closingState.anchorOutput_opt.nonEmpty) assert(closingTxs.anchorTx_opt.nonEmpty) val replyTo = TestProbe() @@ -1391,14 +1392,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 4) // two main outputs + two anchors val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) // actual test starts here - assert(closingState.claimMainOutputTx.nonEmpty) + assert(closingState.localOutput_opt.nonEmpty) assert(closingTxs.mainTx_opt.nonEmpty) - assert(closingState.claimHtlcTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty) assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState) txListener.expectMsgType[TransactionPublished] alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) @@ -1412,12 +1413,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING_SIMPLE].commitments.params.channelFeatures == ChannelFeatures(Features.StaticRemoteKey)) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 2) // two main outputs alice ! WatchFundingSpentTriggered(bobCommitTx) // alice won't create a claimMainOutputTx because her main output is already spendable by the wallet - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimMainOutputTx.isEmpty) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.localOutput_opt.isEmpty) assert(alice.stateName == CLOSING) // once the remote commit is confirmed the channel is definitively closed alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) @@ -1430,14 +1431,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] assert(initialState.commitments.params.channelFeatures == ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state - val bobCommitTx = bobCommitTxs.last.commitTx.tx + val bobCommitTx = bobCommitTxs.last assert(bobCommitTx.txOut.size == 4) // two main outputs + two anchors val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) // actual test starts here - assert(closingState.claimMainOutputTx.nonEmpty) + assert(closingState.localOutput_opt.nonEmpty) assert(closingTxs.mainTx_opt.nonEmpty) - assert(closingState.claimHtlcTxs.isEmpty) + assert(closingState.htlcOutputs.isEmpty) assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState) alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingTxs.mainTx_opt.get) @@ -1459,13 +1460,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) // Bob publishes the latest commit tx. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() channelFeatures.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 } val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 3) - assert(closingState.claimHtlcTxs.size == 3) + assert(closingState.htlcOutputs.size == 3) assert(closingTxs.htlcTimeoutTxs.length == 3) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) @@ -1512,11 +1513,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectMsgType[CommitSig] // We stop here: Alice sent her CommitSig, but doesn't hear back from Bob. // Now Bob publishes the first commit tx (force-close). - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.length == 6) // 2 main outputs + 2 anchor outputs + 2 HTLCs val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) - assert(closingState.claimMainOutputTx.nonEmpty) - assert(closingState.claimHtlcTxs.size == 2) + assert(closingState.localOutput_opt.nonEmpty) + assert(closingState.htlcOutputs.size == 2) assert(closingTxs.htlcTxs.isEmpty) // we don't have the preimage to claim the htlc-success yet // Alice receives the preimage for the first HTLC from downstream; she can now claim the corresponding HTLC output. @@ -1556,29 +1557,34 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (remote commit)") { f => import f._ - // alice sends an htlc to bob + // Alice sends an htlc to Bob: Bob then force-closes. val (_, htlc) = addHtlc(50_000_000 msat, CltvExpiryDelta(24), alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() val (_, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 1) assert(closingTxs.htlcTimeoutTxs.size == 1) val htlcTimeoutTx = closingTxs.htlcTxs.head alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - // simulate a node restart + // We simulate a node restart with a lower feerate. val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] + alice.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2_500 sat))) alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing) alice ! INPUT_RESTORED(beforeRestart) alice2blockchain.expectMsgType[SetChannelId] awaitCond(alice.stateName == CLOSING) - // we should re-publish unconfirmed transactions + // We should re-publish unconfirmed transactions. + // Our main transaction should have a lower feerate. + // HTLC transactions are unchanged: the feerate will be based on their expiry. closingTxs.mainTx_opt.foreach(tx => { - alice2blockchain.expectFinalTxPublished("local-main-delayed") + val tx2 = alice2blockchain.expectFinalTxPublished("local-main-delayed") + assert(tx2.tx.txOut.head.amount > tx.txOut.head.amount) alice2blockchain.expectWatchOutputSpent(tx.txIn.head.outPoint) }) val htlcTimeout = alice2blockchain.expectReplaceableTxPublished[ReplaceableClaimHtlcTimeout](ConfirmationTarget.Absolute(htlc.cltvExpiry.blockHeight)) assert(htlcTimeout.txInfo.input.outPoint == htlcTimeoutTx.txIn.head.outPoint) + assert(htlcTimeout.txInfo.tx.txid == htlcTimeoutTx.txid) alice2blockchain.expectWatchOutputSpent(htlcTimeout.txInfo.input.outPoint) } @@ -1603,13 +1609,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[CommitSig] // not forwarded to Alice (malicious Bob) // Bob publishes the next commit tx. - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() channelFeatures.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 } val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 3) - assert(closingState.claimHtlcTxs.size == 3) + assert(closingState.htlcOutputs.size == 3) assert(closingTxs.htlcTimeoutTxs.size == 3) (bobCommitTx, closingTxs, Set(htlca1, htlca2, htlca3)) } @@ -1693,11 +1699,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[CommitSig] // not forwarded to Alice (malicious Bob) // Now Bob publishes the next commit tx (force-close). - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.length == 7) // 2 main outputs + 2 anchor outputs + 3 HTLCs val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 1) - assert(closingState.claimMainOutputTx.nonEmpty) - assert(closingState.claimHtlcTxs.size == 3) + assert(closingState.localOutput_opt.nonEmpty) + assert(closingState.htlcOutputs.size == 3) assert(closingTxs.htlcTxs.size == 1) // we don't have the preimage to claim the htlc-success yet val htlcTimeoutTx = closingTxs.htlcTimeoutTxs.head @@ -1801,7 +1807,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice now waits for bob to publish its commitment awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) // bob is nice and publishes its commitment - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() channelFeatures.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 @@ -1870,65 +1876,67 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - case class RevokedCloseFixture(bobRevokedTxs: Seq[LocalCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) + case class RevokedCommit(commitTx: Transaction, htlcTxs: Seq[HtlcTx]) + + case class RevokedCloseFixture(bobRevokedTxs: Seq[RevokedCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) private def prepareRevokedClose(f: FixtureParam, channelFeatures: ChannelFeatures): RevokedCloseFixture = { import f._ // Bob's first commit tx doesn't contain any htlc - val localCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit + val bobCommit1 = RevokedCommit(bob.signCommitTx(), Nil) channelFeatures.commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) // 2 main outputs + 2 anchors - case DefaultCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2) // 2 main outputs + 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 } // Bob's second commit tx contains 1 incoming htlc and 1 outgoing htlc - val (localCommit2, htlcAlice1, htlcBob1) = { + val (bobCommit2, htlcAlice1, htlcBob1) = { val (ra, htlcAlice) = addHtlc(35_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) val (rb, htlcBob) = addHtlc(20_000_000 msat, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - val localCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - (localCommit, (htlcAlice, ra), (htlcBob, rb)) + val bobCommit2 = RevokedCommit(bob.signCommitTx(), bob.htlcTxs()) + (bobCommit2, (htlcAlice, ra), (htlcBob, rb)) } - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size) + assert(alice.signCommitTx().txOut.size == bobCommit2.commitTx.txOut.size) channelFeatures.commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) - case DefaultCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) + case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 6) + case DefaultCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 4) } // Bob's third commit tx contains 2 incoming htlcs and 2 outgoing htlcs - val (localCommit3, htlcAlice2, htlcBob2) = { + val (bobCommit3, htlcAlice2, htlcBob2) = { val (ra, htlcAlice) = addHtlc(25_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) val (rb, htlcBob) = addHtlc(18_000_000 msat, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - val localCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit - (localCommit, (htlcAlice, ra), (htlcBob, rb)) + val bobCommit3 = RevokedCommit(bob.signCommitTx(), bob.htlcTxs()) + (bobCommit3, (htlcAlice, ra), (htlcBob, rb)) } - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size) + assert(alice.signCommitTx().txOut.size == bobCommit3.commitTx.txOut.size) channelFeatures.commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8) - case DefaultCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) + case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 8) + case DefaultCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 6) } // Bob's fourth commit tx doesn't contain any htlc - val localCommit4 = { + val bobCommit4 = { Seq(htlcAlice1, htlcAlice2).foreach { case (htlcAlice, _) => failHtlc(htlcAlice.id, bob, alice, bob2alice, alice2bob) } Seq(htlcBob1, htlcBob2).foreach { case (htlcBob, _) => failHtlc(htlcBob.id, alice, bob, alice2bob, bob2alice) } crossSign(alice, bob, alice2bob, bob2alice) - bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit + RevokedCommit(bob.signCommitTx(), Nil) } - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size) + assert(alice.signCommitTx().txOut.size == bobCommit4.commitTx.txOut.size) channelFeatures.commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) - case DefaultCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2) + case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 4) + case DefaultCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 2) } - RevokedCloseFixture(Seq(localCommit1, localCommit2, localCommit3, localCommit4), Seq(htlcAlice1, htlcAlice2), Seq(htlcBob1, htlcBob2)) + RevokedCloseFixture(Seq(bobCommit1, bobCommit2, bobCommit3, bobCommit4), Seq(htlcAlice1, htlcAlice2), Seq(htlcBob1, htlcBob2)) } case class RevokedCloseTxs(mainTx_opt: Option[Transaction], mainPenaltyTx: Transaction, htlcPenaltyTxs: Seq[Transaction]) @@ -1940,7 +1948,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures) // bob publishes one of his revoked txs - val bobRevokedTx = revokedCloseFixture.bobRevokedTxs(1).commitTxAndRemoteSig.commitTx.tx + val bobRevokedTx = revokedCloseFixture.bobRevokedTxs(1).commitTx alice ! WatchFundingSpentTriggered(bobRevokedTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) @@ -1948,18 +1956,18 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedTx) if (!channelFeatures.paysDirectlyToWallet) { - assert(rvk.claimMainOutputTx.nonEmpty) + assert(rvk.localOutput_opt.nonEmpty) } - assert(rvk.mainPenaltyTx.nonEmpty) - assert(rvk.htlcPenaltyTxs.size == 2) - assert(rvk.claimHtlcDelayedPenaltyTxs.isEmpty) + assert(rvk.remoteOutput_opt.nonEmpty) + assert(rvk.htlcOutputs.size == 2) + assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs val mainTx_opt = if (!channelFeatures.paysDirectlyToWallet) 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")) - assert(htlcPenaltyTxs.map(_.input).toSet == rvk.htlcPenaltyTxs.map(_.input.outPoint).toSet) + assert(htlcPenaltyTxs.map(_.input).toSet == rvk.htlcOutputs) htlcPenaltyTxs.foreach(penaltyTx => Transaction.correctlySpends(penaltyTx.tx, bobRevokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) // alice spends all outpoints of the revoked tx, except her main output when it goes directly to our wallet @@ -2012,7 +2020,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingSpentTriggered (multiple revoked tx)") { f => import f._ val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey)) - assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTxAndRemoteSig.commitTx.tx.txid).toSet.size == revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct + 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 = { alice ! WatchFundingSpentTriggered(revokedTx) @@ -2032,16 +2040,16 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } // bob publishes a first revoked tx (no htlc in that commitment) - broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs.head.commitTxAndRemoteSig.commitTx.tx, 0, 1) + broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs.head.commitTx, 0, 1) // bob publishes a second revoked tx - val closingTxs = broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs(1).commitTxAndRemoteSig.commitTx.tx, 2, 2) + val closingTxs = broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs(1).commitTx, 2, 2) // bob publishes a third revoked tx - broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs(2).commitTxAndRemoteSig.commitTx.tx, 4, 3) + broadcastBobRevokedTx(revokedCloseFixture.bobRevokedTxs(2).commitTx, 4, 3) // bob's second revoked tx confirms: once all penalty txs are confirmed, alice can move to the closed state // NB: if multiple txs confirm in the same block, we may receive the events in any order alice ! WatchTxConfirmedTriggered(BlockHeight(100), 1, closingTxs.mainPenaltyTx) - alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, revokedCloseFixture.bobRevokedTxs(1).commitTxAndRemoteSig.commitTx.tx) + alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, revokedCloseFixture.bobRevokedTxs(1).commitTx) alice ! WatchTxConfirmedTriggered(BlockHeight(115), 0, closingTxs.htlcPenaltyTxs(0)) assert(alice.stateName == CLOSING) alice ! WatchTxConfirmedTriggered(BlockHeight(115), 2, closingTxs.htlcPenaltyTxs(1)) @@ -2092,20 +2100,20 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob publishes one of his revoked txs val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) - alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTxAndRemoteSig.commitTx.tx) + alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head - assert(rvk.commitTx == bobRevokedCommit.commitTxAndRemoteSig.commitTx.tx) + assert(rvk.commitTx == bobRevokedCommit.commitTx) if (channelFeatures.paysDirectlyToWallet) { - assert(rvk.claimMainOutputTx.isEmpty) + assert(rvk.localOutput_opt.isEmpty) } else { - assert(rvk.claimMainOutputTx.nonEmpty) + assert(rvk.localOutput_opt.nonEmpty) } - assert(rvk.mainPenaltyTx.nonEmpty) - assert(rvk.htlcPenaltyTxs.size == 4) - assert(rvk.claimHtlcDelayedPenaltyTxs.isEmpty) + assert(rvk.remoteOutput_opt.nonEmpty) + assert(rvk.htlcOutputs.size == 4) + 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 @@ -2122,14 +2130,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob publishes one of his HTLC-success transactions val (fulfilledHtlc, _) = revokedCloseFixture.htlcsAlice.head - val bobHtlcSuccessTx1 = bobRevokedCommit.htlcTxsAndRemoteSigs.collectFirst { case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, _) if txInfo.htlcId == fulfilledHtlc.id => txInfo }.get + val bobHtlcSuccessTx1 = bobRevokedCommit.htlcTxs.collectFirst { case txInfo: HtlcSuccessTx if txInfo.htlcId == fulfilledHtlc.id => txInfo }.get assert(bobHtlcSuccessTx1.paymentHash == fulfilledHtlc.paymentHash) alice ! WatchOutputSpentTriggered(bobHtlcSuccessTx1.amountIn, bobHtlcSuccessTx1.tx) alice2blockchain.expectWatchTxConfirmed(bobHtlcSuccessTx1.tx.txid) // bob publishes one of his HTLC-timeout transactions val (failedHtlc, _) = revokedCloseFixture.htlcsBob.last - val bobHtlcTimeoutTx = bobRevokedCommit.htlcTxsAndRemoteSigs.collectFirst { case HtlcTxAndRemoteSig(txInfo: HtlcTimeoutTx, _) if txInfo.htlcId == failedHtlc.id => txInfo }.get + val bobHtlcTimeoutTx = bobRevokedCommit.htlcTxs.collectFirst { case txInfo: HtlcTimeoutTx if txInfo.htlcId == failedHtlc.id => txInfo }.get assert(bobHtlcTimeoutTx.paymentHash == failedHtlc.paymentHash) alice ! WatchOutputSpentTriggered(bobHtlcTimeoutTx.amountIn, bobHtlcTimeoutTx.tx) alice2blockchain.expectWatchTxConfirmed(bobHtlcTimeoutTx.tx.txid) @@ -2142,7 +2150,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob's HTLC-timeout confirms: alice reacts by publishing a penalty tx alice ! WatchTxConfirmedTriggered(BlockHeight(115), 0, bobHtlcTimeoutTx.tx) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.claimHtlcDelayedPenaltyTxs.size == 1) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.htlcDelayedOutputs.size == 1) val htlcTimeoutDelayedPenalty = alice2blockchain.expectFinalTxPublished("htlc-delayed-penalty") Transaction.correctlySpends(htlcTimeoutDelayedPenalty.tx, bobHtlcTimeoutTx.tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcTimeoutDelayedPenalty.input) @@ -2150,7 +2158,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob's htlc-success RBF confirms: alice reacts by publishing a penalty tx alice ! WatchTxConfirmedTriggered(BlockHeight(115), 1, bobHtlcSuccessTx2) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.claimHtlcDelayedPenaltyTxs.size == 2) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.htlcDelayedOutputs.size == 2) val htlcSuccessDelayedPenalty = alice2blockchain.expectFinalTxPublished("htlc-delayed-penalty") Transaction.correctlySpends(htlcSuccessDelayedPenalty.tx, bobHtlcSuccessTx2 :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcSuccessDelayedPenalty.input) @@ -2158,7 +2166,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // transactions confirm: alice can move to the closed state val bobHtlcOutpoints = Set(bobHtlcTimeoutTx.input.outPoint, bobHtlcSuccessTx1.input.outPoint) - val remainingHtlcPenaltyTxs = rvk.htlcPenaltyTxs.filterNot(htlcPenalty => bobHtlcOutpoints.contains(htlcPenalty.input.outPoint)) + val remainingHtlcPenaltyTxs = htlcPenalty.filterNot(tx => bobHtlcOutpoints.contains(tx.input)) assert(remainingHtlcPenaltyTxs.size == 2) alice ! WatchTxConfirmedTriggered(BlockHeight(110), 2, remainingHtlcPenaltyTxs.head.tx) alice ! WatchTxConfirmedTriggered(BlockHeight(115), 2, remainingHtlcPenaltyTxs.last.tx) @@ -2186,14 +2194,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob publishes one of his revoked txs val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) - alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTxAndRemoteSig.commitTx.tx) + alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.params.commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head - assert(rvk.commitTx == bobRevokedCommit.commitTxAndRemoteSig.commitTx.tx) - assert(rvk.htlcPenaltyTxs.size == 4) - assert(rvk.claimHtlcDelayedPenaltyTxs.isEmpty) + assert(rvk.commitTx == bobRevokedCommit.commitTx) + assert(rvk.htlcOutputs.size == 4) + assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs and watches outputs val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") @@ -2205,12 +2213,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob claims multiple htlc outputs in a single transaction (this is possible with anchor outputs because signatures // use sighash_single | sighash_anyonecanpay) - val bobHtlcTxs = bobRevokedCommit.htlcTxsAndRemoteSigs.collect { - case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, _) => + val bobHtlcTxs = bobRevokedCommit.htlcTxs.collect { + case txInfo: HtlcSuccessTx => val preimage = revokedCloseFixture.htlcsAlice.collectFirst { case (add, preimage) if add.id == txInfo.htlcId => preimage }.get assert(Crypto.sha256(preimage) == txInfo.paymentHash) txInfo - case HtlcTxAndRemoteSig(txInfo: HtlcTimeoutTx, _) => + case txInfo: HtlcTimeoutTx => txInfo } assert(bobHtlcTxs.map(_.input.outPoint).size == 4) @@ -2237,16 +2245,121 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchOutputSpentTriggered(bobHtlcTxs(0).amountIn, bobHtlcTx) alice2blockchain.expectWatchTxConfirmed(bobHtlcTx.txid) alice ! WatchTxConfirmedTriggered(BlockHeight(129), 7, bobHtlcTx) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.claimHtlcDelayedPenaltyTxs.size == 4) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.htlcDelayedOutputs.size == 4) val htlcDelayedPenalty = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-delayed-penalty")) val spentOutpoints = Seq(OutPoint(bobHtlcTx, 1), OutPoint(bobHtlcTx, 2), OutPoint(bobHtlcTx, 3), OutPoint(bobHtlcTx, 4)) assert(htlcDelayedPenalty.map(_.input).toSet == spentOutpoints.toSet) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.claimHtlcDelayedPenaltyTxs.map(_.input.outPoint).toSet == spentOutpoints.toSet) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.htlcDelayedOutputs == spentOutpoints.toSet) htlcDelayedPenalty.foreach(penalty => Transaction.correctlySpends(penalty.tx, bobHtlcTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) alice2blockchain.expectWatchOutputsSpent(spentOutpoints) alice2blockchain.expectNoMessage(100 millis) } + test("recv INPUT_RESTORED (revoked htlc transactions confirmed)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + import f._ + + // 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 bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) + val commitTx = bobRevokedCommit.commitTx + alice ! WatchFundingSpentTriggered(commitTx) + awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) + + // Alice publishes the penalty txs and watches outputs. + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") + val mainPenalty = alice2blockchain.expectFinalTxPublished("main-penalty") + val htlcPenalty = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) + alice2blockchain.expectWatchTxConfirmed(commitTx.txid) + alice2blockchain.expectWatchOutputsSpent(Seq(mainTx.input, mainPenalty.input) ++ htlcPenalty.map(_.input)) + alice ! WatchTxConfirmedTriggered(BlockHeight(700_000), 2, commitTx) + alice2blockchain.expectNoMessage(100 millis) + + // Bob claims HTLC outputs using aggregated transactions. + val bobHtlcTxs = bobRevokedCommit.htlcTxs + assert(bobHtlcTxs.map(_.input.outPoint).size == 4) + val bobHtlcTx1 = Transaction( + 2, + Seq( + TxIn(OutPoint(randomTxId(), 4), Nil, 1), // utxo used for fee bumping + bobHtlcTxs(0).tx.txIn.head, + TxIn(OutPoint(randomTxId(), 4), Nil, 1), // unrelated utxo + bobHtlcTxs(1).tx.txIn.head, + ), + Seq( + TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey)), // change output + bobHtlcTxs(0).tx.txOut.head, + TxOut(15_000 sat, Script.pay2wpkh(randomKey().publicKey)), // unrelated output + bobHtlcTxs(1).tx.txOut.head, + ), + 0 + ) + val bobHtlcTx2 = Transaction( + 2, + Seq( + bobHtlcTxs(2).tx.txIn.head, + bobHtlcTxs(3).tx.txIn.head, + TxIn(OutPoint(randomTxId(), 0), Nil, 1), // utxo used for fee bumping + ), + Seq( + bobHtlcTxs(2).tx.txOut.head, + bobHtlcTxs(3).tx.txOut.head, + TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey)), // change output + ), + 0 + ) + + // Alice reacts by publishing penalty txs that spend bob's htlc transactions. + val htlcDelayedPenalty = Seq(bobHtlcTx1, bobHtlcTx2).flatMap(bobHtlcTx => { + alice ! WatchOutputSpentTriggered(bobHtlcTxs(0).amountIn, bobHtlcTx) + alice2blockchain.expectWatchTxConfirmed(bobHtlcTx.txid) + alice ! WatchTxConfirmedTriggered(BlockHeight(700_004), 7, bobHtlcTx) + val htlcDelayedPenalty = (1 to 2).map(_ => alice2blockchain.expectFinalTxPublished("htlc-delayed-penalty")) + alice2blockchain.expectWatchOutputsSpent(htlcDelayedPenalty.map(_.input)) + htlcDelayedPenalty.foreach(penalty => Transaction.correctlySpends(penalty.tx, bobHtlcTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + htlcDelayedPenalty + }) + assert(htlcDelayedPenalty.map(_.input).toSet == Set(OutPoint(bobHtlcTx1, 1), OutPoint(bobHtlcTx1, 3), OutPoint(bobHtlcTx2, 0), OutPoint(bobHtlcTx2, 1))) + awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.htlcDelayedOutputs.size == 4) + + // We simulate a node restart after a feerate increase. + val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] + alice.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(5_000 sat))) + alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing) + alice ! INPUT_RESTORED(beforeRestart) + alice2blockchain.expectMsgType[SetChannelId] + awaitCond(alice.stateName == CLOSING) + + // We re-publish closing transactions with a higher feerate. + val mainTx2 = alice2blockchain.expectFinalTxPublished("remote-main-delayed") + assert(mainTx2.input == mainTx.input) + assert(mainTx2.tx.txOut.head.amount < mainTx.tx.txOut.head.amount) + Transaction.correctlySpends(mainTx2.tx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + val mainPenalty2 = alice2blockchain.expectFinalTxPublished("main-penalty") + assert(mainPenalty2.input == mainPenalty.input) + assert(mainPenalty2.tx.txOut.head.amount < mainPenalty.tx.txOut.head.amount) + Transaction.correctlySpends(mainPenalty2.tx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + alice2blockchain.expectWatchOutputsSpent(Seq(mainTx2.input, mainPenalty2.input)) + val htlcDelayedPenalty2 = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-delayed-penalty")) + alice2blockchain.expectWatchOutputsSpent(htlcDelayedPenalty2.map(_.input)) + assert(htlcDelayedPenalty2.map(_.input).toSet == htlcDelayedPenalty.map(_.input).toSet) + assert(htlcDelayedPenalty2.map(_.tx.txOut.head.amount).sum < htlcDelayedPenalty.map(_.tx.txOut.head.amount).sum) + htlcDelayedPenalty2.foreach { + case txInfo if txInfo.input.txid == bobHtlcTx1.txid => Transaction.correctlySpends(txInfo.tx, Seq(bobHtlcTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + case txInfo => Transaction.correctlySpends(txInfo.tx, Seq(bobHtlcTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + } + + // The remaining transactions confirm. + alice ! WatchTxConfirmedTriggered(BlockHeight(700_009), 18, mainTx.tx) + alice ! WatchTxConfirmedTriggered(BlockHeight(700_011), 11, mainPenalty2.tx) + // Some of the updated HTLC-delayed penalty transactions confirm. + htlcDelayedPenalty2.take(3).foreach(p => alice ! WatchTxConfirmedTriggered(BlockHeight(700_015), 0, p.tx)) + assert(alice.stateName == CLOSING) + // The last HTLC-delayed penalty to confirm is the previous version with a lower feerate. + htlcDelayedPenalty.filter(p => !htlcDelayedPenalty2.take(3).map(_.input).contains(p.input)).foreach(p => alice ! WatchTxConfirmedTriggered(BlockHeight(700_016), 0, p.tx)) + awaitCond(alice.stateName == CLOSED) + } + private def testRevokedTxConfirmed(f: FixtureParam, channelFeatures: ChannelFeatures): Unit = { import f._ assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures) @@ -2254,14 +2367,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 4 case DefaultCommitmentFormat => 2 } - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == initOutputCount) + assert(bob.signCommitTx().txOut.size == initOutputCount) // bob's second commit tx contains 2 incoming htlcs val (bobRevokedTx, htlcs1) = { val (_, htlc1) = addHtlc(35_000_000 msat, alice, bob, alice2bob, bob2alice) val (_, htlc2) = addHtlc(20_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx + val bobCommitTx = bob.signCommitTx() assert(bobCommitTx.txOut.size == initOutputCount + 2) (bobCommitTx, Seq(htlc1, htlc2)) } @@ -2272,7 +2385,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (_, htlc4) = addHtlc(18_000_000 msat, alice, bob, alice2bob, bob2alice) failHtlc(htlcs1.head.id, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == initOutputCount + 3) + assert(bob.signCommitTx().txOut.size == initOutputCount + 3) Seq(htlc3, htlc4) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index 5db35ffdbf..ee1206ce5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -261,8 +261,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { // we generate a few blocks to get the commit tx confirmed generateBlocks(8, Some(minerAddress)) // we wait until the htlc-timeout has been broadcast - assert(localCommit.htlcTxs.size == 1) - waitForOutputSpent(localCommit.htlcTxs.keys.head, bitcoinClient, sender) + assert(localCommit.htlcOutputs.size == 1) + waitForOutputSpent(localCommit.htlcOutputs.head, bitcoinClient, sender) // we generate more blocks for the htlc-timeout to reach enough confirmations generateBlocks(8, Some(minerAddress)) // this will fail the htlc @@ -317,8 +317,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { generateBlocks((htlc.cltvExpiry.blockHeight - getBlockHeight()).toInt, Some(minerAddress)) // we wait until the claim-htlc-timeout has been broadcast val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient) - assert(remoteCommit.claimHtlcTxs.size == 1) - waitForOutputSpent(remoteCommit.claimHtlcTxs.keys.head, bitcoinClient, sender) + assert(remoteCommit.htlcOutputs.size == 1) + waitForOutputSpent(remoteCommit.htlcOutputs.head, bitcoinClient, sender) // and we generate blocks for the claim-htlc-timeout to reach enough confirmations generateBlocks(8, Some(minerAddress)) // this will fail the htlc @@ -401,15 +401,19 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { val commitmentsF = sigListener.expectMsgType[ChannelSignatureReceived].commitments sigListener.expectNoMessage(1 second) assert(commitmentsF.params.commitmentFormat == commitmentFormat) + // we prepare the revoked transactions F will publish + val channelKeysF = nodes("F").nodeParams.channelKeyManager.channelKeys(commitmentsF.params.channelConfig, commitmentsF.params.localParams.fundingKeyPath) + val commitmentKeysF = commitmentsF.latest.localKeys(channelKeysF) + val revokedCommitTx = commitmentsF.latest.fullySignedLocalCommitTx(channelKeysF) // in this commitment, both parties should have a main output, there are four pending htlcs and anchor outputs if applicable - val localCommitF = commitmentsF.latest.localCommit commitmentFormat match { - case Transactions.DefaultCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) - case _: Transactions.AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8) + case Transactions.DefaultCommitmentFormat => assert(revokedCommitTx.txOut.size == 6) + case _: Transactions.AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(revokedCommitTx.txOut.size == 8) } - val outgoingHtlcExpiry = localCommitF.spec.htlcs.collect { case OutgoingHtlc(add) => add.cltvExpiry }.max - val htlcTimeoutTxs = localCommitF.htlcTxsAndRemoteSigs.collect { case h@HtlcTxAndRemoteSig(_: Transactions.HtlcTimeoutTx, _) => h } - val htlcSuccessTxs = localCommitF.htlcTxsAndRemoteSigs.collect { case h@HtlcTxAndRemoteSig(_: Transactions.HtlcSuccessTx, _) => h } + val outgoingHtlcExpiry = commitmentsF.latest.localCommit.spec.htlcs.collect { case OutgoingHtlc(add) => add.cltvExpiry }.max + val htlcTxsF = commitmentsF.latest.htlcTxs(channelKeysF) + val htlcTimeoutTxs = htlcTxsF.collect { case (tx: Transactions.HtlcTimeoutTx, remoteSig) => (tx, remoteSig) } + val htlcSuccessTxs = htlcTxsF.collect { case (tx: Transactions.HtlcSuccessTx, remoteSig) => (tx, remoteSig) } assert(htlcTimeoutTxs.size == 2) assert(htlcSuccessTxs.size == 2) // we fulfill htlcs to get the preimages @@ -435,24 +439,15 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { sender.send(nodes("C").register, Register.Forward(sender.ref.toTyped[Any], commitmentsF.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender))) sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]] val Right(finalAddressC) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("C").wallet.getReceivePublicKeyScript(renew = false)) - // we prepare the revoked transactions F will publish - val channelKeysF = nodes("F").nodeParams.channelKeyManager.channelKeys(commitmentsF.params.channelConfig, commitmentsF.params.localParams.fundingKeyPath) - val fundingKeyF = channelKeysF.fundingKey(commitmentsF.latest.fundingTxIndex) - val commitmentKeysF = commitmentsF.latest.localKeys(channelKeysF) - val revokedCommitTx = { - val commitTx = localCommitF.commitTxAndRemoteSig.commitTx - val localSig = commitTx.sign(fundingKeyF, commitmentsF.latest.remoteFundingPubKey) - val remoteSig = localCommitF.commitTxAndRemoteSig.remoteSig.asInstanceOf[ChannelSpendSignature.IndividualSignature] - commitTx.aggregateSigs(fundingKeyF.publicKey, commitmentsF.latest.remoteFundingPubKey, localSig, remoteSig) - } val htlcSuccess = htlcSuccessTxs.zip(Seq(preimage1, preimage2)).map { - case (htlcTxAndSigs, preimage) => - val localSig = htlcTxAndSigs.htlcTx.sign(commitmentKeysF, commitmentFormat, Map.empty) - htlcTxAndSigs.htlcTx.asInstanceOf[Transactions.HtlcSuccessTx].addSigs(commitmentKeysF, localSig, htlcTxAndSigs.remoteSig, preimage, commitmentsF.params.commitmentFormat).tx + case ((htlcTx, remoteSig), preimage) => + val localSig = htlcTx.sign(commitmentKeysF, commitmentFormat, Map.empty) + htlcTx.addSigs(commitmentKeysF, localSig, remoteSig, preimage, commitmentsF.params.commitmentFormat).tx } - val htlcTimeout = htlcTimeoutTxs.map { htlcTxAndSigs => - val localSig = htlcTxAndSigs.htlcTx.sign(commitmentKeysF, commitmentFormat, Map.empty) - htlcTxAndSigs.htlcTx.asInstanceOf[Transactions.HtlcTimeoutTx].addSigs(commitmentKeysF, localSig, htlcTxAndSigs.remoteSig, commitmentsF.params.commitmentFormat).tx + val htlcTimeout = htlcTimeoutTxs.map { + case (htlcTx, remoteSig) => + val localSig = htlcTx.sign(commitmentKeysF, commitmentFormat, Map.empty) + htlcTx.addSigs(commitmentKeysF, localSig, remoteSig, commitmentsF.params.commitmentFormat).tx } htlcSuccess.foreach(tx => Transaction.correctlySpends(tx, Seq(revokedCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) htlcTimeout.foreach(tx => Transaction.correctlySpends(tx, Seq(revokedCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) @@ -595,9 +590,6 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { Script.write(Script.pay2wsh(toRemote)) } - // toRemote output of C as seen by F - val Some(toRemoteOutC) = initialStateDataF.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.find(_.publicKeyScript == toRemoteAddress) - // let's make a payment to advance the commit index val amountMsat = 4200000.msat sender.send(nodes("F").paymentHandler, ReceiveStandardPayment(sender.ref.toTyped, Some(amountMsat), Left("1 coffee"))) @@ -620,16 +612,10 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { sender.send(nodes("F").register, Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender))) val stateDataF = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data val commitmentIndex = stateDataF.commitments.localCommitIndex - val commitTx = stateDataF.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val Some(toRemoteOutCNew) = commitTx.txOut.find(_.publicKeyScript == toRemoteAddress) - + val commitTxId = stateDataF.commitments.latest.localCommit.txId // there is a new commitment index in the channel state assert(commitmentIndex > initialCommitmentIndex) - // script pubkeys of toRemote output remained the same across commitments - assert(toRemoteOutCNew.publicKeyScript == toRemoteOutC.publicKeyScript) - assert(toRemoteOutCNew.amount < toRemoteOutC.amount) - val stateListener = TestProbe() nodes("C").system.eventStream.subscribe(stateListener.ref, classOf[ChannelStateChanged]) @@ -642,16 +628,17 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { awaitCond(stateListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSING, max = 60 seconds) val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient) - awaitCond({ - bitcoinClient.getTransaction(commitTx.txid).map(tx => Some(tx)).recover(_ => None).pipeTo(sender.ref) + val commitTx = awaitAssert({ + bitcoinClient.getTransaction(commitTxId).map(tx => Some(tx)).recover(_ => None).pipeTo(sender.ref) val tx = sender.expectMsgType[Option[Transaction]] // the unilateral close contains the static toRemote output - tx.exists(_.txOut.exists(_.publicKeyScript == toRemoteOutC.publicKeyScript)) + assert(tx.exists(_.txOut.exists(_.publicKeyScript == toRemoteAddress))) + tx.get }, max = 20 seconds, interval = 1 second) // bury the unilateral close in a block, C should claim its main output generateBlocks(2) - val mainOutputC = OutPoint(commitTx, commitTx.txOut.indexWhere(_.publicKeyScript == toRemoteOutC.publicKeyScript)) + val mainOutputC = OutPoint(commitTx, commitTx.txOut.indexWhere(_.publicKeyScript == toRemoteAddress)) awaitCond({ bitcoinClient.getMempool().pipeTo(sender.ref) sender.expectMsgType[Seq[Transaction]].exists(_.txIn.head.outPoint == mainOutputC) 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 d3f0e8c7db..ac2a500ecc 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 @@ -4,18 +4,16 @@ import akka.actor.typed.scaladsl.adapter.ClassicActorRefOps import akka.actor.{Actor, ActorContext, ActorRef, Props} import akka.testkit.{TestActorRef, TestProbe} import com.softwaremill.quicklens.ModifyPimp -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, OutPoint, Satoshi, TxHash, TxOut} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.bitcoin.scalacompat.{ByteVector64, Satoshi} import fr.acinq.eclair.TestConstants._ -import fr.acinq.eclair.channel.SpliceStatus.NoSplice -import fr.acinq.eclair.channel.{ChannelFeatures, ChannelFlags, ChannelIdAssigned, ChannelParams, CommitTxAndRemoteSig, Commitment, Commitments, DATA_NORMAL, LocalCommit, LocalParams, PersistentChannelData, RemoteCommit, RemoteParams, Upstream} +import fr.acinq.eclair.channel.{ChannelIdAssigned, DATA_NORMAL, PersistentChannelData, Upstream} import fr.acinq.eclair.io.Peer.PeerNotFound import fr.acinq.eclair.io.Switchboard._ import fr.acinq.eclair.payment.relay.{OnTheFlyFunding, OnTheFlyFundingSpec} -import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, Features, InitFeature, MilliSatoshiLong, NodeParams, TestKitBaseClass, TimestampSecondLong, UInt64, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, Features, InitFeature, MilliSatoshiLong, NodeParams, TestKitBaseClass, TimestampSecondLong, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits._ @@ -167,7 +165,7 @@ class SwitchboardSpec extends TestKitBaseClass with AnyFunSuiteLike { def dummyDataNormal(remoteNodeId: PublicKey, capacity: Satoshi): DATA_NORMAL = { val data = ChannelCodecsSpec.normal.modify(_.commitments.params.remoteParams.nodeId).setTo(remoteNodeId) - .modify(_.commitments.active).apply(_.map(_.modify(_.localCommit.commitTxAndRemoteSig.commitTx.input.txOut.amount).setTo(capacity))) + .modify(_.commitments.active).apply(_.map(_.modify(_.localCommit.input.txOut.amount).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 ee5ef3f741..42b8c18d9e 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 @@ -125,7 +125,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val localParams = LocalParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), 546 sat, Long.MaxValue.msat, Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) val remoteParams = RemoteParams(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), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), IndividualSignature(ByteVector64.Zeroes)), Nil) + 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 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"), @@ -207,13 +207,20 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "localCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 100000000, "toRemote": 50000000 }, - | "commitTxAndRemoteSig": { "commitTx": { "txid": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", "tx": "02000000000000000000" },"remoteSig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - | "htlcTxsAndRemoteSigs": [] + | "txId": "0202020202020202020202020202020202020202020202020202020202020202", + | "input": { + | "outPoint":"0202020202020202020202020202020202020202020202020202020202020202:0", + | "amountSatoshis": 150000 + | }, + | "remoteSig": { + | "sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + | }, + | "htlcRemoteSigs": [] | }, | "remoteCommit": { | "index": 0, | "spec": { "htlcs": [], "commitTxFeerate": 2500, "toLocal": 50000000, "toRemote": 100000000 }, - | "txid": "0202020202020202020202020202020202020202020202020202020202020202", + | "txId": "0202020202020202020202020202020202020202020202020202020202020202", | "remotePerCommitmentPoint": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" | } | } 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 a8d63586bb..b52d9acef6 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 @@ -18,7 +18,7 @@ package fr.acinq.eclair.payment import akka.actor.ActorRef import fr.acinq.bitcoin.scalacompat.DeterministicWallet.ExtendedPrivateKey -import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxIn, TxOut} +import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxOut} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features._ import fr.acinq.eclair.TestUtils.randomTxId @@ -34,7 +34,6 @@ import fr.acinq.eclair.payment.send.{BlindedRecipient, ClearRecipient, Trampolin 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 import fr.acinq.eclair.transactions.Transactions.InputInfo import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} @@ -749,7 +748,7 @@ object PaymentPacketSpec { val remoteParams = RemoteParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, None) 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, CommitTxAndRemoteSig(Transactions.CommitTx(commitInput, Transaction(2, Seq(TxIn(commitInput.outPoint, Nil, 0)), Seq(TxOut(testCapacity, Nil)), 0)), IndividualSignature(ByteVector64.Zeroes)), Nil) + val localCommit = LocalCommit(0, null, randomTxId(), commitInput, IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, null, randomTxId(), randomKey().publicKey) val localChanges = LocalChanges(Nil, Nil, Nil) val remoteChanges = RemoteChanges(Nil, Nil, Nil) 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 24f2a95e79..b0ce04a24d 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 @@ -34,7 +34,6 @@ import fr.acinq.eclair.payment.relay.{OnTheFlyFunding, PostRestartHtlcCleaner, R import fr.acinq.eclair.payment.send.SpontaneousRecipient import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate import fr.acinq.eclair.router.Router.Route -import fr.acinq.eclair.transactions.Transactions.{ClaimRemoteDelayedOutputTx, InputInfo} import fr.acinq.eclair.transactions.{DirectedHtlc, IncomingHtlc, OutgoingHtlc} import fr.acinq.eclair.wire.internal.channel.{ChannelCodecs, ChannelCodecsSpec} import fr.acinq.eclair.wire.protocol._ @@ -426,7 +425,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit val (closingState, closingTxs) = localClose(alice, alice2blockchain, htlcTimeoutCount = 4) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, closingState.commitTx) // All committed htlcs timed out except the last two; one will be fulfilled later and the other will timeout later. - assert(closingState.htlcTxs.size == 4) + assert(closingState.htlcOutputs.size == 4) assert(closingTxs.htlcTxs.size == 4) val htlcTxs = closingTxs.htlcTxs.sortBy(_.txOut.map(_.amount).sum) htlcTxs.reverse.drop(2).zipWithIndex.foreach { @@ -511,10 +510,8 @@ 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 = normal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.copy(txOut = Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey)))) - val dummyClaimMainTx = Transaction(2, Seq(TxIn(OutPoint(revokedCommitTx, 0), Nil, 0)), Seq(revokedCommitTx.txOut.head.copy(amount = 4000 sat)), 0) - val dummyClaimMain = ClaimRemoteDelayedOutputTx(InputInfo(OutPoint(revokedCommitTx, 0), revokedCommitTx.txOut.head, ByteVector.empty), dummyClaimMainTx) - val rcp = RevokedCommitPublished(revokedCommitTx, Some(dummyClaimMain), None, Nil, Nil, Map(revokedCommitTx.txIn.head.outPoint -> revokedCommitTx)) + val revokedCommitTx = Transaction(2, Seq(TxIn(normal.commitments.latest.localCommit.input.outPoint, 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/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index eef61fb583..9af49f533a 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,7 +17,7 @@ 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, TxIn} +import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector64, Crypto, DeterministicWallet, Satoshi, SatoshiLong, Transaction, TxId} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature @@ -68,11 +68,7 @@ class ChannelCodecsSpec extends AnyFunSuite { hex"304502210093fd7dfa3ef6cdf5b94cfadf83022be98062d53cd7097a73947453b210a481eb0220622e63a21b787ea7bb55f01ab6fe503fcb8ef4cb65adce7a264ae014403646fe01" ) - val sigs = c.commitments.latest - .localCommit - .htlcTxsAndRemoteSigs - .map(data => Scripts.der(data.remoteSig)) - + val sigs = c.commitments.latest.localCommit.htlcRemoteSigs.map(Scripts.der(_)) assert(ref == sigs) } @@ -191,46 +187,45 @@ class ChannelCodecsSpec extends AnyFunSuite { negotiating.closingTxProposed.flatten.foreach(tx => assert(tx.unsignedTx.toLocalOutput_opt.isEmpty)) val normal = channelDataCodec.decode(dataNormal.bits).require.value.asInstanceOf[DATA_NORMAL] - assert(normal.commitments.latest.localCommit.htlcTxsAndRemoteSigs.nonEmpty) - normal.commitments.latest.localCommit.htlcTxsAndRemoteSigs.foreach(tx => assert(tx.htlcTx.htlcId == 0)) + assert(normal.commitments.latest.localCommit.htlcRemoteSigs.nonEmpty) val closingLocal = channelDataCodec.decode(dataClosingLocal.bits).require.value.asInstanceOf[DATA_CLOSING] assert(closingLocal.localCommitPublished.nonEmpty) assert(closingLocal.localCommitPublished.get.commitTx.txOut.size == 6) - assert(closingLocal.localCommitPublished.get.htlcTxs.size == 4) - assert(closingLocal.localCommitPublished.get.claimHtlcDelayedTxs.size == 4) + assert(closingLocal.localCommitPublished.get.htlcOutputs.size == 4) + assert(closingLocal.localCommitPublished.get.htlcDelayedOutputs.size == 4) assert(closingLocal.localCommitPublished.get.irrevocablySpent.isEmpty) val closingRemote = channelDataCodec.decode(dataClosingRemote.bits).require.value.asInstanceOf[DATA_CLOSING] assert(closingRemote.remoteCommitPublished.nonEmpty) assert(closingRemote.remoteCommitPublished.get.commitTx.txOut.size == 7) assert(closingRemote.remoteCommitPublished.get.commitTx.txOut.count(_.amount == AnchorOutputsCommitmentFormat.anchorAmount) == 2) - assert(closingRemote.remoteCommitPublished.get.claimHtlcTxs.size == 3) + assert(closingRemote.remoteCommitPublished.get.htlcOutputs.size == 3) assert(closingRemote.remoteCommitPublished.get.irrevocablySpent.isEmpty) val closingRevoked = channelDataCodec.decode(dataClosingRevoked.bits).require.value.asInstanceOf[DATA_CLOSING] assert(closingRevoked.revokedCommitPublished.size == 1) assert(closingRevoked.revokedCommitPublished.head.commitTx.txOut.size == 6) - assert(closingRevoked.revokedCommitPublished.head.htlcPenaltyTxs.size == 4) - assert(closingRevoked.revokedCommitPublished.head.claimHtlcDelayedPenaltyTxs.size == 2) + assert(closingRevoked.revokedCommitPublished.head.htlcOutputs.size == 4) + assert(closingRevoked.revokedCommitPublished.head.htlcDelayedOutputs.size == 2) assert(closingRevoked.revokedCommitPublished.head.irrevocablySpent.isEmpty) } - test("verify that we don't store local sigs for local commitment") { - case class TestCase(localFundingPublicKey: PublicKey, remoteFundingPublicKey: PublicKey, encoded: ByteVector) + test("migrate remote commit sigs from local commit") { + case class TestCase(localFundingPublicKey: PublicKey, remoteFundingPublicKey: PublicKey, commitTx: Transaction, encoded: ByteVector) val testCases = List( - TestCase(PublicKey(hex"020d9b51f544c7a1c061a6604badd79d068a7e1078ee06a013e17a39c13cb302f2"), PublicKey(hex"028b8f6f6eb0ca68d25760a419c007d7e74c2228ee53448042e6e06b1eae254e5e"), hex"00000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000400633f73979e2e7cc9a76f2dbcccf4d9000000000000022200000002540be40000000000000003e800000000000000010090001e000bd48a6ca0842ad7ec52d29f4be4c7015ea447e435e5404380000000c5019a59a1964d2e91fa2ae22c629bd57e7458b98373a3503a6b3675778cc09c3298000000000000011e8000000002f34f6000000000000001f400000000000001f4004800f18145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f01df72613647456031c11cf28c90e06230848223b1064616b89bb2e8d2557c631f011d20840603112fa5faaa4053f09f9f50bcdf94f392941569ce09310340dc50020117810d980eb608cae903683abfc8438fef5421397e688d8c4aa3a47e0fc3418601b035c2fabbe71271d3a07be023eae63e4ac5272bd09a6b990a5409160f3ab28a000000008400800000000000000000000000186a00000000000000000000000002faf08000124e7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d76463800000000015d04300800000000011001070d0d1c4bd6ad8c9b7bcb36f6308b955dc28e4970b43991232fe217bf4a56deb0023a9108106cda8faa263d0e030d33025d6ebce83453f083c77035009f0bd1ce09e598179108145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f2957009801000000000080ce7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d7646380000000004e14ca4000a3318080000000000b000a0b0517d7dabd6f2da1a5bfef97681bd4958a94018200241822811080579dbcb14b3d3f2c7200693391d43977881357806ebd47b2633d0c1b5ac1a56481103c3a8f6a5e15cab0144f99ec5f875fabbdf32ef6748326f4a5b3a1b29b8c7f9e80a418228110806b4c99af1b3c8f74bef604336f1a1200a754f1680923a511f7af92391bac39ab811019085fba36cec85b5537807d07f2931858413a56c47b2cf7753fbcf4cc7206bc80a3a9108106cda8faa263d0e030d33025d6ebce83453f083c77035009f0bd1ce09e598179108145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f2957435cc1100000000000000000000000000000186a0000000002faf08000000000000000005fbb84fad0781822e1268df19eb42468a5041d0b44dc8c7a21aa7aae19409bab81e514d9069fc6064c17f35d1d0f227fbe1b74ca89fec6c67b9668fe96341d43e200000000000000000000000000000000000000000000000000000000000040ee389313797b7f5177522d46afba5c43e37edebee6598c72e5a94c81fcc50a840009273fbf78b1a6e126d34c0e73d82e357ed5724854114e96ca3d373f2c30ebb231c0000000000ae821804000000000088008386868e25eb56c64dbde59b7b1845caaee14724b85a1cc89197f10bdfa52b6f58011d488408366d47d5131e87018699812eb75e741a29f841e3b81a804f85e8e704f2cc0bc8840a2e3dbdbac329a3495d829067001f5f9d3088a3b94d12010b9b81ac7ab89539794ab8000139fdfbc58d3709369a60739ec171abf6ab9242a08a74b651e9b9f961875d918ece7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d76463cc80398ba555ae3310a427fe5a79299e970d0b199fdade631246d123ea6324e58f40736fc2307f587e516761e35b11c3d960991f1d905d523ff0cbdd88d527790"), - TestCase(PublicKey(hex"03038461f9a84dddcf1a1f8adf424a2aa5250b1ab0ad53505d7face53a1b70cc9d"), PublicKey(hex"02c5d5b5f7291d11c052ce00bde1b4517d309f2b73f99c798fc3e3329b215e3319"), hex"000002010000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340009c5b105f42a2aea346cf8759f7c78135158b93878faac0659b26f723cc549fded80000000000000000000022200000004a817c8000000000000028f5c000000000000000102d0001e000b000a490f9af5166835d37b0f2155fa3a7a2b4fae66fd8000000105450189fed9a69c67c55d9743b3dde05b031088442ad94e9e3437e15f479f5cd66a7e000000000000011e80000001eefffe5c00000000000147ae00000000000001f403f000f18162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c8179a7ba86216d011a9c1c49684578a6e0ca111279e6641404dee194eabcbbd21c81aaa1f3d35df2711a08374da8278fe259f6085189cc539a144c1aa0744b23495601331777d0354951fa354a8a2ee70e2bf40c9bb3f4fe340c9dab02bba5b6bb1aa00158bc5a03c0c79b947e5ac70f76f600c7fab8561e244f1425935fc51ffbe330380000000181515080800000000000000000000000007e800000000000000000000001f3fffe0c00121c2ba37173c23b01a1781e65cdf373e97395526f9e855164dac95c37502e9af3800000000015ffffff80000000001100101621d570811311537fddef81ee6f7023e2d17c11d32a53f472887dd035a78a518023a9108162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c908181c230fcd426eee78d0fc56fa125155292858d5856a9a82ebfd6729d0db8664ea9570097010000000000809c2ba37173c23b01a1781e65cdf373e97395526f9e855164dac95c37502e9af380000000005657b04000a47fff80000000000b000a66e6e46b946f3259039de2840809a4b340aa510e8200239822011030949de6ac0462e1b5a42ded6c7763779d9f6aab069cb3366a3ca330bd1fe52b81101564903650deeb2a1f719865742e99ea0986774184727607a69bfff9f552a45a00a398220110228710b1083bde27eeb793ad155142cb69655ad8424eeae2044808edc631840d8110225098637d7f91c20e00d4a82a56520ad3cead728dd5b82b0bdcfe40d4b6acfb00a3a9108162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c908181c230fcd426eee78d0fc56fa125155292858d5856a9a82ebfd6729d0db8664ea95773ad9b100000000000000000000000000000007e80000001f3fffe0c000000000000000063aa3a9e7e5e45c265ae8bf12e96a239ddbae4224c6f4560e149cce8a2675b208178d03c15e7303b3415e3549606ecab96ff5250ce38b2052b0a9dd2ed0af5ee75800000000000000000000000000000000000000000000000000000000000409cb1d9e3463cd48a65e9a2fa95f0755e924dba3664026d3682892bbf9527396bc0090e15d1b8b9e11d80d0bc0f32e6f9b9f4b9caa937cf42a8b26d64ae1ba8174d79c0000000000affffffc0000000000880080b10eab8408988a9bfeef7c0f737b811f168be08e99529fa39443ee81ad3c528c011d48840b1756d7dca47447014b3802f786d145f4c27cadcfe671e63f0f8cca6c8578cc64840c0e1187e6a137773c687e2b7d0928aa94942c6ac2b54d4175feb394e86dc332754ab8000070ae8dc5cf08ec0685e0799737cdcfa5ce5549be7a1545936b2570dd40ba6bce32de50000008000070ae8dc5cf08ec0685e0799737cdcfa5ce5549be7a1545936b2570dd40ba6bce0656b85b7c0e2dd09a620e28ddf865c6197074354c922bb9b76fb27ffe7fbf19cc0"), - TestCase(PublicKey(hex"03571382973ec0873b6668463dff0e0d426e85059e30888c448416eaad4849061b"), PublicKey(hex"023c22f28ad8e7aef1e0e7c2ea8902768933ca0a7f391591f3f63e1d66fe8b948e"), hex"00000203933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400041507665ff588dba1b9d4949e739ad05b0000000000000222000000003b9aca00000000000002718000000000000000010090001e000bd48a65a1ef54e50a013d73e46815abed7672835c83f34380000000c101d9ed460a901187b39b2c00d859f885104116abf778bf31c369342e4147db56e4800000000000011e80000001d87278e000000000000138c000000000000001f403c200f1811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca4701e06722e502349a2857e99bf9299fe781a343aebce7557c0c3738e20f14b9e96f81985a99219fd30dcdf40438cc4d01d6027087f65d6dea4e0cd9864016febcbcbf8176c51f0ee44572f83a8a02f50053ed3cfcedbf5ce6c70f70970f5784c0bb0dfe0195a18fc266e7e7f1dabcc5234f493620de9f8e8d5932de3e8a44626974e0dd1080000000c100000000000000000000000005b8d8000000000000000000000001dd3826e0001208e981c8344120b2dce21aadce8a1ad9eb0123a574d898931d3d1449a33966f10080000000158c2b7a00000000001100101395ec5380c9641495ac8c4bb01605cf6ac58ee89a33da93d5eb09ab4be0e0ab8023a910811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca471081ab89c14b9f60439db334231eff8706a1374282cf18444622420b7556a424830da95700980100000000008088e981c8344120b2dce21aadce8a1ad9eb0123a574d898931d3d1449a33966f100800000003eb8c1c0008006f600000000000b000a0eb37e79f65de8a4ca4df80979505338bcea6e76020024182281108070acce2513bf0df155ec51404333ac0397b6015bff2d87120d51d8dbf0b0f698811013a616842936434a093f8e9f85cbccb846ec2e0c49c9b541d46563cfb181886b00a418228110807ab650f61af7ce294df5a26e8cc1abb3658f0201c0cb9a2484e43fb9e61622d081102767e9f7608de2330774556121ddbd48c7a45d9598e38c821a3d9846c37e75f580a3a910811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca471081ab89c14b9f60439db334231eff8706a1374282cf18444622420b7556a424830da957360f66900000000000000000000000000005b8d800000001dd3826e000000000000000005ef1d8da098d1f363033e64f2272a12b638bd3fe501c829d8350e8f3fc1c6b1481f12f3b5425786f43870f00174ddd36710d1864bbcf24aa54694cb183b43ebcbf000000000000000000000000000000000000000000000000000000000000408200a48c21439ebc4d7c70b310000bef69e454f808dee73be19aa960d54cac8100090474c0e41a2090596e710d56e7450d6cf58091d2ba6c4c498e9e8a24d19cb37880400000000ac615bd000000000008800809caf629c064b20a4ad64625d80b02e7b562c7744d19ed49eaf584d5a5f07055c011d488408f08bca2b639ebbc7839f0baa2409da24cf2829fce45647cfd8f8759bfa2e5238840d5c4e0a5cfb021ced99a118f7fc383509ba141678c2223112105baab52124186d4ab8000023a60720d10482cb73886ab73a286b67ac048e95d362624c74f451268ce59bc62b21ec0008d8000223a60720d10482cb73886ab73a286b67ac048e95d362624c74f451268ce59bc6049c6fe56892029844393a0afbf8c2c19088f565e80764b75a7e0591b97c1108ac0"), - TestCase(PublicKey(hex"02022b6d4bc22a31b82b31f79006031e7b0fdb58f1bcdcaa89ff0d1759700690b8"), PublicKey(hex"02f9cb479f8f313003191a891347276348ded1214f34ace6ff0cf7f4ad8a235471"), hex"000005010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400045d37887e8d909085b00af1d8b09c514b000000000000022200000004a817c8000000000000000b18000000000000000102d0001e000bd48a3d3ca19286d69a373eb6dda039da0cccf0bb6330c380000000c501df7e8244b99cc46d750bcef5830755886bad20416438b5973b3c8174f9d85b93000000000000011e80000000086131ec000000000000058c00000000000001f4004800f1817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa388107f85642fedecfcb7ef3cea6b38e26aead2759263d123692fd0c7d458fbc188881580b52cf6cdb449abc060bce9db3f35eebec2c87b316fd7a2c3193296d4a6e2e81fd2e032dd33ba9fa974246fccfc3ab5ccc1eb9ede9c900fc4e5d455c42150b3a8113237ef34839fc8055cbb63c0b9892defe6554c9dc70813681c2f3f35dedc13300000000c08080000000000000000000000010de8000000000000000000000000876dccc00125f6d0ab2880e92007f31c38dc0a8460c95a4c15291a24c92af1bca9264d8a915808000000015b7aa82000000000011001004a9c0705b42fa733f200639ed816601f0f413b9f29616f9c23f02f0dfc1df7e0023a910810115b6a5e11518dc1598fbc803018f3d87edac78de6e5544ff868bacb803485c10817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa38a957009781000000000080df6d0ab2880e92007f31c38dc0a8460c95a4c15291a24c92af1bca9264d8a91580800000003df2714000811e8200000000000b000a11e33689533b2499781f9b2f9c1893add8c57c4e02002418228110806567eeed21d6e87a045f3debf5ca10a05339c25f021400d96f7b492a957a94d301101df62a1c365062cce3b9a3ef530a4cd37eae4346491ed37e050dfd54213ef54f00a3982201103484562bcbb22ae3d892808bb04ff25f309a4732474a57bf6dd7691e17fc3df6811006854f362bcec61184d4721dfd30f777485be3580cd116564d139668bb6ee3a280a3a910810115b6a5e11518dc1598fbc803018f3d87edac78de6e5544ff868bacb803485c10817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa38a9573b51e910000000000000000000000000000010de800000000876dccc000000000000000025f43cb5a86018845618974486d0cb225355649b88690a914ee6cfec1a75286481d9daff2fcb7edb8d99f8fab9f77ff1033d5fc33a42ea7f8b70d29614b6d3195d000000000000000000000000000000000000000000000000000000000000409799acc6bbef72e7bc3dbed7831ac4e4ecfdad169d19628429edaa495d3c79ef00092fb68559440749003f98e1c6e05423064ad260a948d12649578de549326c548ac0400000000adbd54100000000000880080254e0382da17d399f90031cf6c0b300f87a09dcf94b0b7ce11f81786fe0efbf0011d48840808adb52f08a8c6e0acc7de40180c79ec3f6d63c6f372aa27fc345d65c01a42e0840be72d1e7e3cc4c00c646a244d1c9d8d237b44853cd2b39bfc33dfd2b6288d51c54ab800017db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a4557db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a454002f5228f4f2864a1b5a68dcfadb7680e7683333c2ed8cc30f7db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a454002c0028c95c468f1c569d43c9af6d335b8820bdda4888fe000200000"), - TestCase(PublicKey(hex"03d57bc818cee6523ac976e32c532baffa68028e165ca74640663bf2ac4256182e"), PublicKey(hex"03efea3bb157f8b21fd6cad3adecad654c40be673d852fcf9204737e8154865d3a"), hex"00000503933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000454699830eddf332655e0ff86e6c536f30000000000000222000000003b9aca0000000000000186a000000000000000010090001e000bd48a482e9d4f3df4621c059bdf7df20f5ec9c4df26364380000000c101bf0694efa44b50d2817c3ac271bd38ad5110d72162deb6328cc919097ed7727f8000000000000111000000001dcd6500000000000000c35000000000000000008048000f01f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d01125974b8fc60d0122160b22db09edf15265c9f8d42c407a4b6e286445ce2897e01c8dfdd96184b8a8a0faa4d5d92acea243bb257e2bf911767efbf3e0fee216d3b81f4207be48f4ebbc397fb0bf3d223696ce431919a583ad69a7f3825323977b38a811b7e73e5b685f78f56e8d250e68d99452deed356a826fb5f207d314d0b61b97780000000c50000000000000000000000000001770000000000000000000000012a05f20000121a8b778cb9a0d244c835acbedbfc43713175d98e2d1a48316af9b751f4c75686800000000015c04b4c000000000011001029e7f0fe79b14835816eb3cb89338bf442f040ee84ea6fdfb401ac2aac96919a0023a91081eabde40c6773291d64bb71962995d7fd3401470b2e53a320331df956212b0c171081f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d29570097810000000000809a8b778cb9a0d244c835acbedbfc43713175d98e2d1a48316af9b751f4c7568680000000003b78dfc000b0ca4c00000000000b000a62d6449887f18b11f7c38a7d73a58620a23bda4682002418228110805b5b0d8c18572b99a6496d58eefe9bc20bcf359d91cc480c022591ba7c86d2280110096244c4df48199bd71b654c29ac49aa2a1dfcb6f2554a89b7d38bd28de98f6200a39822011013d445ecdc92ced0ebe8920580d3e5e7a1cdfd19fd39a5e57495a80e8a7cc0630110163a3f38ba3f23acc687ac397fb98d8dedb9a07a4085dec28d5a792e9ea2aece00a3a91081eabde40c6773291d64bb71962995d7fd3401470b2e53a320331df956212b0c171081f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d29570cf71b1000000000000000000000000000000177000000012a05f200000000000000000054e507323b7ab08f00c834e2ba694233f9d50293312291fc893691e512805f45814c861ee65261b9b7bdaaaebd59d316e5bf00417c2c9afeb5db143096feff164480000000000000000000000000000000000000000000000000000000000040b625f1f1e5239ce80103faf7851d4adbfbed7cf5bace51b959d5d6050f88e65d40090d45bbc65cd06922641ad65f6dfe21b898baecc7168d2418b57cdba8fa63ab4340000000000ae025a6000000000008800814f3f87f3cd8a41ac0b759e5c499c5fa21782077427537efda00d615564b48cd0011d48840f55ef20633b9948eb25db8cb14caebfe9a00a3859729d190198efcab1095860b8840fbfa8eec55fe2c87f5b2b4eb7b2b5953102f99cf614bf3e4811cdfa05521974e94ab800006a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a6a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a002f522920ba753cf7d18870166f7df7c83d7b27137c98d90e6a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a002f522994760b5d8d708a41fe54bdd87888baf41f10e7bd0e000200000"), - TestCase(PublicKey(hex"0297d3589bbeddeb079bcd9495d52cb47ea20515f962501f0de15a8f4a66293a11"), PublicKey(hex"0242199d21cc7a2fac5c4168b6c3635bb0cc63ed4b920651833f8395f010bcc01c"), hex"00000603933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340004004c3bfd866d20986aa90ec265fdfcbe0000000000000222000000012a05f20000000000000186a000000000000000010090001e000bd48a3b48ca7482f0442970651ba40bf45091fddef3a84380000000c501a0528fcd6c721246634deb024236eb2f4c124e13b587093a6652e7bfc12db3250000000000000111000000009502f900000000000000c350000000000000000083f0000f01210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e0110d8d9aa7be8b4553b0af05b921c72c48356c4a0af61ffed7f1eadb7c5071c64816bf131172fe61d3145d39511755ee8095927534b76c4bb2464b706eaaee1486e01dd6caa8417804e26ce56f83a2525aa05fba25d3bab6db2cae5a203ce126da1ec01c8dfca1943df0dd0d59ffda66e207316c4af1cd3a4b6443fc5298a7a3864658f00000001404500000000000000001400000000017700000000015b95aa0000000128aa5c560012338c4e025330542050c2387b69ebb64e9c884113bb1045e996ae73e2b1b00a45800000000015c04b4c00000000001100104c97a415e599a3817965ff9a64e87d2af9bbcd77cae148ea934f9488216ffee10023a91081210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e10814be9ac4ddf6ef583cde6ca4aea965a3f51028afcb1280f86f0ad47a533149d08a95700ad01000000000080b38c4e025330542050c2387b69ebb64e9c884113bb1045e996ae73e2b1b00a4580000000003eef56c0017b588000000000001100100783bea5072cc7b3be1475a5a8d7c45f16978bbc8b914937c89631a4e7f0d7e3b5714b80000000000b000a0c933f5731198ebb4e0fda5b7ba130c1c4d2fc870200239822011032e32cd21310cf1864c862bb32b9c78b1b8a626d1094d47d0e1938166388243f81101bbf4ba992687498419d57da8e6d700809fdf1f882425296292ff55b67ec82bd80a4182281108054815d830690f798ccd75722576882f62a088d37e81cde50c40bc5b080615c3e011030965e18229cbf3a03024a97988d75cb1b1569eaa1ce381d0689958161c4799900a3a91081210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e10814be9ac4ddf6ef583cde6ca4aea965a3f51028afcb1280f86f0ad47a533149d08a9571b2c3e90000000000000000000140000000001770000000128aa5c5600000000015b95aa392494f078bec5b681d4997f2497d52112ad54eaaa81dec63f6480336139048b81fc2e019907aefb5efc5922187f575e10635611bde69ae0bd46b0b6ea44aea7748000000000000000000000000000000000000000000000000000000a000040f7dd9de11426234d0121ec1f5d2d19c3b4e78679b2e07a5c4f91bc624458352bc00919c6270129982a1028611c3db4f5db274e442089dd8822f4cb5739f158d80522c0000000000ae025a60000000000088008264bd20af2ccd1c0bcb2ffcd32743e957cdde6bbe570a47549a7ca4410b7ff708011d4884090866748731e8beb17105a2db0d8d6ec3318fb52e4819460cfe0e57c042f30070840a5f4d626efb77ac1e6f36525754b2d1fa881457e589407c37856a3d2998a4e8454ab8000800ec0003ffffffffff80106b5a98105912cee7f3cd2a4ed131283ee6fa4f5aef2e74aec08ebc2d6770ee38001e80007fffffffffec0082e868d9d8ebcc94bff771ca9d328bccfdfafdbc17e2b078b728caa0bb7179c6420001ffffffffffb0ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c02916000200e60400000002ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c029160000000001fffffffe05ed620000000000002f5228ed2329d20bc110a5c1946e902fd14247f77bcea10f17c52e00000000002f52295b2c2f65d18b177c1a91981b0335c8d209f93ea10e000000000002029e04000000000202ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c029160000000001fffffffe05ed620000000000002f5228ed2329d20bc110a5c1946e902fd14247f77bcea10f17c52e00000000002f52295b2c2f65d18b177c1a91981b0335c8d209f93ea10e08008e608804401c9f21ea6ff52325709bc8fec460de1d9f584cbf6aced6abeda5842595fbb968044076a3a4b918db66a59ee47c4d3a2f8bb0f17665e6d904df5cab74ee06cbafbb3e028e60880440b6af6af4787b1229039a7351f7156d0cc30755a1902c6d483ea85915a29b40f6044023ea5be6088489f6dda062498c485465c7451d7bf49eb46dae206bf952ddc860028ea4420484333a4398f45f58b882d16d86c6b76198c7da97240ca3067f072be02179803842052fa6b1377dbbd60f379b292baa5968fd440a2bf2c4a03e1bc2b51e94cc527422a55c0000000000000"), - TestCase(PublicKey(hex"03455b390d9fbc70d538f3e38abaf1a217e66b45c87ff025f3be7878697a84796a"), PublicKey(hex"03c6b8a41701c6abdc9951432c94873cfac9fa5c3bbad8215e28012c8e212748d8"), hex"000008010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340004515822333393159ec64e3694540844cd000000000000022200000004a817c80000000000000003e8000000000000000102d0001e000bd48a04a8223d31a871ecced64af1c4690532d4b5de984380000000c5012e53ad31acde84a88f55e981f2c33e8d6b451e15463d3da9335dbe9e30a474c60000000000000111000000009502f90000000000000001f400000000000000008168000f01e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c0126a65afe85868caba0a468ae6adf48b02e3dbd527a7d6e86c8b6426af06a5cce81ece00b5452805039314df65a98f5eeb21eab3d917853cf49567c28392e094806012f5f8f9177f320504b51ee8112a62fb5c505b8d9167737731545d99141d3485501004c1a5ece2ff034dcc63ec008df0ec8185934a2fcaf3075a75a5a999989d96580000000c50080000000000000000000000057e400000000000000000000000002faf08000125d32c79d17913addb93e6c7b952be8c2f93718867c1e39098daa4b85fc56a26c800000000015d04300800000000011001006333ad899044f5d624983aa15a497eb65df4372319091299696efb7f3d7b4cd8023a91081a2ad9c86cfde386a9c79f1c55d78d10bf335a2e43ff812f9df3c3c34bd423cb51081e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c2957009781000000000080dd32c79d17913addb93e6c7b952be8c2f93718867c1e39098daa4b85fc56a26c80000000003b1f4bc000ae038080000000000b000a12c0490f3a2d18421362bc7a0b9aa7302410903302002418228110807b9455b3bbed8d0994cf1fd0d8fcd2e63a03710f470943da74ef0f4513fc6f43811009275b2d4020f8bfc179ae4dd5cb3935c30767f48ee723cca0d708184b01f2c400a3982201102f192e0d772fc5b08cba1e7b1c39dbed4f64439569c493af18a9539818a6f36c0110272c9b5f9058e69485ca53388311c00ff69b5631334ed4e002cef0958389e42880a3a91081a2ad9c86cfde386a9c79f1c55d78d10bf335a2e43ff812f9df3c3c34bd423cb51081e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c295717042f10000000000000000000000000000057e40000000002faf080000000000000000046ec68ce2b1bca12747a13122270527bdad325cd34478d112adcc4bd0fc25f1e814f2919e34f1842677f99901e2aaf7f0abfafd39e6350238b52b8c2bf3b00ff7f00000000000000000000000000000000000000000000000000000000000040f5ca8901722651214939b99ca3c79307f3380af44ae1aecd4d317784fda7a15d80092e9963ce8bc89d6edc9f363dca95f4617c9b8c433e0f1c84c6d525c2fe2b513640000000000ae82180400000000008800803199d6c4c8227aeb124c1d50ad24bf5b2efa1b918c84894cb4b77dbf9ebda66c011d48840d156ce4367ef1c354e3cf8e2aebc6885f99ad1721ffc097cef9e1e1a5ea11e5a8840f1ae2905c071aaf7265450cb2521cf3eb27e970eeeb608578a004b238849d23614ab8000174cb1e745e44eb76e4f9b1ee54afa30be4dc6219f078e42636a92e17f15a89b2000000005d353b426e9963ce8bc89d6edc9f363dca95f4617c9b8c433e0f1c84c6d525c2fe2b513660192a0c75d44f2a6f4e1bc66fddf10b2949b9896cbe8548edeba49c0dd8300d82f42ef7330fb4601c7f2e1af3be67e3c3e86bb4c93a3bd9085c53732111c42dc"), - TestCase(PublicKey(hex"03d4c8565cb50638dc9a36d147e271aa4b052af498adb423ed1402922c1e9e491a"), PublicKey(hex"02a4adefc639aff3bcc872187529c75bea3a199857a002645e99991a942647f6fd"), hex"000009010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000411bde5f686930a7085566a22fd7fac49000000000000022200000004a817c80000000000000005dc000000000000000102d0001e000bd48a142af4dfb86cd991ad356e17ef245bd58413568ec3800101000000c5017ebac21a9e7d42c23db38b88b689f1efb9ad52d0e728e4553493dcde20dff8f70000000000000111000000009502f90000000000000002ee000000000000000083f0000f015256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e81290a232a7ee5a18660742d9368628ddb929322bf8e55695aa3af19002b1fea2e810acfc782fa2344e096e930b4850396e2e004d875727f0839975bd3d79e3419eb81cc09de2631507630dd3788182d4e3e2c16c246fa0277f68efb26f195d11d6ce801a2b78a195c345f107a9dbd8d4927ca9c95b68c9c7431bd64132f1d5012d9c8dd8000000140450000000000000000030000000001770000000002549e60000000000223ca6000122b8f7f0d614f1b4439e38dc516105f660639328ea162648daa422a11369ff4c3000000000015f824810000000000110010682606ad0a329b07977b91626c3018d8c82cf084a7d4ef1f800efa5cd36985e90023a910815256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e9081ea642b2e5a831c6e4d1b68a3f138d52582957a4c56da11f68a0149160f4f248d295700ad01000000000080ab8f7f0d614f1b4439e38dc516105f660639328ea162648daa422a11369ff4c30000000000625ece40012c8b0080000000000b000a23484912e6810c08eeb52dfc67cee7a1d9321e1abc1880800000000011001028f4b0e4b17b183bbc7432902506a3accedb2459daf3146d8e0b1ac8d2a4b27782002418228110806873db49b74aa163df0331441bcc8cae3935262ce6accc9ce7e70dec462535c4811036156baddb0c8598cacfd150a6f7f3893d53b784ea0e188bd2400f0d68abf5ff00a398220110054bfe99fb9bd42af0827b4092a86f8c113ffecd69a7e7ac15d057da965d20ea01100c244a344b23872542da30b81f0dadb9790f4370e224de0b75ad606d79f76daf00a3a910815256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e9081ea642b2e5a831c6e4d1b68a3f138d52582957a4c56da11f68a0149160f4f248d2957663d311000000000000000000003000000000177000000000223ca600000000002549e602184aefb8063b9f14a8ef4be8ba84c09107359d62c994df78cc3b07e4c2abd7d018c4e716e3df369d6d8b17aed7197e453ac4ebb7a99b55e101cd2347794edc471000000000000000000000000000000000000000000000000000000010000409d47ae68afd388ad7bf011aac01308facdcab44edb8dc6f39409b69c4837f76d400915c7bf86b0a78da21cf1c6e28b082fb3031c994750b13246d52115089b4ffa6180000000000afc124080000000000880083413035685194d83cbbdc8b136180c6c6416784253ea778fc0077d2e69b4c2f48011d48840a92b7bf18e6bfcef321c861d4a71d6fa8e866615e8009917a66646a50991fdbf4840f53215972d418e37268db451f89c6a92c14abd262b6d08fb4500a48b07a7924694ab8000800f80003fffffffffff0020f2d63ebf3cd801d8cd1d3e6be1841a0e1027cff13d633b8efc7362951ad8c040003f0000fffffffffffa00407685484eaf45d16eae74cfb9a045e3e475f69d6362cda0f327659264a2db94d30000fffffffffffa571efe1ac29e368873c71b8a2c20becc0c72651d42c4c91b548454226d3fe986000000002eb1182c8000000040568080000000004055c7bf86b0a78da21cf1c6e28b082fb3031c994750b13246d52115089b4ffa618000000000312f672000964580400000000005800511a4248973408604775a96fe33e773d0ec990f0d5e0c404000000000088008147a587258bd8c1dde3a1948128351d6676d922ced798a36c7058d646952593bc100120c114088403439eda4dba550b1ef8198a20de646571c9a93167356664e73f386f623129ae240881b0ab5d6ed8642cc6567e8a8537bf9c49ea9dbc275070c45e9200786b455faff8051cc11008802a5ff4cfdcdea1578413da0495437c6089fff66b4d3f3d60ae82bed4b2e907500880612251a2591c392a16d185c0f86d6dcbc87a1b871126f05bad6b036bcfbb6d78051d48840a92b7bf18e6bfcef321c861d4a71d6fa8e866615e8009917a66646a50991fdbf4840f53215972d418e37268db451f89c6a92c14abd262b6d08fb4500a48b07a7924694abb31e9888201dc04000000000202a58948e8ec017cafede7ca2686457010875559b273a63b417b6fe6c22eeb21a80200000001c00e00000216600200000000002f522850abd37ee1b36646b4d5b85fbc916f56104d5a3b0e0690608a04420138a23062b387099c97d9458403dd33953e565d054b209e98684f4bd602cca268044076e7132bc91195ff251682cdaf978e6e7998f065ef6d4384c0ae7176888774ec02009ac64206e1126632e2d08e50f47925f11f8b04aba20cad7a65a25d0a32e2931d17945424ce05c00f64ea4206a88d1fa87c84e0c5228a2a5b045dccb605a01117740b58a7384f5d66528d7062d1580000000000000000000000040048ae3dfc35853c6d10e78e371458417d9818e4ca3a85899236a908a844da7fd30c00000001a920ea2ec3e76f7b403ba673b35954861170448627cbe7ecaf7c00ece94988a40048a58948e8ec017cafede7ca2686457010875559b273a63b417b6fe6c22eeb21a8020000009d78c152837378b3cbbc9b373ffbf698792e9046ee8f942855f69cd7808891e000000"), - TestCase(PublicKey(hex"03d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c46"), PublicKey(hex"028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb26572"), hex"0100220000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400092dd581059d2bb431f96f6c199e59a9cdf4ddaea58dca8229725660804592523a80000000000000000000022200000004a817c80000000000000004b0000000000000000102d0001e0016001411e6e9810bf2d21b329be20c89fa19556bdd933c00000003080a8a037245a5d1fc8a4d0cf1bf882c121732b9255a327efba1c1c63e10f6ff481199f30000000000000222000000012a05f20000000000000004b0000000000000000107e0001e028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb26572021021b7b90467b6d39f86b9160d4801dc1e92eedbf5303ffc419fc013bd24a91d02766d36e2bc109b08ec5b0850c53a59372cd306cc1f95cefc4ceb24a6d7886e1b03a9ca87c8642c6770627ee356f92ceb075fa23aed68a6b952b50455ba939c9d85038ffc2170ef33aca1f4741347f1de06cda9f60cd2d5121e6bc8892953a38bb00e000000028a8a0000000000000000080000000002ee0000000000b2872000000000067486e0240098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f000000002bc0d40100000000002200205be011cd1ed87926bd56a773c1b1907fe457ba0bcc876dc61f32c1ca85470e49475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652aefd015a020000000001010098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f00000000005141888002b42d000000000000220020143e4a3411ea01f087e0904a2ae0f4a312355368091aa08acf2279f9b6b72711eda40100000000001600149be0e57e4e3561b30fb11014bc3ed2036855381c04004730440220068466106db388aabc63e77d137b815649614d0ac2530589ab4c5e05d78afe3f022064c9ee1be2b7b794b6645c7b5ec8f0e69e727980dc7219fe1715e9803178a98201483045022100e8e5ad22d38e27f56ad53e43e34656026a74ae3527ceb06752f9114483b057d702205978489fb86927818e0b71deeed44c8c5827237f40f92483b36688cc48171bb101475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652aee93b9820000000000000000000080000000002ee00000000067486e00000000000b28720ed5846e908bbaaa6b2f994f3c03f8f96350d2825cd6849104207a8ee263849b302bcc40fecd594b6fa15d22cd5e9a8df070ce97e8a1e90b4fdd8a1ef0ddb4c0d97000000000000000000000000000000000000000000000000000000010000ff036fd9558ffbcee019936ae3251b1696cf0ec92a58a73dcc6e0c33ea64738242e4240098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f000000002bc0d40100000000002200205be011cd1ed87926bd56a773c1b1907fe457ba0bcc876dc61f32c1ca85470e49475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652ae0001003d0000fffffffffff80100a03da9ba67d38e18a51216c7d501d200aa3127445c959dd972193d3dd5951e7c0003ffffffffffe00098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f1bffef0000420000ff0088d2195367160d41c7c2bf78ce8996e62b632adfb829c8382955b1e8a5c27008dd7e9d91440a520b59c49af0c1a5a5e1768e5eab47556a81c6248278acc7f5757643497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001bffef00004200005f644c69010100900000000000000001000003e8000000640000000007270e000000"), - TestCase(PublicKey(hex"029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff2"), PublicKey(hex"02b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c23"), hex"0100250000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000425ea3f2629ba65b6849f78829099a1d10000000000000222000000003b9aca000000000000027efb00000000000000010090001e0017a914d73900cac21afc97ce994c27af395e7f4d681bcb870000000182030540ba863ec0edc426981ffa8eace0be4e9a03295b3a0ff6ca545d21983e3b6b0000000000000222000000003b9aca000000000000027efb00000000000000010090001e02b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2303ac60bfe54dbbd771b9d4c92dd33b584d91cda6f4da296aa10f009f043743aa90034980f41ef05b9248f6793f77666534685df92b54dcb4ee1030f5dbef2e88a4e102177e1b10f681a1a46221c0f87aaa8e2a49d8257924d229faf8525e3f447374e5023c6191ef9b284cc6aa0c88b3913d91bf1bde6c5ec6f9d6f8dafa6bb65c2dcbbd000000010a01000000000000011f00000000251c000000000000000000000003cf01bee02412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c000000002b0c9af90000000000220020b2ad00d661059d790dea8056dfd8a962a53023a3603fda636c20eb8b4e434ecb475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aefd01300200000000010112f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000000e3400480012e7ff90000000000160014c6a763425c341e0ba7d0c12ed9126a661e38aac3040048304502210097df6e1d55051150897b7196be2944239dc1d820ae08ba72cffdd2822d57da320220403951e2355f52e385a34665156402cb2e85e6133b3d2606e37947809ccf4888014830450221008700f11d141bad2fc7eb66163a8f621685420b75bfb9f2c49c7227bf8fdb2ef702202f4a8485c71dad72263bcae61d6e17f21fbbe24331c3119deda2eaa1420cd74e01475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aea375b2200000000000000000011f00000000251c00000003cf01bee00000000000000000a6b6d346b134eeb64ed57d0b125934490af31cbb650813ae6454ab83d12042550331d262498b6ff23a23ac5a3e834afddf752bf1b5e81259233316a9ec72f5562f000000000000000000000000000000000000000000000000000000000000ff02db56e0e64c827f44aade7b016a37e6a7c0780db3666f7d8e8b422b6b02474b362412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c000000002b0c9af90000000000220020b2ad00d661059d790dea8056dfd8a962a53023a3603fda636c20eb8b4e434ecb475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352ae0006003f0000fffffffffee20041f7ae2006d76bc11dc18f8457cb4f76df375a85312315edbcfe508aca1596bcf4007a0001fffffffffdd002022e11a1cea6e7a8d662f74967d072ac654a72b59ada363c0e363ec4d26f8ef6800380000ffffffffff002065215a990d4af376ceb7647998ceea281d44fba94ec18c3a505f595fae0efe79003c0000fffffffffef00209a10496ebab959fe95ddfbe43f26cc9566bdc3a9cebc509e098367a2916b5e86003e0000fffffffffee40083551db0a0be081e7788bf7b13023ab3f306657022a9623a2179ca37c92f2e0d3801000003fffffffffb84008210ec1faedb71958c276e8e1502e7e1e588d4406cc9986157958e9d84f02a667e0001fffffffffdc212f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c00000000005f47acf200000000fffd01300200000000010112f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000000e3400480012e7ff90000000000160014c6a763425c341e0ba7d0c12ed9126a661e38aac3040048304502210097df6e1d55051150897b7196be2944239dc1d820ae08ba72cffdd2822d57da320220403951e2355f52e385a34665156402cb2e85e6133b3d2606e37947809ccf4888014830450221008700f11d141bad2fc7eb66163a8f621685420b75bfb9f2c49c7227bf8fdb2ef702202f4a8485c71dad72263bcae61d6e17f21fbbe24331c3119deda2eaa1420cd74e01475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aea375b2200000000000000000012412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000083b5c6292f61ad3cba1e702abb6d4ec1aec503c5b1b393e09c942ad4448a1b930000000000"), - TestCase(PublicKey(hex"03f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba"), PublicKey(hex"026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c27316"), hex"0200050000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340009e9907c0bc4f4e5b55a3b7012180e63b0ebf76498c543b991c056f0930a6ac09d80000000000000000000022200000004a817c800000000000000c350000000000000000102d0001e0016001495cde5680fa7aaa16935b74cc11b4ee695aa6b1500000004080aa98202cc1bef9c46bae2f3a965d7bf4bfca249098c5847b71d700ca0eba00db523986a0000000000000222000000012a05f200000000000000c350000000000000000107e0001e026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c2731603d49ddadc0517b1bb1f9c2773621e62b7720b2a622e4a5548becc37164bcd70f10368718d6b2887b302f75e5af0bac2f3b883663bde0fd61344ce16ed9a31c55eda03530674aa8610a599d0cd8be226923b5d8fe2a422cfbdc4fb790e39c7b05e0b7e0246ce4c46c2ee4110cce17bae3c7e6de01a8f0f8c1970cf58b2122b8fc77ffdeb000000028a8a0000000000000001aa0000000002ee0000000019545f970000000110b19269240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52aefd015a020000000001010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d00000000008dc3288002ff7b0600000000002200206444bace1cb90460914f5c964acfdd7846104e93a205d0bd5d00eb73f03acced21cd4500000000001600148636658ef4d15c8d9824bfea9b70ddc592b24bb404004730440220137ad66cce66df0041e172036c0c6de1e1cc8981130883bf693960778a2160fa022039d9505c98f78f54aa30172f4deccae26f9c4a7b55e2b72d4bd8875809143d5501483045022100ced9a63de710f1339c5d9086b1cbe0097f942872fbf1d0ca3a643409933cc86802201d5e311f655f64fb64eae62c9a3e321329158d055ecce9c537657979c17fe62501475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae38497c20000000000000000001aa0000000002ee0000000110b192690000000019545f97359430d3b112dd6d24edb449958073132a7721c737f2a72edf12ff74606d6c6f022030518cd036759a25ae66be2bbd46855ba6cdd214742f42aecd72b9093a444a000000000000000000000000000000000000000000000000000000d10000ff0200c2341c9b6d1ba8955188aa913cbf5b303c65ad19491df7d15fa0a811c44d38240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae0005003d0000fffffffffe580103151b3b767e35873dd3babc0f5bc9ea4a99069c269456b9e1157dda87db454b7801f80007fffffffff2b0020b4a390fde1b8bf038232ea79e48b825b5d1e930a97f2532aacbe0ddfaf24e9be00380000ffffffffff002038da5bbdce565d1eea32b7b9f4c4932e63892a154197054f5eb0116aab0f41de003b0000fffffffffe60041298f04989916fa9ac7b83da8a340a8bc6fcad73925c190576d728b28dfcf8de400720001fffffffffd00204225a2a872ea98ff1b22991635a2d6cb598839bd8a3988eeeb53adf784149fa880007fffffffff2b000e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000001e2d5a0004240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15eecd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15c4cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15bacd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15b8cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b150001240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52aefd014f020000000001010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15b8cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c87040047304402203167bba6344d2d82aa11db437dcfd17903de22079f58597f8927813cb96c32c3022036ead9938307bcc82561f85cebd8123b273ca26e9aeafc6e0502e97a38b508da01483045022100e0cffda9bd2bd045b99024d99597698a25803d42cd22c8b58d7a9e29363e79e802202a0e27d553bce59bb7b4e2cd89b847ade01d39bcb1027e0a7c0b6e0d4fff096601475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae00000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15000000000000"), + TestCase(PublicKey(hex"020d9b51f544c7a1c061a6604badd79d068a7e1078ee06a013e17a39c13cb302f2"), PublicKey(hex"028b8f6f6eb0ca68d25760a419c007d7e74c2228ee53448042e6e06b1eae254e5e"), Transaction.read("02000000019cfefde2c69b849b4d3039cf60b8d5fb55c92150453a5b28f4dcfcb0c3aec8c700000000009c299480014663010000000000160014160a2fafb57ade5b434b7fdf2ed037a92b15280386b98220"), hex"00000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000400633f73979e2e7cc9a76f2dbcccf4d9000000000000022200000002540be40000000000000003e800000000000000010090001e000bd48a6ca0842ad7ec52d29f4be4c7015ea447e435e5404380000000c5019a59a1964d2e91fa2ae22c629bd57e7458b98373a3503a6b3675778cc09c3298000000000000011e8000000002f34f6000000000000001f400000000000001f4004800f18145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f01df72613647456031c11cf28c90e06230848223b1064616b89bb2e8d2557c631f011d20840603112fa5faaa4053f09f9f50bcdf94f392941569ce09310340dc50020117810d980eb608cae903683abfc8438fef5421397e688d8c4aa3a47e0fc3418601b035c2fabbe71271d3a07be023eae63e4ac5272bd09a6b990a5409160f3ab28a000000008400800000000000000000000000186a00000000000000000000000002faf08000124e7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d76463800000000015d04300800000000011001070d0d1c4bd6ad8c9b7bcb36f6308b955dc28e4970b43991232fe217bf4a56deb0023a9108106cda8faa263d0e030d33025d6ebce83453f083c77035009f0bd1ce09e598179108145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f2957009801000000000080ce7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d7646380000000004e14ca4000a3318080000000000b000a0b0517d7dabd6f2da1a5bfef97681bd4958a94018200241822811080579dbcb14b3d3f2c7200693391d43977881357806ebd47b2633d0c1b5ac1a56481103c3a8f6a5e15cab0144f99ec5f875fabbdf32ef6748326f4a5b3a1b29b8c7f9e80a418228110806b4c99af1b3c8f74bef604336f1a1200a754f1680923a511f7af92391bac39ab811019085fba36cec85b5537807d07f2931858413a56c47b2cf7753fbcf4cc7206bc80a3a9108106cda8faa263d0e030d33025d6ebce83453f083c77035009f0bd1ce09e598179108145c7b7b7586534692bb0520ce003ebf3a611147729a240217370358f5712a72f2957435cc1100000000000000000000000000000186a0000000002faf08000000000000000005fbb84fad0781822e1268df19eb42468a5041d0b44dc8c7a21aa7aae19409bab81e514d9069fc6064c17f35d1d0f227fbe1b74ca89fec6c67b9668fe96341d43e200000000000000000000000000000000000000000000000000000000000040ee389313797b7f5177522d46afba5c43e37edebee6598c72e5a94c81fcc50a840009273fbf78b1a6e126d34c0e73d82e357ed5724854114e96ca3d373f2c30ebb231c0000000000ae821804000000000088008386868e25eb56c64dbde59b7b1845caaee14724b85a1cc89197f10bdfa52b6f58011d488408366d47d5131e87018699812eb75e741a29f841e3b81a804f85e8e704f2cc0bc8840a2e3dbdbac329a3495d829067001f5f9d3088a3b94d12010b9b81ac7ab89539794ab8000139fdfbc58d3709369a60739ec171abf6ab9242a08a74b651e9b9f961875d918ece7f7ef1634dc24da6981ce7b05c6afdaae490a8229d2d947a6e7e5861d76463cc80398ba555ae3310a427fe5a79299e970d0b199fdade631246d123ea6324e58f40736fc2307f587e516761e35b11c3d960991f1d905d523ff0cbdd88d527790"), + TestCase(PublicKey(hex"03038461f9a84dddcf1a1f8adf424a2aa5250b1ab0ad53505d7face53a1b70cc9d"), PublicKey(hex"02c5d5b5f7291d11c052ce00bde1b4517d309f2b73f99c798fc3e3329b215e3319"), Transaction.read("0200000001385746e2e784760342f03ccb9be6e7d2e72aa4df3d0aa2c9b592b86ea05d35e70000000000acaf60800148ffff0000000000160014cdcdc8d728de64b2073bc508101349668154a21de75b3620"), hex"000002010000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340009c5b105f42a2aea346cf8759f7c78135158b93878faac0659b26f723cc549fded80000000000000000000022200000004a817c8000000000000028f5c000000000000000102d0001e000b000a490f9af5166835d37b0f2155fa3a7a2b4fae66fd8000000105450189fed9a69c67c55d9743b3dde05b031088442ad94e9e3437e15f479f5cd66a7e000000000000011e80000001eefffe5c00000000000147ae00000000000001f403f000f18162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c8179a7ba86216d011a9c1c49684578a6e0ca111279e6641404dee194eabcbbd21c81aaa1f3d35df2711a08374da8278fe259f6085189cc539a144c1aa0744b23495601331777d0354951fa354a8a2ee70e2bf40c9bb3f4fe340c9dab02bba5b6bb1aa00158bc5a03c0c79b947e5ac70f76f600c7fab8561e244f1425935fc51ffbe330380000000181515080800000000000000000000000007e800000000000000000000001f3fffe0c00121c2ba37173c23b01a1781e65cdf373e97395526f9e855164dac95c37502e9af3800000000015ffffff80000000001100101621d570811311537fddef81ee6f7023e2d17c11d32a53f472887dd035a78a518023a9108162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c908181c230fcd426eee78d0fc56fa125155292858d5856a9a82ebfd6729d0db8664ea9570097010000000000809c2ba37173c23b01a1781e65cdf373e97395526f9e855164dac95c37502e9af380000000005657b04000a47fff80000000000b000a66e6e46b946f3259039de2840809a4b340aa510e8200239822011030949de6ac0462e1b5a42ded6c7763779d9f6aab069cb3366a3ca330bd1fe52b81101564903650deeb2a1f719865742e99ea0986774184727607a69bfff9f552a45a00a398220110228710b1083bde27eeb793ad155142cb69655ad8424eeae2044808edc631840d8110225098637d7f91c20e00d4a82a56520ad3cead728dd5b82b0bdcfe40d4b6acfb00a3a9108162eadafb948e88e02967005ef0da28be984f95b9fcce3cc7e1f1994d90af198c908181c230fcd426eee78d0fc56fa125155292858d5856a9a82ebfd6729d0db8664ea95773ad9b100000000000000000000000000000007e80000001f3fffe0c000000000000000063aa3a9e7e5e45c265ae8bf12e96a239ddbae4224c6f4560e149cce8a2675b208178d03c15e7303b3415e3549606ecab96ff5250ce38b2052b0a9dd2ed0af5ee75800000000000000000000000000000000000000000000000000000000000409cb1d9e3463cd48a65e9a2fa95f0755e924dba3664026d3682892bbf9527396bc0090e15d1b8b9e11d80d0bc0f32e6f9b9f4b9caa937cf42a8b26d64ae1ba8174d79c0000000000affffffc0000000000880080b10eab8408988a9bfeef7c0f737b811f168be08e99529fa39443ee81ad3c528c011d48840b1756d7dca47447014b3802f786d145f4c27cadcfe671e63f0f8cca6c8578cc64840c0e1187e6a137773c687e2b7d0928aa94942c6ac2b54d4175feb394e86dc332754ab8000070ae8dc5cf08ec0685e0799737cdcfa5ce5549be7a1545936b2570dd40ba6bce32de50000008000070ae8dc5cf08ec0685e0799737cdcfa5ce5549be7a1545936b2570dd40ba6bce0656b85b7c0e2dd09a620e28ddf865c6197074354c922bb9b76fb27ffe7fbf19cc0"), + TestCase(PublicKey(hex"03571382973ec0873b6668463dff0e0d426e85059e30888c448416eaad4849061b"), PublicKey(hex"023c22f28ad8e7aef1e0e7c2ea8902768933ca0a7f391591f3f63e1d66fe8b948e"), Transaction.read("020000000111d3039068824165b9c4355b9d1435b3d602474ae9b131263a7a28934672cde201000000007d71838001000dec00000000001600141d66fcf3ecbbd149949bf012f2a0a67179d4dcec6c1ecd20"), hex"00000203933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400041507665ff588dba1b9d4949e739ad05b0000000000000222000000003b9aca00000000000002718000000000000000010090001e000bd48a65a1ef54e50a013d73e46815abed7672835c83f34380000000c101d9ed460a901187b39b2c00d859f885104116abf778bf31c369342e4147db56e4800000000000011e80000001d87278e000000000000138c000000000000001f403c200f1811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca4701e06722e502349a2857e99bf9299fe781a343aebce7557c0c3738e20f14b9e96f81985a99219fd30dcdf40438cc4d01d6027087f65d6dea4e0cd9864016febcbcbf8176c51f0ee44572f83a8a02f50053ed3cfcedbf5ce6c70f70970f5784c0bb0dfe0195a18fc266e7e7f1dabcc5234f493620de9f8e8d5932de3e8a44626974e0dd1080000000c100000000000000000000000005b8d8000000000000000000000001dd3826e0001208e981c8344120b2dce21aadce8a1ad9eb0123a574d898931d3d1449a33966f10080000000158c2b7a00000000001100101395ec5380c9641495ac8c4bb01605cf6ac58ee89a33da93d5eb09ab4be0e0ab8023a910811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca471081ab89c14b9f60439db334231eff8706a1374282cf18444622420b7556a424830da95700980100000000008088e981c8344120b2dce21aadce8a1ad9eb0123a574d898931d3d1449a33966f100800000003eb8c1c0008006f600000000000b000a0eb37e79f65de8a4ca4df80979505338bcea6e76020024182281108070acce2513bf0df155ec51404333ac0397b6015bff2d87120d51d8dbf0b0f698811013a616842936434a093f8e9f85cbccb846ec2e0c49c9b541d46563cfb181886b00a418228110807ab650f61af7ce294df5a26e8cc1abb3658f0201c0cb9a2484e43fb9e61622d081102767e9f7608de2330774556121ddbd48c7a45d9598e38c821a3d9846c37e75f580a3a910811e1179456c73d778f073e17544813b4499e5053f9c8ac8f9fb1f0eb37f45ca471081ab89c14b9f60439db334231eff8706a1374282cf18444622420b7556a424830da957360f66900000000000000000000000000005b8d800000001dd3826e000000000000000005ef1d8da098d1f363033e64f2272a12b638bd3fe501c829d8350e8f3fc1c6b1481f12f3b5425786f43870f00174ddd36710d1864bbcf24aa54694cb183b43ebcbf000000000000000000000000000000000000000000000000000000000000408200a48c21439ebc4d7c70b310000bef69e454f808dee73be19aa960d54cac8100090474c0e41a2090596e710d56e7450d6cf58091d2ba6c4c498e9e8a24d19cb37880400000000ac615bd000000000008800809caf629c064b20a4ad64625d80b02e7b562c7744d19ed49eaf584d5a5f07055c011d488408f08bca2b639ebbc7839f0baa2409da24cf2829fce45647cfd8f8759bfa2e5238840d5c4e0a5cfb021ced99a118f7fc383509ba141678c2223112105baab52124186d4ab8000023a60720d10482cb73886ab73a286b67ac048e95d362624c74f451268ce59bc62b21ec0008d8000223a60720d10482cb73886ab73a286b67ac048e95d362624c74f451268ce59bc6049c6fe56892029844393a0afbf8c2c19088f565e80764b75a7e0591b97c1108ac0"), + TestCase(PublicKey(hex"02022b6d4bc22a31b82b31f79006031e7b0fdb58f1bcdcaa89ff0d1759700690b8"), PublicKey(hex"02f9cb479f8f313003191a891347276348ded1214f34ace6ff0cf7f4ad8a235471"), Transaction.read("0200000001beda1565101d2400fe63871b81508c192b4982a5234499255e379524c9b1522b01000000007be4e28001023d04000000000016001423c66d12a6764932f03f365f3831275bb18af89c76a3d220"), hex"000005010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400045d37887e8d909085b00af1d8b09c514b000000000000022200000004a817c8000000000000000b18000000000000000102d0001e000bd48a3d3ca19286d69a373eb6dda039da0cccf0bb6330c380000000c501df7e8244b99cc46d750bcef5830755886bad20416438b5973b3c8174f9d85b93000000000000011e80000000086131ec000000000000058c00000000000001f4004800f1817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa388107f85642fedecfcb7ef3cea6b38e26aead2759263d123692fd0c7d458fbc188881580b52cf6cdb449abc060bce9db3f35eebec2c87b316fd7a2c3193296d4a6e2e81fd2e032dd33ba9fa974246fccfc3ab5ccc1eb9ede9c900fc4e5d455c42150b3a8113237ef34839fc8055cbb63c0b9892defe6554c9dc70813681c2f3f35dedc13300000000c08080000000000000000000000010de8000000000000000000000000876dccc00125f6d0ab2880e92007f31c38dc0a8460c95a4c15291a24c92af1bca9264d8a915808000000015b7aa82000000000011001004a9c0705b42fa733f200639ed816601f0f413b9f29616f9c23f02f0dfc1df7e0023a910810115b6a5e11518dc1598fbc803018f3d87edac78de6e5544ff868bacb803485c10817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa38a957009781000000000080df6d0ab2880e92007f31c38dc0a8460c95a4c15291a24c92af1bca9264d8a91580800000003df2714000811e8200000000000b000a11e33689533b2499781f9b2f9c1893add8c57c4e02002418228110806567eeed21d6e87a045f3debf5ca10a05339c25f021400d96f7b492a957a94d301101df62a1c365062cce3b9a3ef530a4cd37eae4346491ed37e050dfd54213ef54f00a3982201103484562bcbb22ae3d892808bb04ff25f309a4732474a57bf6dd7691e17fc3df6811006854f362bcec61184d4721dfd30f777485be3580cd116564d139668bb6ee3a280a3a910810115b6a5e11518dc1598fbc803018f3d87edac78de6e5544ff868bacb803485c10817ce5a3cfc79898018c8d4489a393b1a46f6890a79a56737f867bfa56c511aa38a9573b51e910000000000000000000000000000010de800000000876dccc000000000000000025f43cb5a86018845618974486d0cb225355649b88690a914ee6cfec1a75286481d9daff2fcb7edb8d99f8fab9f77ff1033d5fc33a42ea7f8b70d29614b6d3195d000000000000000000000000000000000000000000000000000000000000409799acc6bbef72e7bc3dbed7831ac4e4ecfdad169d19628429edaa495d3c79ef00092fb68559440749003f98e1c6e05423064ad260a948d12649578de549326c548ac0400000000adbd54100000000000880080254e0382da17d399f90031cf6c0b300f87a09dcf94b0b7ce11f81786fe0efbf0011d48840808adb52f08a8c6e0acc7de40180c79ec3f6d63c6f372aa27fc345d65c01a42e0840be72d1e7e3cc4c00c646a244d1c9d8d237b44853cd2b39bfc33dfd2b6288d51c54ab800017db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a4557db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a454002f5228f4f2864a1b5a68dcfadb7680e7683333c2ed8cc30f7db42aca203a4801fcc70e3702a118325693054a4689324abc6f2a499362a454002c0028c95c468f1c569d43c9af6d335b8820bdda4888fe000200000"), + TestCase(PublicKey(hex"03d57bc818cee6523ac976e32c532baffa68028e165ca74640663bf2ac4256182e"), PublicKey(hex"03efea3bb157f8b21fd6cad3adecad654c40be673d852fcf9204737e8154865d3a"), Transaction.read("02000000013516ef197341a489906b597db7f886e262ebb31c5a349062d5f36ea3e98ead0d000000000076f1bf80016194980000000000160014c5ac89310fe31623ef8714fae74b0c414477b48d19ee3620"), hex"00000503933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000454699830eddf332655e0ff86e6c536f30000000000000222000000003b9aca0000000000000186a000000000000000010090001e000bd48a482e9d4f3df4621c059bdf7df20f5ec9c4df26364380000000c101bf0694efa44b50d2817c3ac271bd38ad5110d72162deb6328cc919097ed7727f8000000000000111000000001dcd6500000000000000c35000000000000000008048000f01f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d01125974b8fc60d0122160b22db09edf15265c9f8d42c407a4b6e286445ce2897e01c8dfdd96184b8a8a0faa4d5d92acea243bb257e2bf911767efbf3e0fee216d3b81f4207be48f4ebbc397fb0bf3d223696ce431919a583ad69a7f3825323977b38a811b7e73e5b685f78f56e8d250e68d99452deed356a826fb5f207d314d0b61b97780000000c50000000000000000000000000001770000000000000000000000012a05f20000121a8b778cb9a0d244c835acbedbfc43713175d98e2d1a48316af9b751f4c75686800000000015c04b4c000000000011001029e7f0fe79b14835816eb3cb89338bf442f040ee84ea6fdfb401ac2aac96919a0023a91081eabde40c6773291d64bb71962995d7fd3401470b2e53a320331df956212b0c171081f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d29570097810000000000809a8b778cb9a0d244c835acbedbfc43713175d98e2d1a48316af9b751f4c7568680000000003b78dfc000b0ca4c00000000000b000a62d6449887f18b11f7c38a7d73a58620a23bda4682002418228110805b5b0d8c18572b99a6496d58eefe9bc20bcf359d91cc480c022591ba7c86d2280110096244c4df48199bd71b654c29ac49aa2a1dfcb6f2554a89b7d38bd28de98f6200a39822011013d445ecdc92ced0ebe8920580d3e5e7a1cdfd19fd39a5e57495a80e8a7cc0630110163a3f38ba3f23acc687ac397fb98d8dedb9a07a4085dec28d5a792e9ea2aece00a3a91081eabde40c6773291d64bb71962995d7fd3401470b2e53a320331df956212b0c171081f7f51dd8abfc590feb6569d6f656b2a6205f339ec297e7c90239bf40aa432e9d29570cf71b1000000000000000000000000000000177000000012a05f200000000000000000054e507323b7ab08f00c834e2ba694233f9d50293312291fc893691e512805f45814c861ee65261b9b7bdaaaebd59d316e5bf00417c2c9afeb5db143096feff164480000000000000000000000000000000000000000000000000000000000040b625f1f1e5239ce80103faf7851d4adbfbed7cf5bace51b959d5d6050f88e65d40090d45bbc65cd06922641ad65f6dfe21b898baecc7168d2418b57cdba8fa63ab4340000000000ae025a6000000000008800814f3f87f3cd8a41ac0b759e5c499c5fa21782077427537efda00d615564b48cd0011d48840f55ef20633b9948eb25db8cb14caebfe9a00a3859729d190198efcab1095860b8840fbfa8eec55fe2c87f5b2b4eb7b2b5953102f99cf614bf3e4811cdfa05521974e94ab800006a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a6a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a002f522920ba753cf7d18870166f7df7c83d7b27137c98d90e6a2dde32e683491320d6b2fb6ff10dc4c5d76638b46920c5abe6dd47d31d5a1a002f522994760b5d8d708a41fe54bdd87888baf41f10e7bd0e000200000"), + TestCase(PublicKey(hex"0297d3589bbeddeb079bcd9495d52cb47ea20515f962501f0de15a8f4a66293a11"), PublicKey(hex"0242199d21cc7a2fac5c4168b6c3635bb0cc63ed4b920651833f8395f010bcc01c"), Transaction.read("020000000167189c04a660a840a18470f6d3d76c9d3910822776208bd32d5ce7c56360148b00000000007ddead8002f6b10000000000002200200f077d4a0e598f677c28eb4b51af88be2d2f17791722926f912c6349cfe1afc76ae297000000000016001419267eae62331d769c1fb4b6f742618389a5f90e36587d20"), hex"00000603933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340004004c3bfd866d20986aa90ec265fdfcbe0000000000000222000000012a05f20000000000000186a000000000000000010090001e000bd48a3b48ca7482f0442970651ba40bf45091fddef3a84380000000c501a0528fcd6c721246634deb024236eb2f4c124e13b587093a6652e7bfc12db3250000000000000111000000009502f900000000000000c350000000000000000083f0000f01210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e0110d8d9aa7be8b4553b0af05b921c72c48356c4a0af61ffed7f1eadb7c5071c64816bf131172fe61d3145d39511755ee8095927534b76c4bb2464b706eaaee1486e01dd6caa8417804e26ce56f83a2525aa05fba25d3bab6db2cae5a203ce126da1ec01c8dfca1943df0dd0d59ffda66e207316c4af1cd3a4b6443fc5298a7a3864658f00000001404500000000000000001400000000017700000000015b95aa0000000128aa5c560012338c4e025330542050c2387b69ebb64e9c884113bb1045e996ae73e2b1b00a45800000000015c04b4c00000000001100104c97a415e599a3817965ff9a64e87d2af9bbcd77cae148ea934f9488216ffee10023a91081210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e10814be9ac4ddf6ef583cde6ca4aea965a3f51028afcb1280f86f0ad47a533149d08a95700ad01000000000080b38c4e025330542050c2387b69ebb64e9c884113bb1045e996ae73e2b1b00a4580000000003eef56c0017b588000000000001100100783bea5072cc7b3be1475a5a8d7c45f16978bbc8b914937c89631a4e7f0d7e3b5714b80000000000b000a0c933f5731198ebb4e0fda5b7ba130c1c4d2fc870200239822011032e32cd21310cf1864c862bb32b9c78b1b8a626d1094d47d0e1938166388243f81101bbf4ba992687498419d57da8e6d700809fdf1f882425296292ff55b67ec82bd80a4182281108054815d830690f798ccd75722576882f62a088d37e81cde50c40bc5b080615c3e011030965e18229cbf3a03024a97988d75cb1b1569eaa1ce381d0689958161c4799900a3a91081210cce90e63d17d62e20b45b61b1add86631f6a5c90328c19fc1caf8085e600e10814be9ac4ddf6ef583cde6ca4aea965a3f51028afcb1280f86f0ad47a533149d08a9571b2c3e90000000000000000000140000000001770000000128aa5c5600000000015b95aa392494f078bec5b681d4997f2497d52112ad54eaaa81dec63f6480336139048b81fc2e019907aefb5efc5922187f575e10635611bde69ae0bd46b0b6ea44aea7748000000000000000000000000000000000000000000000000000000a000040f7dd9de11426234d0121ec1f5d2d19c3b4e78679b2e07a5c4f91bc624458352bc00919c6270129982a1028611c3db4f5db274e442089dd8822f4cb5739f158d80522c0000000000ae025a60000000000088008264bd20af2ccd1c0bcb2ffcd32743e957cdde6bbe570a47549a7ca4410b7ff708011d4884090866748731e8beb17105a2db0d8d6ec3318fb52e4819460cfe0e57c042f30070840a5f4d626efb77ac1e6f36525754b2d1fa881457e589407c37856a3d2998a4e8454ab8000800ec0003ffffffffff80106b5a98105912cee7f3cd2a4ed131283ee6fa4f5aef2e74aec08ebc2d6770ee38001e80007fffffffffec0082e868d9d8ebcc94bff771ca9d328bccfdfafdbc17e2b078b728caa0bb7179c6420001ffffffffffb0ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c02916000200e60400000002ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c029160000000001fffffffe05ed620000000000002f5228ed2329d20bc110a5c1946e902fd14247f77bcea10f17c52e00000000002f52295b2c2f65d18b177c1a91981b0335c8d209f93ea10e000000000002029e04000000000202ce3138094cc150814308e1eda7aed93a7221044eec4117a65ab9cf8ac6c029160000000001fffffffe05ed620000000000002f5228ed2329d20bc110a5c1946e902fd14247f77bcea10f17c52e00000000002f52295b2c2f65d18b177c1a91981b0335c8d209f93ea10e08008e608804401c9f21ea6ff52325709bc8fec460de1d9f584cbf6aced6abeda5842595fbb968044076a3a4b918db66a59ee47c4d3a2f8bb0f17665e6d904df5cab74ee06cbafbb3e028e60880440b6af6af4787b1229039a7351f7156d0cc30755a1902c6d483ea85915a29b40f6044023ea5be6088489f6dda062498c485465c7451d7bf49eb46dae206bf952ddc860028ea4420484333a4398f45f58b882d16d86c6b76198c7da97240ca3067f072be02179803842052fa6b1377dbbd60f379b292baa5968fd440a2bf2c4a03e1bc2b51e94cc527422a55c0000000000000"), + TestCase(PublicKey(hex"03455b390d9fbc70d538f3e38abaf1a217e66b45c87ff025f3be7878697a84796a"), PublicKey(hex"03c6b8a41701c6abdc9951432c94873cfac9fa5c3bbad8215e28012c8e212748d8"), Transaction.read("0200000001ba658f3a2f2275bb727cd8f72a57d185f26e310cf83c72131b54970bf8ad44d90000000000763e9780015c070100000000001600142580921e745a308426c578f417354e60482120662e085e20"), hex"000008010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340004515822333393159ec64e3694540844cd000000000000022200000004a817c80000000000000003e8000000000000000102d0001e000bd48a04a8223d31a871ecced64af1c4690532d4b5de984380000000c5012e53ad31acde84a88f55e981f2c33e8d6b451e15463d3da9335dbe9e30a474c60000000000000111000000009502f90000000000000001f400000000000000008168000f01e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c0126a65afe85868caba0a468ae6adf48b02e3dbd527a7d6e86c8b6426af06a5cce81ece00b5452805039314df65a98f5eeb21eab3d917853cf49567c28392e094806012f5f8f9177f320504b51ee8112a62fb5c505b8d9167737731545d99141d3485501004c1a5ece2ff034dcc63ec008df0ec8185934a2fcaf3075a75a5a999989d96580000000c50080000000000000000000000057e400000000000000000000000002faf08000125d32c79d17913addb93e6c7b952be8c2f93718867c1e39098daa4b85fc56a26c800000000015d04300800000000011001006333ad899044f5d624983aa15a497eb65df4372319091299696efb7f3d7b4cd8023a91081a2ad9c86cfde386a9c79f1c55d78d10bf335a2e43ff812f9df3c3c34bd423cb51081e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c2957009781000000000080dd32c79d17913addb93e6c7b952be8c2f93718867c1e39098daa4b85fc56a26c80000000003b1f4bc000ae038080000000000b000a12c0490f3a2d18421362bc7a0b9aa7302410903302002418228110807b9455b3bbed8d0994cf1fd0d8fcd2e63a03710f470943da74ef0f4513fc6f43811009275b2d4020f8bfc179ae4dd5cb3935c30767f48ee723cca0d708184b01f2c400a3982201102f192e0d772fc5b08cba1e7b1c39dbed4f64439569c493af18a9539818a6f36c0110272c9b5f9058e69485ca53388311c00ff69b5631334ed4e002cef0958389e42880a3a91081a2ad9c86cfde386a9c79f1c55d78d10bf335a2e43ff812f9df3c3c34bd423cb51081e35c520b80e355ee4ca8a1964a439e7d64fd2e1ddd6c10af140096471093a46c295717042f10000000000000000000000000000057e40000000002faf080000000000000000046ec68ce2b1bca12747a13122270527bdad325cd34478d112adcc4bd0fc25f1e814f2919e34f1842677f99901e2aaf7f0abfafd39e6350238b52b8c2bf3b00ff7f00000000000000000000000000000000000000000000000000000000000040f5ca8901722651214939b99ca3c79307f3380af44ae1aecd4d317784fda7a15d80092e9963ce8bc89d6edc9f363dca95f4617c9b8c433e0f1c84c6d525c2fe2b513640000000000ae82180400000000008800803199d6c4c8227aeb124c1d50ad24bf5b2efa1b918c84894cb4b77dbf9ebda66c011d48840d156ce4367ef1c354e3cf8e2aebc6885f99ad1721ffc097cef9e1e1a5ea11e5a8840f1ae2905c071aaf7265450cb2521cf3eb27e970eeeb608578a004b238849d23614ab8000174cb1e745e44eb76e4f9b1ee54afa30be4dc6219f078e42636a92e17f15a89b2000000005d353b426e9963ce8bc89d6edc9f363dca95f4617c9b8c433e0f1c84c6d525c2fe2b513660192a0c75d44f2a6f4e1bc66fddf10b2949b9896cbe8548edeba49c0dd8300d82f42ef7330fb4601c7f2e1af3be67e3c3e86bb4c93a3bd9085c53732111c42dc"), + TestCase(PublicKey(hex"03d4c8565cb50638dc9a36d147e271aa4b052af498adb423ed1402922c1e9e491a"), PublicKey(hex"02a4adefc639aff3bcc872187529c75bea3a199857a002645e99991a942647f6fd"), Transaction.read("0200000001571efe1ac29e368873c71b8a2c20becc0c72651d42c4c91b548454226d3fe9860000000000c4bd9c8002591601000000000016001446909225cd021811dd6a5bf8cf9dcf43b2643c35783101000000000022002051e961c962f6307778e865204a0d47599db648b3b5e628db1c163591a54964efcc7a6220"), hex"000009010000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000411bde5f686930a7085566a22fd7fac49000000000000022200000004a817c80000000000000005dc000000000000000102d0001e000bd48a142af4dfb86cd991ad356e17ef245bd58413568ec3800101000000c5017ebac21a9e7d42c23db38b88b689f1efb9ad52d0e728e4553493dcde20dff8f70000000000000111000000009502f90000000000000002ee000000000000000083f0000f015256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e81290a232a7ee5a18660742d9368628ddb929322bf8e55695aa3af19002b1fea2e810acfc782fa2344e096e930b4850396e2e004d875727f0839975bd3d79e3419eb81cc09de2631507630dd3788182d4e3e2c16c246fa0277f68efb26f195d11d6ce801a2b78a195c345f107a9dbd8d4927ca9c95b68c9c7431bd64132f1d5012d9c8dd8000000140450000000000000000030000000001770000000002549e60000000000223ca6000122b8f7f0d614f1b4439e38dc516105f660639328ea162648daa422a11369ff4c3000000000015f824810000000000110010682606ad0a329b07977b91626c3018d8c82cf084a7d4ef1f800efa5cd36985e90023a910815256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e9081ea642b2e5a831c6e4d1b68a3f138d52582957a4c56da11f68a0149160f4f248d295700ad01000000000080ab8f7f0d614f1b4439e38dc516105f660639328ea162648daa422a11369ff4c30000000000625ece40012c8b0080000000000b000a23484912e6810c08eeb52dfc67cee7a1d9321e1abc1880800000000011001028f4b0e4b17b183bbc7432902506a3accedb2459daf3146d8e0b1ac8d2a4b27782002418228110806873db49b74aa163df0331441bcc8cae3935262ce6accc9ce7e70dec462535c4811036156baddb0c8598cacfd150a6f7f3893d53b784ea0e188bd2400f0d68abf5ff00a398220110054bfe99fb9bd42af0827b4092a86f8c113ffecd69a7e7ac15d057da965d20ea01100c244a344b23872542da30b81f0dadb9790f4370e224de0b75ad606d79f76daf00a3a910815256f7e31cd7f9de64390c3a94e3adf51d0ccc2bd001322f4ccc8d4a1323fb7e9081ea642b2e5a831c6e4d1b68a3f138d52582957a4c56da11f68a0149160f4f248d2957663d311000000000000000000003000000000177000000000223ca600000000002549e602184aefb8063b9f14a8ef4be8ba84c09107359d62c994df78cc3b07e4c2abd7d018c4e716e3df369d6d8b17aed7197e453ac4ebb7a99b55e101cd2347794edc471000000000000000000000000000000000000000000000000000000010000409d47ae68afd388ad7bf011aac01308facdcab44edb8dc6f39409b69c4837f76d400915c7bf86b0a78da21cf1c6e28b082fb3031c994750b13246d52115089b4ffa6180000000000afc124080000000000880083413035685194d83cbbdc8b136180c6c6416784253ea778fc0077d2e69b4c2f48011d48840a92b7bf18e6bfcef321c861d4a71d6fa8e866615e8009917a66646a50991fdbf4840f53215972d418e37268db451f89c6a92c14abd262b6d08fb4500a48b07a7924694ab8000800f80003fffffffffff0020f2d63ebf3cd801d8cd1d3e6be1841a0e1027cff13d633b8efc7362951ad8c040003f0000fffffffffffa00407685484eaf45d16eae74cfb9a045e3e475f69d6362cda0f327659264a2db94d30000fffffffffffa571efe1ac29e368873c71b8a2c20becc0c72651d42c4c91b548454226d3fe986000000002eb1182c8000000040568080000000004055c7bf86b0a78da21cf1c6e28b082fb3031c994750b13246d52115089b4ffa618000000000312f672000964580400000000005800511a4248973408604775a96fe33e773d0ec990f0d5e0c404000000000088008147a587258bd8c1dde3a1948128351d6676d922ced798a36c7058d646952593bc100120c114088403439eda4dba550b1ef8198a20de646571c9a93167356664e73f386f623129ae240881b0ab5d6ed8642cc6567e8a8537bf9c49ea9dbc275070c45e9200786b455faff8051cc11008802a5ff4cfdcdea1578413da0495437c6089fff66b4d3f3d60ae82bed4b2e907500880612251a2591c392a16d185c0f86d6dcbc87a1b871126f05bad6b036bcfbb6d78051d48840a92b7bf18e6bfcef321c861d4a71d6fa8e866615e8009917a66646a50991fdbf4840f53215972d418e37268db451f89c6a92c14abd262b6d08fb4500a48b07a7924694abb31e9888201dc04000000000202a58948e8ec017cafede7ca2686457010875559b273a63b417b6fe6c22eeb21a80200000001c00e00000216600200000000002f522850abd37ee1b36646b4d5b85fbc916f56104d5a3b0e0690608a04420138a23062b387099c97d9458403dd33953e565d054b209e98684f4bd602cca268044076e7132bc91195ff251682cdaf978e6e7998f065ef6d4384c0ae7176888774ec02009ac64206e1126632e2d08e50f47925f11f8b04aba20cad7a65a25d0a32e2931d17945424ce05c00f64ea4206a88d1fa87c84e0c5228a2a5b045dccb605a01117740b58a7384f5d66528d7062d1580000000000000000000000040048ae3dfc35853c6d10e78e371458417d9818e4ca3a85899236a908a844da7fd30c00000001a920ea2ec3e76f7b403ba673b35954861170448627cbe7ecaf7c00ece94988a40048a58948e8ec017cafede7ca2686457010875559b273a63b417b6fe6c22eeb21a8020000009d78c152837378b3cbbc9b373ffbf698792e9046ee8f942855f69cd7808891e000000"), + TestCase(PublicKey(hex"03d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c46"), PublicKey(hex"028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb26572"), Transaction.read("02000000010098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f00000000005141888002b42d000000000000220020143e4a3411ea01f087e0904a2ae0f4a312355368091aa08acf2279f9b6b72711eda40100000000001600149be0e57e4e3561b30fb11014bc3ed2036855381ce93b9820"), hex"0100220000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400092dd581059d2bb431f96f6c199e59a9cdf4ddaea58dca8229725660804592523a80000000000000000000022200000004a817c80000000000000004b0000000000000000102d0001e0016001411e6e9810bf2d21b329be20c89fa19556bdd933c00000003080a8a037245a5d1fc8a4d0cf1bf882c121732b9255a327efba1c1c63e10f6ff481199f30000000000000222000000012a05f20000000000000004b0000000000000000107e0001e028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb26572021021b7b90467b6d39f86b9160d4801dc1e92eedbf5303ffc419fc013bd24a91d02766d36e2bc109b08ec5b0850c53a59372cd306cc1f95cefc4ceb24a6d7886e1b03a9ca87c8642c6770627ee356f92ceb075fa23aed68a6b952b50455ba939c9d85038ffc2170ef33aca1f4741347f1de06cda9f60cd2d5121e6bc8892953a38bb00e000000028a8a0000000000000000080000000002ee0000000000b2872000000000067486e0240098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f000000002bc0d40100000000002200205be011cd1ed87926bd56a773c1b1907fe457ba0bcc876dc61f32c1ca85470e49475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652aefd015a020000000001010098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f00000000005141888002b42d000000000000220020143e4a3411ea01f087e0904a2ae0f4a312355368091aa08acf2279f9b6b72711eda40100000000001600149be0e57e4e3561b30fb11014bc3ed2036855381c04004730440220068466106db388aabc63e77d137b815649614d0ac2530589ab4c5e05d78afe3f022064c9ee1be2b7b794b6645c7b5ec8f0e69e727980dc7219fe1715e9803178a98201483045022100e8e5ad22d38e27f56ad53e43e34656026a74ae3527ceb06752f9114483b057d702205978489fb86927818e0b71deeed44c8c5827237f40f92483b36688cc48171bb101475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652aee93b9820000000000000000000080000000002ee00000000067486e00000000000b28720ed5846e908bbaaa6b2f994f3c03f8f96350d2825cd6849104207a8ee263849b302bcc40fecd594b6fa15d22cd5e9a8df070ce97e8a1e90b4fdd8a1ef0ddb4c0d97000000000000000000000000000000000000000000000000000000010000ff036fd9558ffbcee019936ae3251b1696cf0ec92a58a73dcc6e0c33ea64738242e4240098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f000000002bc0d40100000000002200205be011cd1ed87926bd56a773c1b1907fe457ba0bcc876dc61f32c1ca85470e49475221028f1b5095a886ec9efca0b014aa794c434f072fc8a435d2e98fe3db571fb265722103d5ddbf3cf1c396ad4be4c715759d8389a1f0b9bf1be1344c1258ec9bd79d2c4652ae0001003d0000fffffffffff80100a03da9ba67d38e18a51216c7d501d200aa3127445c959dd972193d3dd5951e7c0003ffffffffffe00098a98f1d736e4650bea1303e122e5e8c80005950b33590856332a2b1bea51f1bffef0000420000ff0088d2195367160d41c7c2bf78ce8996e62b632adfb829c8382955b1e8a5c27008dd7e9d91440a520b59c49af0c1a5a5e1768e5eab47556a81c6248278acc7f5757643497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001bffef00004200005f644c69010100900000000000000001000003e8000000640000000007270e000000"), + TestCase(PublicKey(hex"029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff2"), PublicKey(hex"02b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c23"), Transaction.read("020000000112f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000000e3400480012e7ff90000000000160014c6a763425c341e0ba7d0c12ed9126a661e38aac3a375b220"), hex"0100250000000003933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134000425ea3f2629ba65b6849f78829099a1d10000000000000222000000003b9aca000000000000027efb00000000000000010090001e0017a914d73900cac21afc97ce994c27af395e7f4d681bcb870000000182030540ba863ec0edc426981ffa8eace0be4e9a03295b3a0ff6ca545d21983e3b6b0000000000000222000000003b9aca000000000000027efb00000000000000010090001e02b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2303ac60bfe54dbbd771b9d4c92dd33b584d91cda6f4da296aa10f009f043743aa90034980f41ef05b9248f6793f77666534685df92b54dcb4ee1030f5dbef2e88a4e102177e1b10f681a1a46221c0f87aaa8e2a49d8257924d229faf8525e3f447374e5023c6191ef9b284cc6aa0c88b3913d91bf1bde6c5ec6f9d6f8dafa6bb65c2dcbbd000000010a01000000000000011f00000000251c000000000000000000000003cf01bee02412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c000000002b0c9af90000000000220020b2ad00d661059d790dea8056dfd8a962a53023a3603fda636c20eb8b4e434ecb475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aefd01300200000000010112f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000000e3400480012e7ff90000000000160014c6a763425c341e0ba7d0c12ed9126a661e38aac3040048304502210097df6e1d55051150897b7196be2944239dc1d820ae08ba72cffdd2822d57da320220403951e2355f52e385a34665156402cb2e85e6133b3d2606e37947809ccf4888014830450221008700f11d141bad2fc7eb66163a8f621685420b75bfb9f2c49c7227bf8fdb2ef702202f4a8485c71dad72263bcae61d6e17f21fbbe24331c3119deda2eaa1420cd74e01475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aea375b2200000000000000000011f00000000251c00000003cf01bee00000000000000000a6b6d346b134eeb64ed57d0b125934490af31cbb650813ae6454ab83d12042550331d262498b6ff23a23ac5a3e834afddf752bf1b5e81259233316a9ec72f5562f000000000000000000000000000000000000000000000000000000000000ff02db56e0e64c827f44aade7b016a37e6a7c0780db3666f7d8e8b422b6b02474b362412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c000000002b0c9af90000000000220020b2ad00d661059d790dea8056dfd8a962a53023a3603fda636c20eb8b4e434ecb475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352ae0006003f0000fffffffffee20041f7ae2006d76bc11dc18f8457cb4f76df375a85312315edbcfe508aca1596bcf4007a0001fffffffffdd002022e11a1cea6e7a8d662f74967d072ac654a72b59ada363c0e363ec4d26f8ef6800380000ffffffffff002065215a990d4af376ceb7647998ceea281d44fba94ec18c3a505f595fae0efe79003c0000fffffffffef00209a10496ebab959fe95ddfbe43f26cc9566bdc3a9cebc509e098367a2916b5e86003e0000fffffffffee40083551db0a0be081e7788bf7b13023ab3f306657022a9623a2179ca37c92f2e0d3801000003fffffffffb84008210ec1faedb71958c276e8e1502e7e1e588d4406cc9986157958e9d84f02a667e0001fffffffffdc212f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c00000000005f47acf200000000fffd01300200000000010112f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000000e3400480012e7ff90000000000160014c6a763425c341e0ba7d0c12ed9126a661e38aac3040048304502210097df6e1d55051150897b7196be2944239dc1d820ae08ba72cffdd2822d57da320220403951e2355f52e385a34665156402cb2e85e6133b3d2606e37947809ccf4888014830450221008700f11d141bad2fc7eb66163a8f621685420b75bfb9f2c49c7227bf8fdb2ef702202f4a8485c71dad72263bcae61d6e17f21fbbe24331c3119deda2eaa1420cd74e01475221029eb94e19945896a4cbe830d925272d3edb4426096b53cd88012edd1cc0518ff22102b8f1b6e1830cfc069650ea1d7571f7fffc591dd80ba0e2280a6643677de52c2352aea375b2200000000000000000012412f1713bd4417815932d254d0e2e1ddc9292f81c41c90de45e6ae6301934c73c0000000083b5c6292f61ad3cba1e702abb6d4ec1aec503c5b1b393e09c942ad4448a1b930000000000"), + TestCase(PublicKey(hex"03f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba"), PublicKey(hex"026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c27316"), Transaction.read("02000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d00000000008dc3288002ff7b0600000000002200206444bace1cb90460914f5c964acfdd7846104e93a205d0bd5d00eb73f03acced21cd4500000000001600148636658ef4d15c8d9824bfea9b70ddc592b24bb438497c20"), hex"0200050000000103933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b1340009e9907c0bc4f4e5b55a3b7012180e63b0ebf76498c543b991c056f0930a6ac09d80000000000000000000022200000004a817c800000000000000c350000000000000000102d0001e0016001495cde5680fa7aaa16935b74cc11b4ee695aa6b1500000004080aa98202cc1bef9c46bae2f3a965d7bf4bfca249098c5847b71d700ca0eba00db523986a0000000000000222000000012a05f200000000000000c350000000000000000107e0001e026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c2731603d49ddadc0517b1bb1f9c2773621e62b7720b2a622e4a5548becc37164bcd70f10368718d6b2887b302f75e5af0bac2f3b883663bde0fd61344ce16ed9a31c55eda03530674aa8610a599d0cd8be226923b5d8fe2a422cfbdc4fb790e39c7b05e0b7e0246ce4c46c2ee4110cce17bae3c7e6de01a8f0f8c1970cf58b2122b8fc77ffdeb000000028a8a0000000000000001aa0000000002ee0000000019545f970000000110b19269240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52aefd015a020000000001010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d00000000008dc3288002ff7b0600000000002200206444bace1cb90460914f5c964acfdd7846104e93a205d0bd5d00eb73f03acced21cd4500000000001600148636658ef4d15c8d9824bfea9b70ddc592b24bb404004730440220137ad66cce66df0041e172036c0c6de1e1cc8981130883bf693960778a2160fa022039d9505c98f78f54aa30172f4deccae26f9c4a7b55e2b72d4bd8875809143d5501483045022100ced9a63de710f1339c5d9086b1cbe0097f942872fbf1d0ca3a643409933cc86802201d5e311f655f64fb64eae62c9a3e321329158d055ecce9c537657979c17fe62501475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae38497c20000000000000000001aa0000000002ee0000000110b192690000000019545f97359430d3b112dd6d24edb449958073132a7721c737f2a72edf12ff74606d6c6f022030518cd036759a25ae66be2bbd46855ba6cdd214742f42aecd72b9093a444a000000000000000000000000000000000000000000000000000000d10000ff0200c2341c9b6d1ba8955188aa913cbf5b303c65ad19491df7d15fa0a811c44d38240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae0005003d0000fffffffffe580103151b3b767e35873dd3babc0f5bc9ea4a99069c269456b9e1157dda87db454b7801f80007fffffffff2b0020b4a390fde1b8bf038232ea79e48b825b5d1e930a97f2532aacbe0ddfaf24e9be00380000ffffffffff002038da5bbdce565d1eea32b7b9f4c4932e63892a154197054f5eb0116aab0f41de003b0000fffffffffe60041298f04989916fa9ac7b83da8a340a8bc6fcad73925c190576d728b28dfcf8de400720001fffffffffd00204225a2a872ea98ff1b22991635a2d6cb598839bd8a3988eeeb53adf784149fa880007fffffffff2b000e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000001e2d5a0004240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15eecd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15c4cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15bacd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae7202000000010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15b8cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c8700000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b150001240e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d000000002b404b4c0000000000220020c670f08ef02bd0e9226f315eb2e2d193d2946d4aa3d03e612a0bdda231cfe9c4475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52aefd014f020000000001010e985f9ec14d882a73f50843ad0fbcf37c644100b13c7eb3d91da703246a669d0000000000ffffffff02ff7b06000000000016001495cde5680fa7aaa16935b74cc11b4ee695aa6b15b8cd45000000000017a9142a90566bc7c0515d50552fb8bdf9e7f5c541637c87040047304402203167bba6344d2d82aa11db437dcfd17903de22079f58597f8927813cb96c32c3022036ead9938307bcc82561f85cebd8123b273ca26e9aeafc6e0502e97a38b508da01483045022100e0cffda9bd2bd045b99024d99597698a25803d42cd22c8b58d7a9e29363e79e802202a0e27d553bce59bb7b4e2cd89b847ade01d39bcb1027e0a7c0b6e0d4fff096601475221026345c7976588f6d96e7438f8df34e3225d527e9da5e201dbf83b43a1f4c273162103f4b51b541e6a9d935e83828bedbfcfb0ce404c809712376e672b624bb250beba52ae00000000ff000000000000000000067bff16001495cde5680fa7aaa16935b74cc11b4ee695aa6b15000000000000"), ) testCases.foreach { testCase => @@ -241,13 +236,12 @@ class ChannelCodecsSpec extends AnyFunSuite { // make sure that round-trip yields the same data val newnormal = channelDataCodec.decode(newbin.bits).require.value.asInstanceOf[ChannelDataWithCommitments] assert(newnormal == oldnormal) - // make sure that we have stripped sigs from the transactions - assert(newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txIn.forall(_.witness.stack.isEmpty)) - assert(newnormal.commitments.latest.localCommit.htlcTxsAndRemoteSigs.forall(_.htlcTx.tx.txIn.forall(_.witness.stack.isEmpty))) // make sure that we have extracted the remote sig of the local tx - val remoteSig = newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.remoteSig - val commitTx = newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx - assert(commitTx.checkRemoteSig(testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, remoteSig.asInstanceOf[ChannelSpendSignature.IndividualSignature])) + 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) + assert(commitTx.checkRemoteSig(testCase.localFundingPublicKey, testCase.remoteFundingPublicKey, remoteSig)) } } @@ -320,17 +314,7 @@ object ChannelCodecsSpec { 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 commitTx = Transaction( - version = 2, - txIn = TxIn( - outPoint = commitmentInput.outPoint, - signatureScript = ByteVector.empty, - sequence = 0, - witness = Scripts.witness2of2(randomBytes64(), remoteSig, localFundingPubKey, remoteFundingPubKey)) :: Nil, - txOut = Nil, - lockTime = 0 - ) - val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, commitTx), IndividualSignature(remoteSig)), Nil) + val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), TxId.fromValidHex("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), commitmentInput, IndividualSignature(remoteSig), Nil) 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) 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 3aa76a4714..52f4fea035 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.{ByteVector64, OutPoint, Transaction} +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, HtlcTxAndRemoteSig} +import fr.acinq.eclair.channel.{ChannelConfig, ChannelDataWithCommitments, ChannelFeatures} 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 @@ -24,17 +24,6 @@ class ChannelCodecs2Spec extends AnyFunSuite { assert(spentMapCodec.decodeValue(spentMapCodec.encode(map).require).require == map) } - test("remove signatures from commitment txs") { - val commitments = channelDataCodec.decode(dataNormal.bits).require.value.asInstanceOf[ChannelDataWithCommitments].commitments.latest - commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull)) - assert(commitments.localCommit.htlcTxsAndRemoteSigs.nonEmpty) - commitments.localCommit.htlcTxsAndRemoteSigs.foreach { - case HtlcTxAndRemoteSig(htlcTx, remoteSig) => - assert(remoteSig !== ByteVector64.Zeroes) - htlcTx.tx.txIn.foreach(txIn => assert(txIn.witness.isNull)) - } - } - test("split channel version into channel config and channel features") { { // Standard channel 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 44e8eb5e35..c6dab1b41d 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 @@ -2,7 +2,7 @@ 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, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxId, TxIn, TxOut} +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 @@ -18,7 +18,7 @@ import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal 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.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, UInt64, randomBytes32, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, UInt64, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -132,15 +132,11 @@ class ChannelCodecs4Spec extends AnyFunSuite { localOutputs = Nil, remoteOutputs = Nil, lockTime = 0 ) - val commitTx = CommitTx( - fundingInput, - Transaction(2, Seq(TxIn(fundingInput.outPoint, Nil, 0)), Seq(TxOut(150_000 sat, Script.pay2wpkh(randomKey().publicKey))), 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), commitTx, 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))), ) @@ -196,6 +192,44 @@ class ChannelCodecs4Spec extends AnyFunSuite { 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.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.spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs]) + val spliceStatus = decoded.spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs] + 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] + 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"), + ByteVector64(hex"03821c3180261d81126c2157282ea17be5131081c2d27c34f55a4000d06fc12b1a00bc7750d6e8d6765d1d53aae75cef20be8117cf7fc5c91c0ba017d3e214b1"), + ) + assert(localCommit.htlcRemoteSigs == htlcRemoteSigs) + } + test("decode local params pay commit tx fees field") { // The data in this test was encoded using eclair v0.10.0, where a single is_initiator boolean was encoded instead // of two separate booleans (is_channel_opener and pay_commit_tx_fees). @@ -230,48 +264,27 @@ class ChannelCodecs4Spec extends AnyFunSuite { assert(originCodec.decode(trampolineRelayedBin.bits).require.value == trampolineRelayed) } - test("fill transaction data for closing channel") { + test("include directed 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 - assert(lcp.claimMainDelayedOutputTx.map(_.toLocalDelay).contains(CltvExpiryDelta(144))) - val htlcSuccessTxs = lcp.htlcTxs.values.collect { case Some(tx: HtlcSuccessTx) => tx }.toSeq - assert(htlcSuccessTxs.size == 1) - assert(htlcSuccessTxs.head.htlcId == 0) - assert(htlcSuccessTxs.head.paymentHash == ByteVector32(hex"95cc3acd7792306bc5792d504325926816fe42457c6ad29b00741901fbe46d6c")) - assert(htlcSuccessTxs.head.htlcExpiry == CltvExpiry(400144)) - val htlcTimeoutTxs = lcp.htlcTxs.values.collect { case Some(tx: HtlcTimeoutTx) => tx }.toSeq - assert(htlcTimeoutTxs.size == 1) - assert(htlcTimeoutTxs.head.htlcId == 3) - assert(htlcTimeoutTxs.head.paymentHash == ByteVector32(hex"e60afd49d053a0660837e692ca97e6a3a83536bd40a7cce944d0e2c1d2abe7dd")) - assert(htlcTimeoutTxs.head.htlcExpiry == CltvExpiry(400144)) - assert(lcp.claimHtlcDelayedTxs.size == 2) - assert(lcp.claimHtlcDelayedTxs.forall(_.toLocalDelay == CltvExpiryDelta(144))) + assert(lcp.localOutput_opt.nonEmpty) + assert(lcp.htlcs.values.collect { case IncomingHtlcId(htlcId) => htlcId }.toSeq == Seq(0)) + assert(lcp.htlcs.values.collect { case OutgoingHtlcId(htlcId) => htlcId }.toSeq == Seq(3)) + assert(lcp.htlcDelayedOutputs.size == 2) val closing2 = channelDataCodec.decode(hex"001101ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a010102100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa00094510ff19974f78c704a51a7ed74ed76104201c9b8e2a84378a95c72fdea7bef380000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c000028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808020a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e03b240d3152962db6c8df5f492961cee063405a6d1d39a15ef4f6b040170be99e7028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120319bdffabbf3fd88d9635a556fadd2ce3332bd0a66b5ee51b09501dc8531f7e4e028a2744aed6398199fd4d3dce7b21bd994ce0c58027b17d93f10611ee40550ce10000001408000000000000000000000000001008028a5982000000014a0082ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a00000000000000003624f17e10a0ebcc71b4ce5eb541f3e68c73ffddda7294f38f411cd19f24aaed0001fd05b30080ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a00000000000000030000000005a995c05900ea5104b145117c6a48520742fb05d48a16a75ba1cc9f67a5eb90284746d400061b100003a52e309818a26a2737bed449f9310171509591cfd7daeb62216f633b1c9cc14faf2adceebf7dbfcf1fd7138442320ff99c54a4f316fc98b4cd7710c1a279bad88058e6795cb09eb288e07630e9829c6f93b851194363e479a1431ca2cabc4f4c092034cce4ac860533e0cd90b8b5a393941c2f7308ec4068b74c2d245cd6c5cce45a3ee16fc36dc2957134feec5403711489ef56382bbd69e98478323e5b1377564859b67effcec1f164fd82d452863ff2cfbe9e0bfd17b9dcdee5064216fe99df14f3676225321816898b971aa3710e4f954876212e7d446479eb0b5105eb57b90c0f0301d45010f627dc11a67d3289dd6463bb389264b52603e5cee4416f3a1f3d7a2a2da60a0620fc25bb8a01613920049e1a287d0e639462838cbcc1938ab4f441c319324eac70fdea294960c0918ea3361e4325e8da5728d025a3538686e0574e9f72c8745978af00a70f9c7b6610a87e47a720fded32f9af507772e0cb08c64b768d77378c18e3743a27d765aaad9a35cf7bac9e9af2ca5d2a1816d1bd8bba944539e9d85be8b254c82e8ce86333cbf82c72bd5a401eb8fd2e71d84c87f80c65798833f8d4faf5cbf3623f520c6e7c9badd474f6d582f5467e40de58204eb719663ef9f694325df4d7f7a5ef797d675eed2480c7c4f3158f4caeb11b9d0f05b3766c00570274c63475fa6c0f2420b2a19cf289a174961184a4a8dd02ba18d26b845d6960e22689bd790aee45f20dde4ec5156c84ac02c2553d9a5a6bf25a93da90b599420a98b30326509d4661bfb46d9e2f43a1fc97388313f7f3ab61615a728f27ffdaf43c7c88542672983902d79a20fcce03240d3795b58a66c720e1b4a0a9cfc04f368edef33d62eea9e1123ca4a5cb99792d655ad8c44604c42cdb3953335db24fe154b20559774b24ace3d35c2bf518640a5d8b4cd30aa0148e868296d6c9ea050e712f57f940f2693099671038042bfcb145712939129004148c59f79af7a9b7b3c8bf6be4d3a1719629195b7398841df73aee12bb332f132d96298003cee791a60b6c10d0708b3ae46944dd990437ccbe9a37827688150e8aeab68218cca38e8d03ce9ed65ae4831797f45d49ff6e56d72159601e9c1b6766e4feda60d957542eb5df5ab1ef7167924f4ef1d7b7661e9a4c9ee7d7df58d7a2cbc431addfc0a41dfc6e2425b7ab98632e366f5ecaf6ffdc1d73ed47269d1f6fc7c335b7c1f3206ece603016357018ccdf267267548adce056fa2a05c877660019742e10872ea2e24c0b9b853a099f38ae8221b314730bf070cd37859418cf26425d499207d75d5022297d634e05bb4ad6462b44e11a8b9dd6516468d7b4686027b50319fe5676abc4cd25477aabc47f7cf0c1b64c26001f8feac705fbc009d1164cd4811179fda86ae2b93999725b484338d15fbc1c93aca46853a69c6089879e1be8ee2716e0bd3f530de7347ef09443e0a2502ec396da67b13171d0ffddd8b21dc1dafe157c933e596cc52fe1ebe663274a0e058b6b7465280bc27e2c0fb1ccecf7289ad584efef96b0804a1af10b182f788f1b324eb769333af99c7a8f28f6f446032cb387a480354f06bca5960df22b59061946740168d1a383cedb7d7cbccbc3fcff91836ed87d4f3366a6ae7a030b49d37d43a5cee8a85d7443720df1bd57c954d57d5ffdfc9bfadae3754d7ef68830bbd2354069364e99c95d0f72b84fe2d880427c7e41435d3aaa47b2ed28eabe332af0221997386aac37021bcbbd78b8cb12b9f439d8154bf17b5f0252c1fd3c884eb0c723abde233539bf94bef906bc260bc3496cd62c29e82bdd831ea9222af8f8fa28e295409bc4bc7532946e02e6b046857ab8a7d97714fcaf64b1e45ddd1127b678499005bedc4a00a4134b11b2c934e7ecbb164afd9a5964a2def87ffa03bdfe0001a14701070000000000000000000000000000000400000000000000010002fffd05b1ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a000000000000000000000000068e778014d153e87b1c076e0ec079c69f724ad514e470cc9497403c0e7581d20de53df400061ab00002fcf3a4b860fa3e927bf185dc7ddac5b2b5af8bf44dc60da6a8edd8609304afb974f4436aad796a089ec4d7ec2fdfc28070fe0e4b1e5bfb4ce4cf44b6b32a76edb353eacbc7d1ccf503fbd11d3ee7110bc436432bef8e16d91c118bb8b1e84d2f5cd259fdd4dd9668e71cbfdee1cb27c6e528e66e70b69b9df9dd2d2aa05ce8f7c744aeb5bddd0ea456d1d893f7935174f3d70a19f363cadac78bc686b14e22b59bccc90be9c17464c5faab973a806e61aba13c54466c3ea3df26795b88df13846c6043a8e0cbdf2a9ff3484c951d6ccd7fc04f0826d60ebbd2a096b7ba55c114eb5d038d4a501376265d1d6fd056ea70c6d7951369c4aa81edea8aeba8afb9b9fe14320cb0c3f5cec8a2cc508b9480ebe630adc8dd3af0b21eba4d0c39f7c303b983f2af0c8a0ce17a54709544923c557c889712e0913f7fd8c1ce1912d98907e4b14b27019e167f25fda91f0f96916e15524cab8dfaddd58254f02993c1ae71a463297772d3887053cd228826a23f0faa27d51fb71e296f06107071bd54f9c4ee208dc39fd824f059015186fd83cec58c988a5d920fbae51840db4dd643b0e11461832c20a5d1a86813c84ad89ca72d76ebccd739244312493daab09bcbbefa5eacb0cbce18ad5d21f2562c82c59e5de947dfeff0afcb8e5ada37eff448195e911af1b8ccb3bbc0f0d184108d72cbeecbae2f01e5151eb3a0e19022abafd13a8de290f2c59afaa991a8bc05afe046d19e2802fc73646eb7a1e359d49288dba9cde637829e2c5b2022fdaec0882fc0117467ef4089a708ae2797f69e989b1d00e5dd83473b8657bf2ba18411fff74c6a81b7b14771c2baeeb34608526fb194ce4526fc207d99e93b25d0f6c777b631f402f31369f0aa7ecdda31020c0b74dd5e5d67f01d44af2bde4e7ffdf32f48cc731adf08fe3d5a8eb6dd9ed2115b917474bdfdbce2db780a6f62115dc54948fc32ddc3e7552614eb8d7d7b1106c3fd239054b03beaf32e3426d2ef7e2815e7527b4ba158c02dffe84a543bf15f6be831d01b7e37907eb13d7e69dd7cb032f1e78ec4d55328ef188a8db606bd608adfa1ea93a7fc690a3ea435d9c7eb59047fd2dc541bfb4ab7969cb994e03a8a283ca1a4a1331489a5c0793aaa7346912ea09d23baca1f8a381f5040e16af01c901ac2f2973e711c8e3eb889b4a007bc5a2e70dd61e70e48034a3713e99a759a6b04233c80a280bfb794b83f57bbea26d4b00dac61a10f2b6674e494004823dacd2872a2fe349e015c9f8bdd042f686fc73590870c0cf0ce6172101baeb852a213c07c5ffb6a70f833a4c65e47a65e9ff61cadf660628f49970347cbad79d2b5b4f8923330287630d00e095d730d2540421170f4f8560355115aa7c8301a6495e22c9a2c38de611ac0854db03f2bf8e7ccc3a973c6513862ee2b98fdcf7acbc14ce2bcb8442588cca624aeed1e4297d6b272450735e6b6a87b273877a38e17b4ee635d0f6254136db74130ccf5dc287e0b24eeef94deafb81fd5803ff1de1db3c52f65b242e26ce733efe010e58ac2864542c1ffcc5b49959f8b207f237b8a0b86e95393eadf9c778182a1be4680a9eaaae7760fa269ea3afa04595f4c970fe854beacc6e5b4d95de3b9915b24de58271f455a7792bd3a1b794c6eed63b9bad7d2ec1ffdb413fc8aafaf8c11914310e94d5ba3b9673ed77301cf37e65463293cc612fcacd98c15174f06e45e57f135e7573b2ab7a1feee7821f2f0aee3aa35338653c7f17b85f5acf321f48b0e576109b4158efaa1d9fe610fe569e7a639a0ebe0b38facd8e252549d09140b99e175ffc8aba082ddb66c759698d66367c1dc7cc45f03384a3a55eb5e9cc8e681c7824a1de443cd0069ae890a53979b08b4bc07235e7f2aec993f6e02d4e5c645e7e646c2cb1b8322f490a0fe0001a147010700fd05b1ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a00000000000000030000000005a995c05900ea5104b145117c6a48520742fb05d48a16a75ba1cc9f67a5eb90284746d400061b100003a52e309818a26a2737bed449f9310171509591cfd7daeb62216f633b1c9cc14faf2adceebf7dbfcf1fd7138442320ff99c54a4f316fc98b4cd7710c1a279bad88058e6795cb09eb288e07630e9829c6f93b851194363e479a1431ca2cabc4f4c092034cce4ac860533e0cd90b8b5a393941c2f7308ec4068b74c2d245cd6c5cce45a3ee16fc36dc2957134feec5403711489ef56382bbd69e98478323e5b1377564859b67effcec1f164fd82d452863ff2cfbe9e0bfd17b9dcdee5064216fe99df14f3676225321816898b971aa3710e4f954876212e7d446479eb0b5105eb57b90c0f0301d45010f627dc11a67d3289dd6463bb389264b52603e5cee4416f3a1f3d7a2a2da60a0620fc25bb8a01613920049e1a287d0e639462838cbcc1938ab4f441c319324eac70fdea294960c0918ea3361e4325e8da5728d025a3538686e0574e9f72c8745978af00a70f9c7b6610a87e47a720fded32f9af507772e0cb08c64b768d77378c18e3743a27d765aaad9a35cf7bac9e9af2ca5d2a1816d1bd8bba944539e9d85be8b254c82e8ce86333cbf82c72bd5a401eb8fd2e71d84c87f80c65798833f8d4faf5cbf3623f520c6e7c9badd474f6d582f5467e40de58204eb719663ef9f694325df4d7f7a5ef797d675eed2480c7c4f3158f4caeb11b9d0f05b3766c00570274c63475fa6c0f2420b2a19cf289a174961184a4a8dd02ba18d26b845d6960e22689bd790aee45f20dde4ec5156c84ac02c2553d9a5a6bf25a93da90b599420a98b30326509d4661bfb46d9e2f43a1fc97388313f7f3ab61615a728f27ffdaf43c7c88542672983902d79a20fcce03240d3795b58a66c720e1b4a0a9cfc04f368edef33d62eea9e1123ca4a5cb99792d655ad8c44604c42cdb3953335db24fe154b20559774b24ace3d35c2bf518640a5d8b4cd30aa0148e868296d6c9ea050e712f57f940f2693099671038042bfcb145712939129004148c59f79af7a9b7b3c8bf6be4d3a1719629195b7398841df73aee12bb332f132d96298003cee791a60b6c10d0708b3ae46944dd990437ccbe9a37827688150e8aeab68218cca38e8d03ce9ed65ae4831797f45d49ff6e56d72159601e9c1b6766e4feda60d957542eb5df5ab1ef7167924f4ef1d7b7661e9a4c9ee7d7df58d7a2cbc431addfc0a41dfc6e2425b7ab98632e366f5ecaf6ffdc1d73ed47269d1f6fc7c335b7c1f3206ece603016357018ccdf267267548adce056fa2a05c877660019742e10872ea2e24c0b9b853a099f38ae8221b314730bf070cd37859418cf26425d499207d75d5022297d634e05bb4ad6462b44e11a8b9dd6516468d7b4686027b50319fe5676abc4cd25477aabc47f7cf0c1b64c26001f8feac705fbc009d1164cd4811179fda86ae2b93999725b484338d15fbc1c93aca46853a69c6089879e1be8ee2716e0bd3f530de7347ef09443e0a2502ec396da67b13171d0ffddd8b21dc1dafe157c933e596cc52fe1ebe663274a0e058b6b7465280bc27e2c0fb1ccecf7289ad584efef96b0804a1af10b182f788f1b324eb769333af99c7a8f28f6f446032cb387a480354f06bca5960df22b59061946740168d1a383cedb7d7cbccbc3fcff91836ed87d4f3366a6ae7a030b49d37d43a5cee8a85d7443720df1bd57c954d57d5ffdfc9bfadae3754d7ef68830bbd2354069364e99c95d0f72b84fe2d880427c7e41435d3aaa47b2ed28eabe332af0221997386aac37021bcbbd78b8cb12b9f439d8154bf17b5f0252c1fd3c884eb0c723abde233539bf94bef906bc260bc3496cd62c29e82bdd831ea9222af8f8fa28e295409bc4bc7532946e02e6b046857ab8a7d97714fcaf64b1e45ddd1127b678499005bedc4a00a4134b11b2c934e7ecbb164afd9a5964a2def87ffa03bdfe0001a1470107000100000000000000000000000002d87e81abde4e3ec8820378dfed9b23de246ea99520120d1f44fb5f08c6e25cc20a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f00000000002200204187b4de4877993f8eaefba883cd1b5ced129cbf65b32369ce94278ba373bea000000000061a8000002a000000000200000000000000070001ff000000000000000000002710000000000bebc200000000002920908024ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a000000002b40420f00000000002200204187b4de4877993f8eaefba883cd1b5ced129cbf65b32369ce94278ba373bea047522102d87e81abde4e3ec8820378dfed9b23de246ea99520120d1f44fb5f08c6e25cc2210321b0573b4983b21fae946f9d5813b49ba551ae937e04453d853ed2b17ebc0e1f52aea80200000001ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a000000000036a2ab8003b0ad0100000000002200201991d1de5f3d395a979d0fa4ff88d3e7226f66e6961c6641d75e6be850a74f9840ea020000000000220020c9abd3d347f1d3f9b06cf38adb0e466d30ac14531d66ee4a58468da201b1744250870a0000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3ab6789203cc33db437cdc8a155697a5623fc8b95dcdbbe39ae6b8208a87d3d9401b9cd583fd069d733aae4c3176064e906688968d6db67235901f66e057370398bbf03f600011124b25163410a7498125e4b9b7f8b7d9de5058aa17c33b434d20f02bcafb73351ee000000002bb0ad0100000000002200201991d1de5f3d395a979d0fa4ff88d3e7226f66e6961c6641d75e6be850a74f988b76a9149ffa573e4ee636e634f219fadf8c6b12d80486568763ac672103aee5bce7fc5e18ba065474648005ea3e9f38cf970384dc5f8d2e4c03cb516f957c8201208763a9145d87298de5ed29a001ff24c4abb3d3d091682eed88527c210227923219b11f31e65eda4b48e5e1332c64b32855908f3174b3e1988046e40a8152ae677503b01a06b175ac68685e0200000001b25163410a7498125e4b9b7f8b7d9de5058aa17c33b434d20f02bcafb73351ee000000000000000000013a92010000000000220020c9abd3d347f1d3f9b06cf38adb0e466d30ac14531d66ee4a58468da201b174420000000014d153e87b1c076e0ec079c69f724ad514e470cc9497403c0e7581d20de53df4000000000000000000061ab0bb4dd41c527120c21c93f2fb47d1c2ff8cd3a1c02d7ba84ee75c722ecdab68736c29f05f36d2a459b88465138f324a4f7e5d6d1b68cdb0424b0d5a1211c5ccfd00000000000000070001000000000000000000000027100000000029209080000000000bebc2005d845aa65434b27605eb87c7f6140341ea62830069e5f920fadee8fbd04cd9a3039e9c9873bbd72749e8e7c6e20113eb165f1311c54aafcfbcc4d17406782919e0ffe2ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a464775ee59a1cf732abfcfdf379802dcd35ca156552bd4bcec993fd4f973c8923036941dad06a84c462934b49129149adc5ec9966cbc15e9b1d45dcca22dc285000282a40d98f56378d7b5317259cebc9ee7bf988978cc559c364abe11b02d5bc23279e0ec9e9d150f45277c8eb15f7c06e2cb78b61c693a6930ac833fcac743d2f57484d9079265129578deab29ec880838c33533a6b6ccf8e2809589ec102cbfb054f911ce68034c4a85af246805d2a486bb5651046658b9f4d8b374202fb3d12b00000000000000080002000000000000000000ff00000000000000030000271000000000292090800000000006422c409e3a670043626160e4168680f6c9ed54f3e4a36a6431ae8b033e952733d8df4203f6df89806aac70525348c9992304f87c6696af5b79a873babebc98647b5c8dc400000000000000000000070003003e0000fffffffffffc008303fe69a5668f4ab7ab30ba3351c2cee3ea694ff845fd1597d2021bea85133e5400fc0003ffffffffffe80107be162987941764fd52d41bc68e8bc2608490344f5262a6ba73a1cef4cf96635802000007ffffffffffc80101a7d494b974c327cd2c2a51582cf428c0085ea2c28e84256db34e90e1903b8f5c0003ffffffffffe40001000000000000000300038aa4d004f59a49509c75b39e5724f9fe0000061a80160014761879f7b274ce995f87150a02e75cc0c037e8e30000000000ffa80200000001ecd251e9bcb26a5a0414297b97adc533034f14ce81c0ff05c2fb4b1b3360976a000000000036a2ab8003b0ad010000000000220020a913e317167390473b4df76d339637dd116746e0f8a3014a15e028b18b5edf8d40ea020000000000160014761879f7b274ce995f87150a02e75cc0c037e8e350870a000000000022002062c4769ddc7b9e379dade5ad80b8100d540b0df91868e454fe1a5d4195351a1dab67892000000124a3d94cd0fbe8defa20f9e569008362ea410314f6c787eb0576b23454a65a845d00000000ff2324a3d94cd0fbe8defa20f9e569008362ea410314f6c787eb0576b23454a65a845d000000002bb0ad010000000000220020a913e317167390473b4df76d339637dd116746e0f8a3014a15e028b18b5edf8d8576a914acc2a5ecf25b533fee601c4e694c06c53d32e0268763ac6721030afb6f8fba31f5a4394cd3a1145268354dc9464f3253d311333cfc7591c4cb697c820120876475527c21032dd2755000aba32a5442d6d6bf0488d997a1988c534092ac6bf244c7c864ffbf52ae67a9145d87298de5ed29a001ff24c4abb3d3d091682eed88ac6868fd014402000000000101a3d94cd0fbe8defa20f9e569008362ea410314f6c787eb0576b23454a65a845d000000000000000000016297010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3034730440220337e642e9d285ba15a93d070e8a7d5e598de5f90799cf64ec1b4aa5906ccfb9002204756e51351e1afadb73af8072b5b9ae7c9f3a777132d1faeea8240b5fdd6810101203624f17e10a0ebcc71b4ce5eb541f3e68c73ffddda7294f38f411cd19f24aaed8576a914acc2a5ecf25b533fee601c4e694c06c53d32e0268763ac6721030afb6f8fba31f5a4394cd3a1145268354dc9464f3253d311333cfc7591c4cb697c820120876475527c21032dd2755000aba32a5442d6d6bf0488d997a1988c534092ac6bf244c7c864ffbf52ae67a9145d87298de5ed29a001ff24c4abb3d3d091682eed88ac68680000000014d153e87b1c076e0ec079c69f724ad514e470cc9497403c0e7581d20de53df4000000000000000000061ab00000000000000000".bits).require.value.asInstanceOf[DATA_CLOSING] assert(closing2.remoteCommitPublished.nonEmpty) val rcp = closing2.remoteCommitPublished.get - assert(rcp.claimHtlcTxs.size == 1) - val claimHtlcSuccessTxs1 = rcp.claimHtlcTxs.values.collect { case Some(tx: ClaimHtlcSuccessTx) => tx }.toSeq - assert(claimHtlcSuccessTxs1.size == 1) - assert(claimHtlcSuccessTxs1.head.htlcId == 0) - assert(claimHtlcSuccessTxs1.head.paymentHash == ByteVector32(hex"14d153e87b1c076e0ec079c69f724ad514e470cc9497403c0e7581d20de53df4")) - assert(claimHtlcSuccessTxs1.head.htlcExpiry == CltvExpiry(400048)) + assert(rcp.htlcs.size == 1) + assert(rcp.htlcs.values.collect { case OutgoingHtlcId(htlcId) => htlcId }.toSeq == Seq(0)) val closing3 = channelDataCodec.decode(hex"001101b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b010102100002aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaa000947634c4314cc9859632bb0db04f4aa6194fa4c7b5a24f94ed30197557e232fcb80000001000000000000044c000000001dcd65000000000000002710000000000000000000900064c000028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120000186b02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001808020a598202bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e6300000000000003e8000000003b9aca000000000000004e2000000000000003e80090001e0351cda699fd1471468b07360b09aa63d9f0b7066030c053ffc17c708ebc5fa5a7028feba10d0eafd0fad8fe20e6d9206e6bd30242826de05c63f459a00aced24b120379bf217b5c2ea4602f186fb3abff38198ce69a2c48215a0f53bac42da7b8eb3e02595899924a395a4af0987f056456ee1a4133c0183227e72af934d1a2f424a18b0000001408000000000000000000000000001008028a5982000000014a0082b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b0000000000000000e559091e6b69e409919a0956d1fe2e786262446548a920b203fa3a7ce51146ae0001fd05b30080b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b00000000000000030000000005a995c0028fd04843d9cea2a6053932960308a3400dd79873bbdd9b7bf5325ad5eaa2e100061aa00003883bc264072d01155f2ef9b27242fe84cfd26949e8832d38839d5a3924647c045d901a42ff93b586fbb75dc27ea987d2419f08db8fa03bbee2f76ecab9e06dd09374be37e8be3884bbcbe1eb61b2f9087bce50586ebe8401a825f6547e0255b89200b9ec3944798ab66600bd964e21ecb9e7294b10365fe9bff532244e6dc2ef42a90b84bf2a4a69c6160c5ae3b540a49e32b85c059f9d5441f99789fa381d61ce56e276e920ba7e84cff9c15555378d2ee0d4cc1b473f666bfa5ca27289628f04a2470b26934c4ed3b637abff89afcf669b90872bb4dac908d9662f9cf4c6ae0860cdb3d3c98aa6aeb1a6bb078307a8c5f51162165f12ae47540732d04f4c7736bec7644b6f1d542dcd66db6c51a9a3e3b046dfccff15db283df3f7784f06f49eb79bb2b9bd27e87b62ece40f2522e2119799cad60d3491dc470fa8389c4a0ba203cb3cf703f1fc8520d7d24f2964d531e506c98b436303b98615f6fd111cd3eb37276935eb900cb0104a741f4b44de3371eb578ac59314790362426c76838358d352940302d03d9aecc50918dd81748558cff571f5db3514a955a28bb570daa047eb2868d787b84b5707979aba1083e0d22d06890a6fa79f6b0b56f86da80ae79cb9c45641aaca16d57cedca81841d780b8d684f0f9347edccee172a8710248920981195fec9378111293193d6fccb94f438a59ec272130d644c9de7f1d5c1e59bf9a41782ac5a2947f7eba0f6fdf9013a7254c6bbc0c325d50cc69fefbcd83dcd009f7bc26d28aaa622b1f441a9fe95782e5d546fd7746949fe21fa431b4a49e93a9c6e501725977f94f1d8e06e127f15a1ee1beafd6e4a691d1f384315e1060d7c06411f207571128d46dc571d3ffc9afb0c79e2502e5ed02e13c59cd099ad1eeb255af86a64da908d91702ba62739c0dd2df4abe7b0dff71ef24be7c09bf17b280a727a9787e2c8ea4cdb85797cdc2b538ce54791349d61f0b40d134ec667cb3c53d13697af5165ffcc05c7aa847a51986d1beeda7b5d1b644f60217939e35f989fe10ce015253895845ff881472b3f477a78f90998e93e38f2b3c42dfabb08b4d2c1a3f8cd0a969334715ee1b9021a545d9fbddded126b1b93edd068ef25eb7ed73d29249fffeafbcb66badd559422c04120a90001608f712fd55b993bc12f5a540b87dbe1960d388cb8c1e691a7a6990160669532548fe1ed9baa406864d30796db7704030edda83031994a21bcaa2615cf81d5cfdc0dce02bb8771c0d161c17ddf8279407872a7db2374a3a5b159a082a582faa27681215ec63902807a38e48c5fb4973e9fa553b9fb3019971be20e1ea590575d4b595279f791dbdef338a28872e0e06a43ca2d7b74d9474429b2c8069849e626141b87fbbad950f1f86a30f6f24b92fe73f76b4a065ef881ebc168085c5950650b82fb6c92dbda1ba8002b995d35a916e2451e2008385defe1433481dda3d60ab43fbe847233522918906dad9169a45eb8799cf9fb5e9718684a01b30c65b3285c9ff5e5408346822d9a2898e2f717cd3e2f4056af575e4657dbe78b9549b28242034adcec1e3fa1b321997d735ffaf20b1cc9332b7af6f664225c16ae6debeb1d07a17549e5cdabba902f1caf74bb4f2ea6085f219f9edc18eaf2a0193009712d1deaa3ace9a8c05eeeb45aaae5853361d105aa20807647e4404b8e7516971fb0f45cb7fb98b5fc62e39fe13d9243d94cf7fe0abd8be94fed886edc4796d00c37c45a68efc08c19fd172914b5ccce42260d21f99b9ae5f20afc5805b0c65ef52c1678e479ed139076aec1bdca9df21a3e6730a16129b70ebb372a236e1ddac607770bc4033180dedc15bce4afc211644c9c3b5ceca5627b5b3122987eb5397067783eb45624da4d5a189cd340c0d33c7ade63a64a74ee2eec01a5b1fe0001a14701070000000000000000000000000000000400000000000000010002fffd05b1b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b000000000000000000000000068e77806fdf03f953dc6040f0ba2f511c65f70e6d6a32d050db40f8400419fe70ffa0e500061ac0000323fa7a843bd1b7b5e80a2a0401eba107865debb4a99a43680fbfb043046dda23faf07641c4f8ac14552d6e43089d8bd46069aed363c51b0d680e3794a950fbcecc9605d139e9790281319bd53785597ac05e9b545542b11a0f70623e088c3e1565632fdb2a7f51194de9c7094595943ba196d30d5ee1b862b9a306bde44e727297f14289826439541a605ed07c98db42a07db9092ca8c0e127e5b19d6c013386a6e4ec84456e543e9bc4be008e81b6b50c8fb7a4264847f65c0dcc121aea93378ad1d7ceefb9ff87ee1152d11f2b5c17222087268230b2e649290bfda463a41230ffc841d0d06ba7ceed825030fbed28b2519bbf72bf082c83e0abbf2748c4ab786dd4c18f83aeec7718d1c13bb7568d3f3b530acca249258dd64d2fb142e31c0b3ffde72233d6a86b1e10c5357289c72b1bd2ff0565c44aa143b877f2e7144a6c8032b512af112533947b66925effde588f21072534dd1d2efdc40efbcb67eaec37e2f26e590b269c1c8e93337b1d36b3516f43f993a0e5aea2dd6c8421458c41cc262ecf7007b5cf8047404f816b0ed63f6a4c737b78ce0bb74fae381edf3ffbf6fbb3de91cf4922d99d48418f2c5e22f9a187e74c81f953b923d9d16af865f380d62ebd31916ce265393fb6881372e34ee04bc566811320f50ca488fdf480b453f4240447704a617cff060bd32940dfd130eb77513839d2b8de0d48a0d58a29088202bca3e3d7ff38ca820a20f157624169abd68a36f692040032b68f67e500c4d84039a53365e857a0cde0218f8caf331483e904848a3379569e61a01d470547e0b376a6ce824b7c84e9d2ae57fc2e96eb794cdb7317c7f5d978c0f3c1c3e74afd89d234b7cecdd571c5a54c756c14e8d37e5bc2a4f6430ecd80288502b6250be70d52b40074dd0e6284184ad0af4e8162f7d356d76afd74120edf0843ba86f03a0b43228d17ba2ac79b7953f178a1af17f3949fc6020bebcc48503f51e81d2e8b0a2c15dfa89316d9a116d4808f3f45dc75c7d145d2f10d6a5490483b7c92084d6747aca140a4ddca23d25c64cbb74e4d77476fbadfb038ac7d228ab711baadde3b2291ec5b20bafb2f2c2679cf482a2d2725edfaa70acb3f91d68cb532d09bb13fac514079cf60c19f4564be9ad350fa5e34d9f35a40ac32e9838886727065da0b8613000efe8db4ebc5744b9d9deef00747340f40dc2d977a47bbd4ce40dacf9aaa6f798f430a1aa9920f0c2488a48717e6276c6d140577d2ad050d163402ecc04d0008cebe1630ee70d389980edc3ef68c251fa5b075ddc613b96875a77c5984e34163b1e15a86068ec0b3f7fd2f482a57ff6b465837661ec3148d860918515b25f19535db6c0770c05738651ab8c453cbb066c45e4abab96381e8fd0609d8564d0345490c1533d58e58e54e27518d10df186ef6f85fbb3ed0fcde0e55ce19ad11b9b87c837e1e7a5f0834a637c02a20f434b6e52aa6cb8ab5d7f495489ffad8848e7041e6464e4b4bbbce34603ab447e0ee9b1e0a30f5fa858559e4a792b546ea29777bf393231c3e40aa207c13297fad343a2c73959417837f2268bb5d849f158e53ac00261742d91e429e5b330ff284f9b61f3952595d45ed187a7a4946f7664a14b44e5a5c21f9fe5f54e283cb4511c9e26fbf30f40f84c2c67a791fe19a02f6abd4253bf087d93b9a6f73e6a330c8f5848b644907d50fac0eded6e389f05038f6db634e9ac155b38621416254588fc15579163fef54779d139c192615618f12c72dbee711fde597bf6af98e5846241fbe9d572b1cf8eb9afb0e3c4b690da5c20d7e4362c1a69979ce9ef9b4ee7630a711879c03e9ed9d7fe35a02910dc5cadba8d4cb60ff1b4aaf51904ca4ec8e2aeef0e14a42ebdf8f24d73b2ce445f57710a15ee2bd2b7494a775ab9c03dbe9fe0001a147010700fd05b1b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b00000000000000030000000005a995c0028fd04843d9cea2a6053932960308a3400dd79873bbdd9b7bf5325ad5eaa2e100061aa00003883bc264072d01155f2ef9b27242fe84cfd26949e8832d38839d5a3924647c045d901a42ff93b586fbb75dc27ea987d2419f08db8fa03bbee2f76ecab9e06dd09374be37e8be3884bbcbe1eb61b2f9087bce50586ebe8401a825f6547e0255b89200b9ec3944798ab66600bd964e21ecb9e7294b10365fe9bff532244e6dc2ef42a90b84bf2a4a69c6160c5ae3b540a49e32b85c059f9d5441f99789fa381d61ce56e276e920ba7e84cff9c15555378d2ee0d4cc1b473f666bfa5ca27289628f04a2470b26934c4ed3b637abff89afcf669b90872bb4dac908d9662f9cf4c6ae0860cdb3d3c98aa6aeb1a6bb078307a8c5f51162165f12ae47540732d04f4c7736bec7644b6f1d542dcd66db6c51a9a3e3b046dfccff15db283df3f7784f06f49eb79bb2b9bd27e87b62ece40f2522e2119799cad60d3491dc470fa8389c4a0ba203cb3cf703f1fc8520d7d24f2964d531e506c98b436303b98615f6fd111cd3eb37276935eb900cb0104a741f4b44de3371eb578ac59314790362426c76838358d352940302d03d9aecc50918dd81748558cff571f5db3514a955a28bb570daa047eb2868d787b84b5707979aba1083e0d22d06890a6fa79f6b0b56f86da80ae79cb9c45641aaca16d57cedca81841d780b8d684f0f9347edccee172a8710248920981195fec9378111293193d6fccb94f438a59ec272130d644c9de7f1d5c1e59bf9a41782ac5a2947f7eba0f6fdf9013a7254c6bbc0c325d50cc69fefbcd83dcd009f7bc26d28aaa622b1f441a9fe95782e5d546fd7746949fe21fa431b4a49e93a9c6e501725977f94f1d8e06e127f15a1ee1beafd6e4a691d1f384315e1060d7c06411f207571128d46dc571d3ffc9afb0c79e2502e5ed02e13c59cd099ad1eeb255af86a64da908d91702ba62739c0dd2df4abe7b0dff71ef24be7c09bf17b280a727a9787e2c8ea4cdb85797cdc2b538ce54791349d61f0b40d134ec667cb3c53d13697af5165ffcc05c7aa847a51986d1beeda7b5d1b644f60217939e35f989fe10ce015253895845ff881472b3f477a78f90998e93e38f2b3c42dfabb08b4d2c1a3f8cd0a969334715ee1b9021a545d9fbddded126b1b93edd068ef25eb7ed73d29249fffeafbcb66badd559422c04120a90001608f712fd55b993bc12f5a540b87dbe1960d388cb8c1e691a7a6990160669532548fe1ed9baa406864d30796db7704030edda83031994a21bcaa2615cf81d5cfdc0dce02bb8771c0d161c17ddf8279407872a7db2374a3a5b159a082a582faa27681215ec63902807a38e48c5fb4973e9fa553b9fb3019971be20e1ea590575d4b595279f791dbdef338a28872e0e06a43ca2d7b74d9474429b2c8069849e626141b87fbbad950f1f86a30f6f24b92fe73f76b4a065ef881ebc168085c5950650b82fb6c92dbda1ba8002b995d35a916e2451e2008385defe1433481dda3d60ab43fbe847233522918906dad9169a45eb8799cf9fb5e9718684a01b30c65b3285c9ff5e5408346822d9a2898e2f717cd3e2f4056af575e4657dbe78b9549b28242034adcec1e3fa1b321997d735ffaf20b1cc9332b7af6f664225c16ae6debeb1d07a17549e5cdabba902f1caf74bb4f2ea6085f219f9edc18eaf2a0193009712d1deaa3ace9a8c05eeeb45aaae5853361d105aa20807647e4404b8e7516971fb0f45cb7fb98b5fc62e39fe13d9243d94cf7fe0abd8be94fed886edc4796d00c37c45a68efc08c19fd172914b5ccce42260d21f99b9ae5f20afc5805b0c65ef52c1678e479ed139076aec1bdca9df21a3e6730a16129b70ebb372a236e1ddac607770bc4033180dedc15bce4afc211644c9c3b5ceca5627b5b3122987eb5397067783eb45624da4d5a189cd340c0d33c7ade63a64a74ee2eec01a5b1fe0001a1470107000100000000000000000000000002ff4c609c1b9a4b70b8eaafedf018a1cb24a83918bdd1b87fde8c1905c84f450f0a5e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f0000000000220020224b8c58b3fa0020f800829615232bf183dc41b13603a67cce9b94dd28f8bb2800000000061a8000002a000000000200000000000000070001ff000000000000000000002710000000000bebc200000000002920908024b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b000000002b40420f0000000000220020224b8c58b3fa0020f800829615232bf183dc41b13603a67cce9b94dd28f8bb2847522102ff4c609c1b9a4b70b8eaafedf018a1cb24a83918bdd1b87fde8c1905c84f450f2103d68951fa91802e65a951917f58bf547a615875b6553251fe91c940f98b62030552aea80200000001b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b000000000036a2ab8003b0ad0100000000002200202faa5d052a90a5e8ef750efc0f43205cb84bfbe8d0a97c8ebbb0fd7d30be8dab40ea020000000000220020822d615f74a3f1da36d31bad7fbc39920436895f15137aaefa767f5e698af36d50870a0000000000160014761879f7b274ce995f87150a02e75cc0c037e8e3ab6789200bb8316328924ab4641b821b72c9fb1cf9b51311c1e3ffafe291756ea403081e130022dc33eb8a765d0909454ba6bf3a5a74ff5467a1d017e63758eabf91b4ad000111243e453164b4e4944314473df2cd44617c007a69fe00819de3a71a3f39605af67d000000002bb0ad0100000000002200202faa5d052a90a5e8ef750efc0f43205cb84bfbe8d0a97c8ebbb0fd7d30be8dab8b76a9144b261689dafc1227a18f487a7929419c6591d9c58763ac672103c50b9d3b40403c354d73948670978f008a3a671c7f6376f6fc7fc3d0e3e1cdaf7c8201208763a91423a2a6f93c2a944f662595191a7829411630ea9788527c21026b7bfaa3fff831c853e94ecce416fb41c23e8fa74b7f408cfddde01b7cdb798852ae677503c01a06b175ac68685e02000000013e453164b4e4944314473df2cd44617c007a69fe00819de3a71a3f39605af67d000000000000000000013a92010000000000220020822d615f74a3f1da36d31bad7fbc39920436895f15137aaefa767f5e698af36d000000006fdf03f953dc6040f0ba2f511c65f70e6d6a32d050db40f8400419fe70ffa0e5000000000000000000061ac071e938061e81f5ecab7725d70a54e3d6036e2fa22e8b2b0c771d32dfe01707b14c942ce6aa6291f0ab8fce4aee5296cf7b9fdcfdca7ec2b7262c219b020004c400000000000000070001000000000000000000000027100000000029209080000000000bebc200ab17ef69024716713fd8a2433cee1a80230b53d11dbe3802782578017df3a85f03e094d392a23dfd51e499b1d88405ca1234d80dca3e1fe4d6a257956c0ab29185ffe2b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589bdbf802f84854fd0aa4c57db7076190a29ec4a62283568e4d2364216906b6b804457b13b58b8a1106a2dab6496985e64a5829c019d2421d84477e903eab73482800023328bbd3c85d4f30b98fb9ef799f62aaeafca3318fa091fc294fb5ae39ea3a1e1b67960565fa4239d121713074804c5912122ea114e2974a0494592ab33e02c7ad9f3311006e314dc264ce0a8ba93f78d2633bdab60b8cf23208ecaa0aa84f2176291b501e3c0dd50afbc895c01b2a89959773b240ee2962fd8a3694771b369400000000000000080002000000000000000000ff00000000000000030000271000000000292090800000000006422c40ef2c4ef07d34051e1941965e9a420f75735208791058df88162f1e8db90680d0032de5e77d76d525909df1773cbfa29851a9b499bbbc697149060cb390a854fabb00000000000000000000070003003e0000fffffffffffc0081edb904be4493b4bf33014f8c55d7dfc0a0eb6ba1b5c74503731bdccff2fe825c00fc0003ffffffffffe801007810b0c8a83d397e50a0272e9f2457c003f8eb1d2c84b517e49de9df668df10802000007ffffffffffc80107b28bf2a478a86ac898e1edfefa85c5bc5b57100a38a5bc714fab6ddaa57b68f40003ffffffffffe40001000000000000000300031e47851bb56246b4b54c0ca9b7825dce0000061a80160014761879f7b274ce995f87150a02e75cc0c037e8e3000000000000ffd30200000001b848165af91647fd18dc3da2e49972e4c6a76d485b1eafdf4e1ddd393107589b000000000036a2ab80047070010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e31873010000000000220020ebb090e27885e133dcc4c2c50048a5ecad13ef3332340fb4d09933278f0cacfcb0ad0100000000002200202fa94c3bd8b31fb7dd6205f9db6b81066e81c46b8918ef7a02bed3438ab901f650870a0000000000220020bbe62879855ea85e05c2513fd39a76516d448f71269fe74dce02fd0988b3c5ada467892000000224d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef02000000ff2324d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef020000002bb0ad0100000000002200202fa94c3bd8b31fb7dd6205f9db6b81066e81c46b8918ef7a02bed3438ab901f68576a9140bb693d6b90f314c152b1d2c2ffc088f9542b3ba8763ac672102df93807a4ea0764a5fd9fab2bc573f5a8cee592293615a069c33ba1cc6f329467c820120876475527c2102dbd2b371b31656230bedfcafbea4df02b1f9f2d121c596bd984062b589aa27a452ae67a91423a2a6f93c2a944f662595191a7829411630ea9788ac6868fd014402000000000101d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef020000000000000000016297010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e30347304402201b4125bc11ce9f463f659e0058421e9f41e9d4d03e0c3776bc9d5521656382090220276fd4de9215b7f6a329c9af0f254b66181fd9cee38dd1750ae3f9dc29c338e10120e559091e6b69e409919a0956d1fe2e786262446548a920b203fa3a7ce51146ae8576a9140bb693d6b90f314c152b1d2c2ffc088f9542b3ba8763ac672102df93807a4ea0764a5fd9fab2bc573f5a8cee592293615a069c33ba1cc6f329467c820120876475527c2102dbd2b371b31656230bedfcafbea4df02b1f9f2d121c596bd984062b589aa27a452ae67a91423a2a6f93c2a944f662595191a7829411630ea9788ac6868000000006fdf03f953dc6040f0ba2f511c65f70e6d6a32d050db40f8400419fe70ffa0e5000000000000000000061ac024d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef01000000ff2224d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef010000002b1873010000000000220020ebb090e27885e133dcc4c2c50048a5ecad13ef3332340fb4d09933278f0cacfc8b76a9140bb693d6b90f314c152b1d2c2ffc088f9542b3ba8763ac672102df93807a4ea0764a5fd9fab2bc573f5a8cee592293615a069c33ba1cc6f329467c8201208763a91430fc0d20cea6c07069115f75bd755115dd04808388527c2102dbd2b371b31656230bedfcafbea4df02b1f9f2d121c596bd984062b589aa27a452ae677503a01a06b175ac6868fd012a02000000000101d08006b98d1e2f1688df581079085273750f429a5e9641191e05347df04e2cef01000000000000000001ce5d010000000000160014761879f7b274ce995f87150a02e75cc0c037e8e30347304402200109d51a3db516ba023039618a80c68e2791c0cee74f4af0f52bc0e695fa05200220792a459e57eee58efcf4732323e934332a269d4336a8fa182e488ee54d01ed2401008b76a9140bb693d6b90f314c152b1d2c2ffc088f9542b3ba8763ac672102df93807a4ea0764a5fd9fab2bc573f5a8cee592293615a069c33ba1cc6f329467c8201208763a91430fc0d20cea6c07069115f75bd755115dd04808388527c2102dbd2b371b31656230bedfcafbea4df02b1f9f2d121c596bd984062b589aa27a452ae677503a01a06b175ac6868a01a0600000000000000000300061aa000000000000000".bits).require.value.asInstanceOf[DATA_CLOSING] assert(closing3.nextRemoteCommitPublished.nonEmpty) val nrcp = closing3.nextRemoteCommitPublished.get - assert(nrcp.claimHtlcTxs.size == 2) - val claimHtlcSuccessTxs2 = nrcp.claimHtlcTxs.values.collect { case Some(tx: ClaimHtlcSuccessTx) => tx }.toSeq - assert(claimHtlcSuccessTxs2.size == 1) - assert(claimHtlcSuccessTxs2.head.htlcId == 0) - assert(claimHtlcSuccessTxs2.head.paymentHash == ByteVector32(hex"6fdf03f953dc6040f0ba2f511c65f70e6d6a32d050db40f8400419fe70ffa0e5")) - assert(claimHtlcSuccessTxs2.head.htlcExpiry == CltvExpiry(400064)) - val claimHtlcTimeoutTxs2 = nrcp.claimHtlcTxs.values.collect { case Some(tx: ClaimHtlcTimeoutTx) => tx }.toSeq - assert(claimHtlcTimeoutTxs2.size == 1) - assert(claimHtlcTimeoutTxs2.head.htlcId == 3) - assert(claimHtlcTimeoutTxs2.head.paymentHash == ByteVector32(hex"028fd04843d9cea2a6053932960308a3400dd79873bbdd9b7bf5325ad5eaa2e1")) - assert(claimHtlcTimeoutTxs2.head.htlcExpiry == CltvExpiry(400032)) + assert(nrcp.htlcs.size == 2) + assert(nrcp.htlcs.values.collect { case OutgoingHtlcId(htlcId) => htlcId }.toSeq == Seq(0)) + assert(nrcp.htlcs.values.collect { case IncomingHtlcId(htlcId) => htlcId }.toSeq == Seq(3)) } }