From 785d30ba8fd8e9c69b843b423a6cb6260b3b2655 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 16 Jun 2023 14:41:01 +0200 Subject: [PATCH] introduce confirmation target --- .../blockchain/fee/OnChainFeeConf.scala | 7 ++ .../fr/acinq/eclair/channel/Helpers.scala | 12 +- .../eclair/channel/fsm/ErrorHandlers.scala | 6 +- .../publish/ReplaceableTxPublisher.scala | 113 +++++++++++------- .../eclair/channel/publish/TxPublisher.scala | 27 +++-- .../acinq/eclair/json/JsonSerializers.scala | 16 ++- .../eclair/transactions/Transactions.scala | 38 +++--- .../channel/version0/ChannelCodecs0.scala | 11 +- .../channel/version0/ChannelTypes0.scala | 9 +- .../channel/version1/ChannelCodecs1.scala | 11 +- .../channel/version2/ChannelCodecs2.scala | 30 ++--- .../channel/version3/ChannelCodecs3.scala | 26 ++-- .../channel/version4/ChannelCodecs4.scala | 41 +++++-- .../publish/ReplaceableTxFunderSpec.scala | 12 +- .../publish/ReplaceableTxPublisherSpec.scala | 14 +-- .../channel/publish/TxPublisherSpec.scala | 25 ++-- .../channel/states/e/NormalStateSpec.scala | 18 +-- .../channel/states/h/ClosingStateSpec.scala | 26 ++-- .../eclair/json/JsonSerializersSpec.scala | 5 +- .../transactions/TransactionsSpec.scala | 16 +-- .../channel/version3/ChannelCodecs3Spec.scala | 40 +------ 21 files changed, 270 insertions(+), 233 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala index 6c03c8ccf6..d8b094c7fb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.blockchain.fee import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Satoshi +import fr.acinq.eclair.BlockHeight import fr.acinq.eclair.channel.{ChannelTypes, SupportedChannelType} import fr.acinq.eclair.transactions.Transactions @@ -30,12 +31,18 @@ sealed trait ConfirmationPriority { case ConfirmationPriority.Medium => feerates.blocks_12 case ConfirmationPriority.Fast => feerates.blocks_2 } +override def toString: String = super.toString.toLowerCase } object ConfirmationPriority { case object Slow extends ConfirmationPriority case object Medium extends ConfirmationPriority case object Fast extends ConfirmationPriority } +sealed trait ConfirmationTarget +object ConfirmationTarget { + case class Absolute(confirmBefore: BlockHeight) extends ConfirmationTarget + case class Priority(priority: ConfirmationPriority) extends ConfirmationTarget +} // @formatter:on case class FeeTargets(funding: ConfirmationPriority, closing: ConfirmationPriority) 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 ea498dfb5e..7b5308c596 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 @@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat._ import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, FeeratePerKw, OnChainFeeConf} +import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerKw, OnChainFeeConf} import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager @@ -726,7 +726,7 @@ object Helpers { * @param commitment our commitment data, which includes payment preimages * @return a list of transactions (one per output of the commit tx that we can claim) */ - def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, currentBlockHeight: BlockHeight, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): LocalCommitPublished = { + def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): LocalCommitPublished = { require(commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index.toInt) @@ -748,7 +748,7 @@ object Helpers { val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs val claimAnchorTxs: List[ClaimAnchorOutputTx] = if (spendAnchors) { // If we don't have pending HTLCs, we don't have funds at risk, so we can aim for a slower confirmation. - val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmBefore).minOption.getOrElse(currentBlockHeight + 1008) // TODO: setting a block target may not make sense when there is no urgency + val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmationTarget).minByOption(_.confirmBefore).getOrElse(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing)) List( withTxGenerationLog("local-anchor") { Transactions.makeClaimLocalAnchorOutputTx(tx, localFundingPubKey, confirmCommitBefore) @@ -852,15 +852,15 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per output of the commit tx that we can claim) */ - def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, tx: Transaction, currentBlockHeight: BlockHeight, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, tx: Transaction, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RemoteCommitPublished = { require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") val htlcTxs: Map[OutPoint, Option[ClaimHtlcTx]] = claimHtlcOutputs(keyManager, commitment, remoteCommit, onChainFeeConf, finalScriptPubKey) val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs val claimAnchorTxs: List[ClaimAnchorOutputTx] = if (spendAnchors) { - // If we don't have pending HTLCs, we don't have funds at risk, so we can aim for a slower confirmation. - val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmBefore).minOption.getOrElse(currentBlockHeight + 1008) // TODO: setting a block target may not make sense when there is no urgency + // If we don't have pending HTLCs, we don't have funds at risk, so we use the normal closing priority. + val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmationTarget).minByOption(_.confirmBefore).getOrElse(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing)) val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey List( withTxGenerationLog("local-anchor") { 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 289c16d3a1..b569543e64 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 @@ -197,7 +197,7 @@ trait ErrorHandlers extends CommonHandlers { val commitment = d.commitments.latest log.error(s"force-closing with fundingIndex=${commitment.fundingTxIndex}") val commitTx = commitment.fullySignedLocalCommitTx(keyManager).tx - val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey) + val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished)) case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, negotiating.closingTxProposed.flatten.map(_.unsignedTx), localCommitPublished = Some(localCommitPublished)) @@ -241,7 +241,7 @@ trait ErrorHandlers extends CommonHandlers { 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.isInitiator), "remote-commit")) - val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitments, commitments.remoteCommit, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey) + val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitments, commitments.remoteCommit, commitTx, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished)) case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = negotiating.closingTxProposed.flatten.map(_.unsignedTx), remoteCommitPublished = Some(remoteCommitPublished)) @@ -259,7 +259,7 @@ trait ErrorHandlers extends CommonHandlers { val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput, commitTx, d.commitments.params.localParams.isInitiator), "next-remote-commit")) - val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitment, remoteCommit, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey) + val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitment, remoteCommit, commitTx, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished)) case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = negotiating.closingTxProposed.flatten.map(_.unsignedTx), nextRemoteCommitPublished = Some(remoteCommitPublished)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala index 18e766ed66..a9e38386c9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala @@ -21,7 +21,7 @@ import akka.actor.typed.{ActorRef, Behavior} import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Transaction} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient -import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, OnChainFeeConf} +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, OnChainFeeConf} import fr.acinq.eclair.channel.publish.ReplaceableTxFunder.FundedTx import fr.acinq.eclair.channel.publish.ReplaceableTxPrePublisher.{ClaimLocalAnchorWithWitnessData, ReplaceableTxWithWitnessData} import fr.acinq.eclair.channel.publish.TxPublisher.TxPublishContext @@ -46,7 +46,7 @@ object ReplaceableTxPublisher { // @formatter:off sealed trait Command case class Publish(replyTo: ActorRef[TxPublisher.PublishTxResult], cmd: TxPublisher.PublishReplaceableTx) extends Command - case class UpdateConfirmationTarget(confirmBefore: BlockHeight) extends Command + case class UpdateConfirmationTarget(confirmationTarget: ConfirmationTarget) extends Command case object Stop extends Command private case class WrappedPreconditionsResult(result: ReplaceableTxPrePublisher.PreconditionsResult) extends Command @@ -75,27 +75,34 @@ object ReplaceableTxPublisher { } } - def getFeerate(onChainFeeConf: OnChainFeeConf, confirmBefore: BlockHeight, currentBlockHeight: BlockHeight, hasEnoughSafeUtxos: Boolean): FeeratePerKw = { - val remainingBlocks = confirmBefore - currentBlockHeight - if (hasEnoughSafeUtxos) { - remainingBlocks match { - // If our target is still very far in the future, no need to rush - case t if t >= 144 => onChainFeeConf.currentFeerates.blocks_144 - case t if t >= 72 => onChainFeeConf.currentFeerates.blocks_72 - case t if t >= 36 => onChainFeeConf.currentFeerates.blocks_36 - // However, if we get closer to the target, we start being more aggressive - case t if t >= 18 => onChainFeeConf.currentFeerates.blocks_12 - case t if t >= 12 => onChainFeeConf.currentFeerates.blocks_6 - case t if t >= 2 => onChainFeeConf.currentFeerates.blocks_2 - case _ => onChainFeeConf.currentFeerates.block_1 - } - } else { - // We don't have many safe utxos so we want the transaction to confirm quickly. - if (remainingBlocks <= 1) { - onChainFeeConf.currentFeerates.block_1 - } else { - onChainFeeConf.currentFeerates.blocks_2 - } + def getFeerate(onChainFeeConf: OnChainFeeConf, confirmationTarget: ConfirmationTarget, currentBlockHeight: BlockHeight, hasEnoughSafeUtxos: Boolean): FeeratePerKw = { + confirmationTarget match { + case ConfirmationTarget.Absolute(confirmBefore) => + // If we have an absolute block height target, we take into account what the current height is and adjust the feerate + val remainingBlocks = confirmBefore - currentBlockHeight + if (hasEnoughSafeUtxos) { + remainingBlocks match { + // If our target is still very far in the future, no need to rush + case t if t >= 144 => onChainFeeConf.currentFeerates.blocks_144 + case t if t >= 72 => onChainFeeConf.currentFeerates.blocks_72 + case t if t >= 36 => onChainFeeConf.currentFeerates.blocks_36 + // However, if we get closer to the target, we start being more aggressive + case t if t >= 18 => onChainFeeConf.currentFeerates.blocks_12 + case t if t >= 12 => onChainFeeConf.currentFeerates.blocks_6 + case t if t >= 2 => onChainFeeConf.currentFeerates.blocks_2 + case _ => onChainFeeConf.currentFeerates.block_1 + } + } else { + // We don't have many safe utxos so we want the transaction to confirm quickly. + if (remainingBlocks <= 1) { + onChainFeeConf.currentFeerates.block_1 + } else { + onChainFeeConf.currentFeerates.blocks_2 + } + } + case ConfirmationTarget.Priority(priority) => + // If we have a priority target, then the current block height doesn't matter + priority.getFeerate(onChainFeeConf.currentFeerates) } } @@ -114,7 +121,8 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, private val log = context.log - private var confirmBefore: BlockHeight = cmd.txInfo.confirmBefore + /** The confirmation target may be updated in some corner cases (e.g. for a htlc if we learn a payment preimage). */ + private var confirmationTarget: ConfirmationTarget = cmd.txInfo.confirmationTarget def checkPreconditions(): Behavior[Command] = { val prePublisher = context.spawn(ReplaceableTxPrePublisher(nodeParams, bitcoinClient, txPublishContext), "pre-publisher") @@ -126,7 +134,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, case ReplaceableTxPrePublisher.PreconditionsFailed(reason) => sendResult(TxPublisher.TxRejected(txPublishContext.id, cmd, reason), None) } case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => Behaviors.stopped } @@ -142,7 +150,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, Behaviors.receiveMessagePartial { case TimeLocksOk => chooseFeerate(txWithWitnessData) case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => Behaviors.stopped } @@ -156,10 +164,10 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, } Behaviors.receiveMessagePartial { case CheckUtxosResult(isSafe, currentBlockHeight) => - val targetFeerate = getFeerate(nodeParams.onChainFeeConf, confirmBefore, currentBlockHeight, isSafe) + val targetFeerate = getFeerate(nodeParams.onChainFeeConf, confirmationTarget, currentBlockHeight, isSafe) fund(txWithWitnessData, targetFeerate) case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => Behaviors.stopped } @@ -172,14 +180,17 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, case WrappedFundingResult(result) => result match { case ReplaceableTxFunder.TransactionReady(tx) => - log.debug("publishing {} with confirmation target in {} blocks", cmd.desc, confirmBefore - nodeParams.currentBlockHeight) + confirmationTarget match { + case ConfirmationTarget.Absolute(confirmBefore) => log.debug("publishing {} with confirmation target in {} blocks", cmd.desc, confirmBefore - nodeParams.currentBlockHeight) + case ConfirmationTarget.Priority(priority) => log.debug("publishing {} with priority {}", cmd.desc, priority) + } val txMonitor = context.spawn(MempoolTxMonitor(nodeParams, bitcoinClient, txPublishContext), s"mempool-tx-monitor-${tx.signedTx.txid}") txMonitor ! MempoolTxMonitor.Publish(context.messageAdapter[MempoolTxMonitor.TxResult](WrappedTxResult), tx.signedTx, cmd.input, cmd.desc, tx.fee) wait(tx) case ReplaceableTxFunder.FundingFailed(reason) => sendResult(TxPublisher.TxRejected(txPublishContext.id, cmd, reason), None) } case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => // We can't stop right now, the child actor is currently funding the transaction and will send its result soon. @@ -215,23 +226,37 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, case CheckUtxosResult(isSafe, currentBlockHeight) => // We make sure we increase the fees by at least 20% as we get closer to the confirmation target. val bumpRatio = 1.2 - val currentFeerate = getFeerate(nodeParams.onChainFeeConf, confirmBefore, currentBlockHeight, isSafe) - val targetFeerate_opt = if (confirmBefore <= currentBlockHeight + 6) { - log.debug("{} confirmation target is close (in {} blocks): bumping fees", cmd.desc, confirmBefore - currentBlockHeight) - Some(currentFeerate.max(tx.feerate * bumpRatio)) - } else if (tx.feerate * bumpRatio <= currentFeerate) { - log.debug("{} confirmation target is in {} blocks: bumping fees", cmd.desc, confirmBefore - currentBlockHeight) - Some(currentFeerate) - } else { - log.debug("{} confirmation target is in {} blocks: no need to bump fees", cmd.desc, confirmBefore - currentBlockHeight) - None + val currentFeerate = getFeerate(nodeParams.onChainFeeConf, confirmationTarget, currentBlockHeight, isSafe) + val targetFeerate_opt = confirmationTarget match { + case ConfirmationTarget.Absolute(confirmBefore) => + if (confirmBefore <= currentBlockHeight + 6) { + log.debug("{} confirmation target is close (in {} blocks): bumping fees", cmd.desc, confirmBefore - currentBlockHeight) + Some(currentFeerate.max(tx.feerate * bumpRatio)) + } else if (tx.feerate * bumpRatio <= currentFeerate) { + log.debug("{} confirmation target is in {} blocks: bumping fees", cmd.desc, confirmBefore - currentBlockHeight) + Some(currentFeerate) + } else { + log.debug("{} confirmation target is in {} blocks: no need to bump fees", cmd.desc, confirmBefore - currentBlockHeight) + None + } + case ConfirmationTarget.Priority(priority) => + if (tx.feerate * bumpRatio <= currentFeerate) { + log.debug("{} priority {}: bumping fees", cmd.desc, priority) + Some(currentFeerate) + } else { + log.debug("{} priority {}: no need to bump fees", cmd.desc, priority) + None + } } + + + // We avoid a herd effect whenever we fee bump transactions. targetFeerate_opt.foreach(targetFeerate => timers.startSingleTimer(BumpFeeKey, BumpFee(targetFeerate), (1 + Random.nextLong(nodeParams.channelConf.maxTxPublishRetryDelay.toMillis)).millis)) Behaviors.same case BumpFee(targetFeerate) => fundReplacement(targetFeerate, tx) case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => unlockAndStop(cmd.input, Seq(tx.signedTx)) } @@ -256,7 +281,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, timers.startSingleTimer(txResult, 1 second) Behaviors.same case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => // We can't stop right away, because the child actor may need to unlock utxos first. @@ -297,7 +322,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, Behaviors.same } case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => // We don't know yet which transaction won, so we try abandoning both and unlocking their utxos. @@ -329,7 +354,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, timers.startSingleTimer(txResult, 1 second) Behaviors.same case UpdateConfirmationTarget(target) => - confirmBefore = target + confirmationTarget = target Behaviors.same case Stop => // We don't stop right away, because we're cleaning up the failed transaction. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala index 962bfa6a93..2b3f3a58ad 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala @@ -24,6 +24,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Satoshi, Transactio import fr.acinq.eclair.blockchain.CurrentBlockHeight import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.FullCommitment import fr.acinq.eclair.transactions.Transactions.{ReplaceableTransactionWithInputInfo, TransactionWithInputInfo} import fr.acinq.eclair.{BlockHeight, Logs, NodeParams} @@ -156,7 +157,7 @@ object TxPublisher { def cmd: PublishTx } case class FinalAttempt(id: UUID, cmd: PublishFinalTx, actor: ActorRef[FinalTxPublisher.Command]) extends PublishAttempt - case class ReplaceableAttempt(id: UUID, cmd: PublishReplaceableTx, confirmBefore: BlockHeight, actor: ActorRef[ReplaceableTxPublisher.Command]) extends PublishAttempt + case class ReplaceableAttempt(id: UUID, cmd: PublishReplaceableTx, confirmationTarget: ConfirmationTarget, actor: ActorRef[ReplaceableTxPublisher.Command]) extends PublishAttempt // @formatter:on /** @@ -220,23 +221,33 @@ private class TxPublisher(nodeParams: NodeParams, factory: TxPublisher.ChildFact } case cmd: PublishReplaceableTx => - val proposedConfirmationTarget = cmd.txInfo.confirmBefore + val proposedConfirmationTarget = cmd.txInfo.confirmationTarget val attempts = pending.getOrElse(cmd.input, PublishAttempts.empty) attempts.replaceableAttempt_opt match { case Some(currentAttempt) => if (currentAttempt.cmd.txInfo.tx.txOut.headOption.map(_.publicKeyScript) != cmd.txInfo.tx.txOut.headOption.map(_.publicKeyScript)) { log.error("replaceable {} sends to a different address than the previous attempt, this should not happen: proposed={}, previous={}", currentAttempt.cmd.desc, cmd.txInfo, currentAttempt.cmd.txInfo) } - val currentConfirmationTarget = currentAttempt.confirmBefore - if (currentConfirmationTarget <= proposedConfirmationTarget) { - log.debug("not publishing replaceable {} spending {}:{} with confirmation target={}, publishing is already in progress with confirmation target={}", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget) - Behaviors.same - } else { + val currentConfirmationTarget = currentAttempt.confirmationTarget + + def updateConfirmationTarget() = { log.info("replaceable {} spending {}:{} has new confirmation target={} (previous={})", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget) currentAttempt.actor ! ReplaceableTxPublisher.UpdateConfirmationTarget(proposedConfirmationTarget) - val attempts2 = attempts.copy(replaceableAttempt_opt = Some(currentAttempt.copy(confirmBefore = proposedConfirmationTarget))) + val attempts2 = attempts.copy(replaceableAttempt_opt = Some(currentAttempt.copy(confirmationTarget = proposedConfirmationTarget))) run(pending + (cmd.input -> attempts2), retryNextBlock, channelContext) } + + (currentConfirmationTarget, proposedConfirmationTarget) match { + case (ConfirmationTarget.Absolute(currentConfirmBefore), ConfirmationTarget.Absolute(proposedConfirmBefore)) if proposedConfirmBefore < currentConfirmBefore => + // The proposed block target is closer than what it was + updateConfirmationTarget() + case (_: ConfirmationTarget.Priority, ConfirmationTarget.Absolute(proposedConfirmBefore)) => + // Switch from relative priority mode to absolute blockheight mode + updateConfirmationTarget() + case _ => + log.debug("not publishing replaceable {} spending {}:{} with confirmation target={}, publishing is already in progress with confirmation target={}", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget) + Behaviors.same + } case None => val publishId = UUID.randomUUID() log.info("publishing replaceable {} spending {}:{} with id={} ({} other attempts)", cmd.desc, cmd.input.txid, cmd.input.index, publishId, attempts.count) 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 6c865f5a00..675ba9cbd2 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 @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.balance.CheckBalance.{CorrectedOnChainBalance, GlobalBalance, OffChainBalance} -import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{ShaChain, Sphinx} import fr.acinq.eclair.db.FailureType.FailureType @@ -224,26 +224,26 @@ object TransactionWithInputInfoSerializer extends MinimalSerializer({ JField("tx", JString(x.tx.toString())), JField("paymentHash", JString(x.paymentHash.toString())), JField("htlcId", JLong(x.htlcId)), - JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong)) + JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong)) )) case x: HtlcTimeoutTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), JField("htlcId", JLong(x.htlcId)), - JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong)) + JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong)) )) case x: ClaimHtlcSuccessTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), JField("paymentHash", JString(x.paymentHash.toString())), JField("htlcId", JLong(x.htlcId)), - JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong)) + JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong)) )) case x: ClaimHtlcTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), JField("htlcId", JLong(x.htlcId)), - JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong)) + JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong)) )) case x: ClosingTx => val txFields = List( @@ -263,7 +263,11 @@ object TransactionWithInputInfoSerializer extends MinimalSerializer({ case x: ReplaceableTransactionWithInputInfo => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), - JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong)) + x.confirmationTarget match { + case ConfirmationTarget.Absolute(confirmBefore) => JField("confirmBeforeBlock", JLong(confirmBefore.toLong)) + case ConfirmationTarget.Priority(priority) => JField("confirmPriority", JString(priority.toString)) + } + )) case x: TransactionWithInputInfo => JObject(List( JField("txid", JString(x.tx.txid.toHex)), 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 ef7382c77d..764648ebd1 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 @@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, ripemd160} import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat._ import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.transactions.CommitmentOutput._ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc @@ -121,7 +121,7 @@ object Transactions { } sealed trait ReplaceableTransactionWithInputInfo extends TransactionWithInputInfo { /** Block before which the transaction must be confirmed. */ - def confirmBefore: BlockHeight + def confirmationTarget: ConfirmationTarget } case class SpliceTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "splice-tx" } @@ -148,16 +148,20 @@ object Transactions { case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY } } + override def confirmationTarget: ConfirmationTarget.Absolute } - case class HtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmBefore: BlockHeight) extends HtlcTx { override def desc: String = "htlc-success" } - case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends HtlcTx { override def desc: String = "htlc-timeout" } + case class HtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-success" } + case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-timeout" } case class HtlcDelayedTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed" } - sealed trait ClaimHtlcTx extends ReplaceableTransactionWithInputInfo { def htlcId: Long } - case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } - case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } - case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" } + sealed trait ClaimHtlcTx extends ReplaceableTransactionWithInputInfo { + def htlcId: Long + override def confirmationTarget: ConfirmationTarget.Absolute + } + case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } + case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } + case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" } sealed trait ClaimAnchorOutputTx extends TransactionWithInputInfo - case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmBefore: BlockHeight) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" } + case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" } case class ClaimRemoteAnchorOutputTx(input: InputInfo, tx: Transaction) extends ClaimAnchorOutputTx { override def desc: String = "remote-anchor" } sealed trait ClaimRemoteCommitMainOutputTx extends TransactionWithInputInfo case class ClaimP2WPKHOutputTx(input: InputInfo, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main" } @@ -476,7 +480,7 @@ object Transactions { txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, lockTime = htlc.cltvExpiry.toLong ) - Right(HtlcTimeoutTx(input, tx, htlc.id, BlockHeight(htlc.cltvExpiry.toLong))) + Right(HtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } } @@ -503,7 +507,7 @@ object Transactions { txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, lockTime = 0 ) - Right(HtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong))) + Right(HtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } } @@ -550,14 +554,14 @@ object Transactions { txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = 0) - val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)), PlaceHolderSig, ByteVector32.Zeroes).tx.weight() + val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong))) + Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } case None => Left(OutputNotFound) } @@ -585,14 +589,14 @@ object Transactions { txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = htlc.cltvExpiry.toLong) - val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)), PlaceHolderSig).tx.weight() + val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, BlockHeight(htlc.cltvExpiry.toLong))) + Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } case None => Left(OutputNotFound) } @@ -705,8 +709,8 @@ object Transactions { } } - def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmBefore: BlockHeight): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = { - makeClaimAnchorOutputTx(commitTx, localFundingPubkey).map { case (input, tx) => ClaimLocalAnchorOutputTx(input, tx, confirmBefore) } + def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmationTarget: ConfirmationTarget): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = { + makeClaimAnchorOutputTx(commitTx, localFundingPubkey).map { case (input, tx) => ClaimLocalAnchorOutputTx(input, tx, confirmationTarget) } } def makeClaimRemoteAnchorOutputTx(commitTx: Transaction, remoteFundingPubkey: PublicKey): Either[TxGenerationSkipped, ClaimRemoteAnchorOutputTx] = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index b0282c4fe2..628f8e2843 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version0 import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain @@ -126,6 +127,8 @@ private[channel] object ChannelCodecs0 { ("txOut" | txOutCodec) :: ("redeemScript" | varsizebinarydata)).as[InputInfo].decodeOnly + private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) + // We can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a // downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing. // We can also safely set confirmBefore = 0: we will simply use a high feerate to make these transactions confirm @@ -133,10 +136,10 @@ private[channel] object ChannelCodecs0 { // complexity and real world impact. val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) - .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx]) - .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx]) - .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx]) - .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx]) + .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]) + .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) + .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) + .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]) 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 119e25e469..bf8d2e0272 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 @@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version0 import com.softwaremill.quicklens._ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxOut} +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec @@ -58,8 +59,8 @@ private[channel] object ChannelTypes0 { // 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)) - val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, BlockHeight(0))) - val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0))) + val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(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) @@ -78,8 +79,8 @@ private[channel] object ChannelTypes0 { // 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 => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0))) - val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0))) + val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) val claimHtlcTxsNew = (claimHtlcSuccessTxsNew ++ claimHtlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap channel.RemoteCommitPublished(commitTx, claimMainOutputTxNew, claimHtlcTxsNew, Nil, irrevocablySpentNew) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index 0cc0a62f21..d765c9b082 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version1 import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain @@ -101,14 +102,16 @@ private[channel] object ChannelCodecs1 { ("txOut" | txOutCodec) :: ("redeemScript" | lengthDelimited(bytes))).as[InputInfo] + private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) + // NB: we can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a // downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing. val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) - .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx]) - .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx]) - .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx]) - .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx]) + .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTargetBefore" | defaultConfirmationTarget)).as[HtlcSuccessTx]) + .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) + .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) + .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]) 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 5a8191fc03..ccfb9b757f 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 @@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version2 import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction, TxOut} +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain @@ -110,39 +111,24 @@ private[channel] object ChannelCodecs2 { ("amount" | satoshi) :: ("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo] + private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx] + val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget])).as[ClaimLocalAnchorOutputTx] val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] - val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) - .typecase(0x01, commitTxCodec) - .typecase(0x02, htlcSuccessTxCodec) - .typecase(0x03, htlcTimeoutTxCodec) - .typecase(0x04, claimHtlcSuccessTxCodec) - .typecase(0x05, claimHtlcTimeoutTxCodec) - .typecase(0x06, claimP2WPKHOutputTxCodec) - .typecase(0x07, claimLocalDelayedOutputTxCodec) - .typecase(0x08, mainPenaltyTxCodec) - .typecase(0x09, htlcPenaltyTxCodec) - .typecase(0x10, closingTxCodec) - .typecase(0x11, claimLocalAnchorOutputTxCodec) - .typecase(0x12, claimRemoteAnchorOutputTxCodec) - .typecase(0x13, claimRemoteDelayedOutputTxCodec) - .typecase(0x14, claimHtlcDelayedOutputPenaltyTxCodec) - .typecase(0x15, htlcDelayedTxCodec) - val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8) .typecase(0x01, claimP2WPKHOutputTxCodec) .typecase(0x02, claimRemoteDelayedOutputTxCodec) 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 9cbed2909f..caaef720c3 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 @@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version3 import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt} import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{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._ @@ -122,25 +123,28 @@ private[channel] object ChannelCodecs3 { ("amount" | satoshi) :: ("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo] + private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) + private val blockHeightConfirmationTarget: Codec[ConfirmationTarget.Absolute] = blockHeight.map(ConfirmationTarget.Absolute).decodeOnly + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcTimeoutTx] - private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx] - private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] + private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx] - val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcTimeoutTx] - private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcSuccessTx] - private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx] + private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] + private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] + private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | blockHeight)).as[ClaimLocalAnchorOutputTx] - private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] 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 9c416275f4..21f3664936 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -3,6 +3,8 @@ package fr.acinq.eclair.wire.internal.channel.version4 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut} +import fr.acinq.eclair.blockchain.fee +import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget} import fr.acinq.eclair.channel.LocalFundingStatus._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit @@ -116,26 +118,38 @@ private[channel] object ChannelCodecs4 { ("amount" | satoshi) :: ("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo] + private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) + private val blockHeightConfirmationTarget: Codec[ConfirmationTarget.Absolute] = blockHeight.xmap(ConfirmationTarget.Absolute, _.confirmBefore) + private val confirmationPriority: Codec[ConfirmationPriority] = discriminated[ConfirmationPriority].by(uint8) + .typecase(0x01, provide(ConfirmationPriority.Slow)) + .typecase(0x02, provide(ConfirmationPriority.Medium)) + .typecase(0x03, provide(ConfirmationPriority.Fast)) + private val priorityConfirmationTarget: Codec[ConfirmationTarget.Priority] = confirmationPriority.xmap(ConfirmationTarget.Priority, _.priority) + private val confirmationTarget: Codec[ConfirmationTarget] = discriminated[ConfirmationTarget].by(uint8) + .typecase(0x00, blockHeightConfirmationTarget) + .typecase(0x01, priorityConfirmationTarget) + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcTimeoutTx] - private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx] - private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] + private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx] - val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcTimeoutTx] - private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcSuccessTx] - private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx] + private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] + private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] + private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | blockHeight)).as[ClaimLocalAnchorOutputTx] - private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx] - val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | confirmationTarget)).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxBlockHeightConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8) @@ -144,7 +158,8 @@ private[channel] object ChannelCodecs4 { val claimAnchorOutputTxCodec: Codec[ClaimAnchorOutputTx] = discriminated[ClaimAnchorOutputTx].by(uint8) // Important: order matters! - .typecase(0x11, claimLocalAnchorOutputTxCodec) + .typecase(0x12, claimLocalAnchorOutputTxCodec) + .typecase(0x11, claimLocalAnchorOutputTxBlockHeightConfirmCodec) .typecase(0x01, claimLocalAnchorOutputTxNoConfirmCodec) .typecase(0x02, claimRemoteAnchorOutputTxCodec) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala index c9a7d69e59..e65a69e154 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.channel.publish import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxIn, TxOut} -import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel.publish.ReplaceableTxFunder.AdjustPreviousTxOutputResult.{AddWalletInputs, TxOutputAdjusted} import fr.acinq.eclair.channel.publish.ReplaceableTxFunder._ @@ -48,7 +48,7 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { val anchorTx = ClaimLocalAnchorOutputTx( InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head, anchorScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Nil, 0), - BlockHeight(0) + ConfirmationTarget.Absolute(BlockHeight(0)) ) (CommitTx(commitInput, commitTx), anchorTx) } @@ -69,13 +69,13 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), paymentHash, 17, - BlockHeight(0) + ConfirmationTarget.Absolute(BlockHeight(0)) ), PlaceHolderSig, preimage) val htlcTimeout = HtlcTimeoutWithWitnessData(HtlcTimeoutTx( InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last, htlcTimeoutScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 1), ByteVector.empty, 0)), Seq(TxOut(4000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), 12, - BlockHeight(0) + ConfirmationTarget.Absolute(BlockHeight(0)) ), PlaceHolderSig) (htlcSuccess, htlcTimeout) } @@ -90,13 +90,13 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { Transaction(2, Seq(TxIn(OutPoint(ByteVector32.Zeroes, 3), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), paymentHash, 5, - BlockHeight(0) + ConfirmationTarget.Absolute(BlockHeight(0)) ), preimage) val claimHtlcTimeout = ClaimHtlcTimeoutWithWitnessData(ClaimHtlcTimeoutTx( InputInfo(OutPoint(ByteVector32.Zeroes, 7), TxOut(5000 sat, Script.pay2wsh(htlcTimeoutScript)), htlcTimeoutScript), Transaction(2, Seq(TxIn(OutPoint(ByteVector32.Zeroes, 7), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), 7, - BlockHeight(0) + ConfirmationTarget.Absolute(BlockHeight(0)) )) (claimHtlcSuccess, claimHtlcTimeout) } 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 04455a5a25..86a65f4e67 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 @@ -28,7 +28,7 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoindService import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.MempoolTx import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, BitcoinJsonRPCClient} -import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw} +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw} import fr.acinq.eclair.blockchain.{CurrentBlockHeight, OnchainPubkeyCache} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel @@ -182,7 +182,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val publishAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx] assert(publishAnchor.txInfo.input.outPoint.txid == commitTx.tx.txid) assert(publishAnchor.txInfo.isInstanceOf[ClaimLocalAnchorOutputTx]) - val anchorTx = publishAnchor.txInfo.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmBefore = overrideCommitTarget) + val anchorTx = publishAnchor.txInfo.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideCommitTarget)) (publishCommitTx, publishAnchor.copy(txInfo = anchorTx)) } @@ -679,7 +679,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // The confirmation target has changed (probably because we learnt a payment preimage). // We should now use the high feerate, which corresponds to that new target. - publisher ! UpdateConfirmationTarget(aliceBlockHeight() + 15) + publisher ! UpdateConfirmationTarget(ConfirmationTarget.Absolute(aliceBlockHeight() + 15)) system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight())) val anchorTxId2 = listener.expectMsgType[TransactionPublished].tx.txid awaitAssert(assert(!isInMempool(mempoolAnchorTx1.txid)), interval = 200 millis, max = 30 seconds) @@ -870,10 +870,10 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w alice2blockchain.expectMsgType[PublishFinalTx] // claim main output val htlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx] assert(htlcSuccess.txInfo.isInstanceOf[HtlcSuccessTx]) - val htlcSuccessTx = htlcSuccess.txInfo.asInstanceOf[HtlcSuccessTx].copy(confirmBefore = overrideHtlcTarget) + val htlcSuccessTx = htlcSuccess.txInfo.asInstanceOf[HtlcSuccessTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) val htlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx] assert(htlcTimeout.txInfo.isInstanceOf[HtlcTimeoutTx]) - val htlcTimeoutTx = htlcTimeout.txInfo.asInstanceOf[HtlcTimeoutTx].copy(confirmBefore = overrideHtlcTarget) + val htlcTimeoutTx = htlcTimeout.txInfo.asInstanceOf[HtlcTimeoutTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output @@ -1375,10 +1375,10 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w alice2blockchain.expectMsgType[PublishFinalTx] // claim main output val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx] assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx]) - val claimHtlcSuccessTx = claimHtlcSuccess.txInfo.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmBefore = overrideHtlcTarget) + val claimHtlcSuccessTx = claimHtlcSuccess.txInfo.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) val claimHtlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx] assert(claimHtlcTimeout.txInfo.isInstanceOf[ClaimHtlcTimeoutTx]) - val claimHtlcTimeoutTx = claimHtlcTimeout.txInfo.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmBefore = overrideHtlcTarget) + val claimHtlcTimeoutTx = claimHtlcTimeout.txInfo.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala index 5ad93f2b3f..bd753a2628 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala @@ -22,6 +22,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, TypedActorRefOp import akka.testkit.TestProbe import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Transaction, TxIn, TxOut} import fr.acinq.eclair.blockchain.CurrentBlockHeight +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel.publish import fr.acinq.eclair.channel.publish.TxPublisher.TxRejectedReason._ import fr.acinq.eclair.channel.publish.TxPublisher._ @@ -101,7 +102,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { test("publish replaceable tx") { f => import f._ - val confirmBefore = nodeParams.currentBlockHeight + 12 + val confirmBefore = ConfirmationTarget.Absolute(nodeParams.currentBlockHeight + 12) val input = OutPoint(randomBytes32(), 3) val cmd = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore), null) txPublisher ! cmd @@ -115,7 +116,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomBytes32(), 3) - val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore) + val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ConfirmationTarget.Absolute(confirmBefore)) val cmd = PublishReplaceableTx(anchorTx, null) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -125,19 +126,19 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { txPublisher ! PublishReplaceableTx(anchorTx, null) child.expectNoMessage(100 millis) factory.expectNoMessage(100 millis) - val cmdHigherTarget = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore + 1)) + val cmdHigherTarget = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore + 1))) txPublisher ! cmdHigherTarget child.expectNoMessage(100 millis) factory.expectNoMessage(100 millis) // But we update the confirmation target when it is more aggressive than previous attempts: - val cmdLowerTarget = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore - 6)) + val cmdLowerTarget = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore - 6))) txPublisher ! cmdLowerTarget - child.expectMsg(ReplaceableTxPublisher.UpdateConfirmationTarget(confirmBefore - 6)) + child.expectMsg(ReplaceableTxPublisher.UpdateConfirmationTarget(ConfirmationTarget.Absolute(confirmBefore - 6))) factory.expectNoMessage(100 millis) // And we update our internal threshold accordingly: - val cmdInBetween = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore - 3)) + val cmdInBetween = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore - 3))) txPublisher ! cmdInBetween child.expectNoMessage(100 millis) factory.expectNoMessage(100 millis) @@ -159,7 +160,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned].actor attempt2.expectMsgType[FinalTxPublisher.Publish] - val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), nodeParams.currentBlockHeight), null) + val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null) txPublisher ! cmd3 val attempt3 = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor attempt3.expectMsgType[ReplaceableTxPublisher.Publish] @@ -181,7 +182,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt1 = factory.expectMsgType[FinalTxPublisherSpawned] attempt1.actor.expectMsgType[FinalTxPublisher.Publish] - val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), nodeParams.currentBlockHeight), null) + val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null) txPublisher ! cmd2 val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt2.actor.expectMsgType[ReplaceableTxPublisher.Publish] @@ -221,7 +222,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val target = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomBytes32(), 7) val paymentHash = randomBytes32() - val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, target), null) + val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(target)), null) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish] @@ -285,7 +286,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomBytes32(), 7) val paymentHash = randomBytes32() - val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, nodeParams.currentBlockHeight), null) + val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish] @@ -356,7 +357,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { } { // Only replaceable attempts. - val attempt = ReplaceableAttempt(UUID.randomUUID(), null, BlockHeight(0), null) + val attempt = ReplaceableAttempt(UUID.randomUUID(), null, ConfirmationTarget.Absolute(BlockHeight(0)), null) val attempts = PublishAttempts(Nil, Some(attempt)) assert(!attempts.isEmpty) assert(attempts.count == 1) @@ -365,7 +366,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { } { // Mix of final and replaceable attempts with the same id. - val attempt1 = ReplaceableAttempt(UUID.randomUUID(), null, BlockHeight(0), null) + val attempt1 = ReplaceableAttempt(UUID.randomUUID(), null, ConfirmationTarget.Absolute(BlockHeight(0)), null) val attempt2 = FinalAttempt(attempt1.id, null, null) val attempt3 = FinalAttempt(UUID.randomUUID(), null, null) val attempts = PublishAttempts(Seq(attempt2), Some(attempt1)).add(attempt3) 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 7c69577518..fa48800a2f 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 @@ -26,7 +26,7 @@ import fr.acinq.eclair.Features.StaticRemoteKey import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ -import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw} +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte, FeeratePerKw, FeeratesPerKw} import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates} import fr.acinq.eclair.channel.RealScidStatus.Final import fr.acinq.eclair.channel._ @@ -3021,8 +3021,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(amountClaimed == 814880.sat) // alice sets the confirmation targets to the HTLC expiry - assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcSuccessTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlcb1.id -> htlcb1.cltvExpiry.toLong)) - assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong)) + assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcSuccessTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlcb1.id -> htlcb1.cltvExpiry.blockHeight)) + assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight)) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid) @@ -3112,7 +3112,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(amountClaimed == 822310.sat) // alice sets the confirmation targets to the HTLC expiry - assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong)) + assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry, htlca2.id -> htlca2.cltvExpiry)) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid) // claim-main @@ -3387,15 +3387,15 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) val localAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx] - assert(localAnchor.txInfo.confirmBefore.toLong == htlca1.cltvExpiry.toLong) // the target is set to match the first htlc that expires + assert(localAnchor.txInfo.confirmationTarget == ConfirmationTarget.Absolute(htlca1.cltvExpiry.blockHeight)) // the target is set to match the first htlc that expires val claimMain = alice2blockchain.expectMsgType[PublishFinalTx] // 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 htlcConfirmationTargets = Seq( alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 1 alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 2 alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 3 - ).map(p => p.txInfo.asInstanceOf[HtlcTx].htlcId -> p.txInfo.confirmBefore.toLong).toMap - assert(htlcConfirmationTargets == Map(htlcb1.id -> htlcb1.cltvExpiry.toLong, htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong)) + ).map(p => p.txInfo.asInstanceOf[HtlcTx].htlcId -> p.txInfo.asInstanceOf[HtlcTx].confirmationTarget.confirmBefore).toMap + assert(htlcConfirmationTargets == Map(htlcb1.id -> htlcb1.cltvExpiry.blockHeight, htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight)) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx.txid) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.tx.txid) @@ -3438,8 +3438,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectNoMessage(1 second) } else { val localAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx] - // When there are no pending HTLCs, there is no rush to get the commit tx confirmed - assert(localAnchor.txInfo.confirmBefore === currentBlockHeight + 1008) // TODO + // When there are no pending HTLCs, there is no absolute deadline to get the commit tx confirmed, we use priority + assert(localAnchor.txInfo.confirmationTarget.isInstanceOf[ConfirmationTarget.Priority]) val claimMain = alice2blockchain.expectMsgType[PublishFinalTx] assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === aliceCommitTx.txid) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === claimMain.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 cc7c36fb33..43f7b9e7fa 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 @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxIn, TxOut} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ -import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw} +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITCOIN_FUNDING_TIMEOUT} @@ -814,9 +814,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx) val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get).head.tx Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx] - assert(publishHtlcSuccessTx.txInfo.tx == claimHtlcSuccessTx) - assert(publishHtlcSuccessTx.txInfo.confirmBefore.toLong == htlc1.cltvExpiry.toLong) + val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx] + assert(publishHtlcSuccessTx.tx == claimHtlcSuccessTx) + assert(publishHtlcSuccessTx.confirmationTarget == ConfirmationTarget.Absolute(htlc1.cltvExpiry.blockHeight)) // Alice resets watches on all relevant transactions. assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid) @@ -857,9 +857,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we should re-publish unconfirmed transactions closingState.claimMainOutputTx.foreach(claimMain => assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == claimMain.tx)) - val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx] - assert(publishHtlcTimeoutTx.txInfo.tx == htlcTimeoutTx.tx) - assert(publishHtlcTimeoutTx.txInfo.confirmBefore.toLong == htlca.cltvExpiry.toLong) + val publishClaimHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcTimeoutTx] + assert(publishClaimHtlcTimeoutTx.tx == htlcTimeoutTx.tx) + assert(publishClaimHtlcTimeoutTx.confirmationTarget == ConfirmationTarget.Absolute(htlca.cltvExpiry.blockHeight)) closingState.claimMainOutputTx.foreach(claimMain => assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.tx.txid)) assert(alice2blockchain.expectMsgType[WatchOutputSpent].outputIndex == htlcTimeoutTx.input.outPoint.index) } @@ -989,12 +989,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx) val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get).head.tx Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx] - assert(publishHtlcSuccessTx.txInfo.tx == claimHtlcSuccessTx) - assert(publishHtlcSuccessTx.txInfo.confirmBefore.toLong == htlc1.cltvExpiry.toLong) - val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx] - assert(publishHtlcTimeoutTx.txInfo.tx == claimHtlcTimeoutTx) - assert(publishHtlcTimeoutTx.txInfo.confirmBefore.toLong == htlc2.cltvExpiry.toLong) + val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx] + assert(publishHtlcSuccessTx.tx == claimHtlcSuccessTx) + assert(publishHtlcSuccessTx.confirmationTarget == ConfirmationTarget.Absolute(htlc1.cltvExpiry.blockHeight)) + val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcTimeoutTx] + assert(publishHtlcTimeoutTx.tx == claimHtlcTimeoutTx) + assert(publishHtlcTimeoutTx.confirmationTarget == ConfirmationTarget.Absolute(htlc2.cltvExpiry.blockHeight)) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid) assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == closingState.claimMainOutputTx.get.tx.txid) 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 293a1a2673..cbb0f005ad 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 @@ -22,6 +22,7 @@ import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, OutPoint, Satoshi, Satos import fr.acinq.eclair._ import fr.acinq.eclair.balance.CheckBalance import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, GlobalBalance, MainAndHtlcBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance} +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.io.Peer.PeerInfo @@ -228,7 +229,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { val dummyInputInfo = InputInfo(OutPoint(ByteVector32.Zeroes, 0), TxOut(Satoshi(0), Nil), Nil) val htlcSuccessTx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800") - val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, BlockHeight(1105)) + val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, ConfirmationTarget.Absolute(BlockHeight(1105))) val htlcSuccessJson = s"""{ | "txid": "${htlcSuccessTx.txid.toHex}", @@ -241,7 +242,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { assertJsonEquals(JsonSerializers.serialization.write(htlcSuccessTxInfo)(JsonSerializers.formats), htlcSuccessJson) val claimHtlcTimeoutTx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000") - val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, claimHtlcTimeoutTx, 2, BlockHeight(144)) + val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, claimHtlcTimeoutTx, 2, ConfirmationTarget.Absolute(BlockHeight(144))) val claimHtlcTimeoutJson = s"""{ | "txid": "${claimHtlcTimeoutTx.txid.toHex}", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 3d77c73bd6..3dfcae02ea 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, ripemd160, sha256} import fr.acinq.bitcoin.scalacompat.Script.{pay2wpkh, pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, Crypto, MilliBtc, MilliBtcDouble, OutPoint, Protocol, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, millibtc2satoshi} import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.fee.FeeratePerKw +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw} import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.transactions.CommitmentOutput.{InHtlc, OutHtlc} import fr.acinq.eclair.transactions.Scripts.{anchor, htlcOffered, htlcReceived, toLocalDelayed} @@ -198,9 +198,9 @@ class TransactionsSpec extends AnyFunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimAnchorOutputTx val pubKeyScript = write(pay2wsh(anchor(localFundingPriv.publicKey))) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(anchorAmount, pubKeyScript) :: Nil, lockTime = 0) - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, BlockHeight(1105)) + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(1105))) assert(claimAnchorOutputTx.tx.txOut.isEmpty) - assert(claimAnchorOutputTx.confirmBefore == BlockHeight(1105)) + assert(claimAnchorOutputTx.confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(1105))) // we will always add at least one input and one output to be able to set our desired feerate // we use dummy signatures to compute the weight val p2wpkhWitness = ScriptWitness(Seq(Scripts.der(PlaceHolderSig), PlaceHolderPubKey.value)) @@ -304,7 +304,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val htlcTxs = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(DefaultCommitmentFormat), outputs, DefaultCommitmentFormat) assert(htlcTxs.length == 4) - val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap + val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap assert(confirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 295, 3 -> 300)) val htlcSuccessTxs = htlcTxs.collect { case tx: HtlcSuccessTx => tx } val htlcTimeoutTxs = htlcTxs.collect { case tx: HtlcTimeoutTx => tx } @@ -532,7 +532,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val htlcTxs = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(UnsafeLegacyAnchorOutputsCommitmentFormat), outputs, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(htlcTxs.length == 5) - val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap + val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap assert(confirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 310, 3 -> 295, 4 -> 300)) val htlcSuccessTxs = htlcTxs.collect { case tx: HtlcSuccessTx => tx } val htlcTimeoutTxs = htlcTxs.collect { case tx: HtlcTimeoutTx => tx } @@ -545,7 +545,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val zeroFeeCommitTx = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsInitiator = true, zeroFeeOutputs) val zeroFeeHtlcTxs = makeHtlcTxs(zeroFeeCommitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat), zeroFeeOutputs, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) assert(zeroFeeHtlcTxs.length == 7) - val zeroFeeConfirmationTargets = zeroFeeHtlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap + val zeroFeeConfirmationTargets = zeroFeeHtlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap assert(zeroFeeConfirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 310, 3 -> 295, 4 -> 300, 7 -> 300, 8 -> 302)) val zeroFeeHtlcSuccessTxs = zeroFeeHtlcTxs.collect { case tx: HtlcSuccessTx => tx } val zeroFeeHtlcTimeoutTxs = zeroFeeHtlcTxs.collect { case tx: HtlcTimeoutTx => tx } @@ -580,7 +580,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // local spends local anchor - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, BlockHeight(0)) + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))) assert(checkSpendable(claimAnchorOutputTx).isFailure) val localSig = sign(claimAnchorOutputTx, localFundingPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat) val signedTx = addSigs(claimAnchorOutputTx, localSig) @@ -588,7 +588,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends remote anchor - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, BlockHeight(0)) + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))) assert(checkSpendable(claimAnchorOutputTx).isFailure) val localSig = sign(claimAnchorOutputTx, remoteFundingPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat) val signedTx = addSigs(claimAnchorOutputTx, localSig) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index 616f4630c6..49b880a7b3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -15,7 +15,7 @@ */ package fr.acinq.eclair.wire.internal.channel.version3 -import fr.acinq.bitcoin.scalacompat.ByteVector32 +import fr.acinq.eclair.blockchain.fee.ConfirmationTarget import fr.acinq.eclair.channel._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs._ @@ -75,19 +75,6 @@ class ChannelCodecs3Spec extends AnyFunSuite { assert(claimHtlcSuccessTx1.isInstanceOf[LegacyClaimHtlcSuccessTx]) claimHtlcSuccessTx1.asInstanceOf[LegacyClaimHtlcSuccessTx] } - - // We can encode data that contains a payment hash. - val claimHtlcSuccess = ClaimHtlcSuccessTx(legacyClaimHtlcSuccess.input, legacyClaimHtlcSuccess.tx, ByteVector32(hex"0101010101010101010101010101010101010101010101010101010101010101"), legacyClaimHtlcSuccess.htlcId, BlockHeight(0)) - val txWithInputInfoCodecBin = txWithInputInfoCodec.encode(claimHtlcSuccess).require.bytes - assert(txWithInputInfoCodecBin !== oldTxWithInputInfoCodecBin) - val claimHtlcTxBin = claimHtlcTxCodec.encode(claimHtlcSuccess).require.bytes - assert(claimHtlcTxBin !== oldClaimHtlcTxBin) - - // And decode new data that contains a payment hash. - val decoded1 = txWithInputInfoCodec.decode(txWithInputInfoCodecBin.bits).require.value - assert(claimHtlcSuccess == decoded1) - val decoded2 = claimHtlcTxCodec.decode(claimHtlcTxBin.bits).require.value - assert(claimHtlcSuccess == decoded2) } test("backwards compatibility with transactions missing a confirmation target") { @@ -95,46 +82,31 @@ class ChannelCodecs3Spec extends AnyFunSuite { val oldAnchorTxBin = hex"0011 24bd0be30e31c748c7afdde7d2c527d711fadf88500971a4a1136bca375dba07b8000000002b4a0100000000000022002036c067df8952dbcd5db347e7c152ca3fa4514f2072d27867837b1c2d319a7e01282103cc89f1459b5201cda08e08c6fb7b1968c54e8172c555896da27c6fdc10522ceeac736460b268330200000001bd0be30e31c748c7afdde7d2c527d711fadf88500971a4a1136bca375dba07b80000000000000000000000000000" val oldAnchorTx = txWithInputInfoCodec.decode(oldAnchorTxBin.bits).require.value assert(oldAnchorTx.isInstanceOf[ClaimLocalAnchorOutputTx]) - assert(oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].confirmBefore == BlockHeight(0)) - val anchorTx = oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmBefore = BlockHeight(1105)) - val anchorTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(anchorTx).require).require.value - assert(anchorTx == anchorTx2) + assert(oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0))) } { val oldHtlcSuccessTxBin = hex"0002 24f5580de0577271dce09d2de26e19ec58bf2373b0171473291a8f8be9b04fb289000000002bb0ad010000000000220020462cf8912ffc5f27764c109bed188950500011a2837ff8b9c8f9a39cffa395a58b76a91406b0950d9feded82239b3e6c9082308900f389de8763ac672102d65aa07658a7214ff129f91a1a22ade2ea4d1b07cc14b2f85a2842c34240836f7c8201208763a914c461c897e2165c7e44e14850dfcfd68f99127aed88527c21033c8d41cfbe1511a909b63fed68e75a29c3ce30418c39bbb8294c8b36c6a6c16a52ae677503101b06b175ac6868fd01a002000000000101f5580de0577271dce09d2de26e19ec58bf2373b0171473291a8f8be9b04fb289000000000000000000013a920100000000002200208742b16c9fd4e74854dcd84322dd1de06f7993fe627fd2ca0be4b996a936d56b050047304402201b4527c8f420852550af00bbd9149db9b31adcb7e1f127766e75e1e01746df0302202a57bb1e274ed7d3e8dbe5f205de721a23092c1e2ce2135f4750f18f6c0b51b001483045022100b6df309c8e5746a077b1f7c2f299528e164946bd514a5049475af7f5665805da0220392ae877112a3c52f74d190b354b4f5c020da9c1a71a7a08ced0a5363e795a27012017ea8f5afde8f708258d5669e1bbd454e82ddca8c6c480ec5302b4b1e8051d3d8b76a91406b0950d9feded82239b3e6c9082308900f389de8763ac672102d65aa07658a7214ff129f91a1a22ade2ea4d1b07cc14b2f85a2842c34240836f7c8201208763a914c461c897e2165c7e44e14850dfcfd68f99127aed88527c21033c8d41cfbe1511a909b63fed68e75a29c3ce30418c39bbb8294c8b36c6a6c16a52ae677503101b06b175ac686800000000dc7002a387673f17ebaf08545ccec712a9b6914813cdb83b4270932294f20f660000000000000000" val oldHtlcSuccessTx = txWithInputInfoCodec.decode(oldHtlcSuccessTxBin.bits).require.value assert(oldHtlcSuccessTx.isInstanceOf[HtlcSuccessTx]) - assert(oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].confirmBefore == BlockHeight(0)) - val htlcSuccessTx = oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].copy(confirmBefore = BlockHeight(1105)) - val htlcSuccessTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(htlcSuccessTx).require).require.value - assert(htlcSuccessTx == htlcSuccessTx2) + assert(oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0))) } { val oldHtlcTimeoutTxBin = hex"0003 248f0619a4b2a351977b3e5b0ddd700482e1d697d40deea2dd7356df99345d51d0000000002b50c300000000000022002005ff644937d7f5f32ec194424f551371e8d4bcf2cda3e1096cdd2fe88687fc408576a914c98707b6420ef3454f3bd10d663adcc04452baea8763ac672102fb20469287c8ade948011bd001440d74633d5e1a98574e4783dd38b76509d8f67c820120876475527c210279d3a1c2086a0968404a0160c8f8c6f88c0ce7184022bb7406f98fdb503ea51452ae67a9140550b2b1621e788d795fe3ae308e7dec06a6a1e088ac6868fd0179020000000001018f0619a4b2a351977b3e5b0ddd700482e1d697d40deea2dd7356df99345d51d0000000000000000000016aa9000000000000220020c980a1573ce6dc6a1bb8a1d60ecffdf2f0a7aa983c49786a2ab1ba8f0cd74a76050047304402200d2b631fb0e5a7f406f3de2b36fe3c0ab3337fe98f114dadf38de8f5548e87eb02203ad7c385c7cf62ac17ec15329dee071ee2c479aceb34a14d700dfeba80008574014730440220653b4ff490b03de06a9053aa7ab0b30e85c484c76900c586db65f7308f38ad86022019ac12ffb127e4a17a01dc6db730d3eccea837d2dc85a0275bdea8b393a2f11d01008576a914c98707b6420ef3454f3bd10d663adcc04452baea8763ac672102fb20469287c8ade948011bd001440d74633d5e1a98574e4783dd38b76509d8f67c820120876475527c210279d3a1c2086a0968404a0160c8f8c6f88c0ce7184022bb7406f98fdb503ea51452ae67a9140550b2b1621e788d795fe3ae308e7dec06a6a1e088ac6868101b06000000000000000003" val oldHtlcTimeoutTx = txWithInputInfoCodec.decode(oldHtlcTimeoutTxBin.bits).require.value assert(oldHtlcTimeoutTx.isInstanceOf[HtlcTimeoutTx]) - assert(oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].confirmBefore == BlockHeight(0)) - val htlcTimeoutTx = oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].copy(confirmBefore = BlockHeight(1105)) - val htlcTimeoutTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(htlcTimeoutTx).require).require.value - assert(htlcTimeoutTx == htlcTimeoutTx2) + assert(oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0))) } { val oldClaimHtlcSuccessTxBin = hex"0016 24e75b5236d1cdd482a6f540d5f08b9aa27b74a9ecae6e2622a67110b3ee1b3d89000000002bb0ad01000000000022002063e22369052a2bad9eb124737742690b8d1aba7693869d041da16443e2973e638576a91494957f4639ebc6f8a30e126552aff8429174dfb18763ac672102e1aa04ff55771238012edb958e6e0525af0415a01d52dd8d5f69fb391e586adc7c820120876475527c2102384e785d34b3b1fe35d3c093a750b234fbc79d8316c149e7845929f628a5baa052ae67a9145e3be49c9ace2d9eab11dc5fc29e40cd4148262e88ac6868fd014502000000000101e75b5236d1cdd482a6f540d5f08b9aa27b74a9ecae6e2622a67110b3ee1b3d890000000000000000000162970100000000001600140262586eef1a2c8f47ebc139a2733123d09e315603483045022100898f6b51361f044c8c54468dcb1b9decd75edcc1b69b62e584059b512eb075900220675f8b23aa402bb7d5bfdefbac4074088f82bd34d09b34c651c0dff48e6967930120efb38d645311af59c028cd8a9bf8ee21ff9e7c1a1cff1a0398a0315280247ac38576a91494957f4639ebc6f8a30e126552aff8429174dfb18763ac672102e1aa04ff55771238012edb958e6e0525af0415a01d52dd8d5f69fb391e586adc7c820120876475527c2102384e785d34b3b1fe35d3c093a750b234fbc79d8316c149e7845929f628a5baa052ae67a9145e3be49c9ace2d9eab11dc5fc29e40cd4148262e88ac686800000000ad02394dd18774f6f403f783afb518b6c69a89d531f718b29868ddca3e7905020000000000000000" val oldClaimHtlcSuccessTx = txWithInputInfoCodec.decode(oldClaimHtlcSuccessTxBin.bits).require.value assert(oldClaimHtlcSuccessTx.isInstanceOf[ClaimHtlcSuccessTx]) - assert(oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].confirmBefore == BlockHeight(0)) - val claimHtlcSuccessTx = oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmBefore = BlockHeight(1105)) - val claimHtlcSuccessTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(claimHtlcSuccessTx).require).require.value - assert(claimHtlcSuccessTx == claimHtlcSuccessTx2) + assert(oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0))) } { val oldClaimHtlcTimeoutTxBin = hex"0005 24df6aa4cd4e8877e4b3a363d6180951036d6f18ef214dabd50a6c05a60077d4d8000000002b50c30000000000002200205db892d76fb358ed508ec09c77b5a184cd9de3aa3e74a025a5c5d3f7adc221f78b76a914e0c241e0656088953f84475cbe5c70ded12e05b58763ac6721028876f3e23b21e07f889f10fc2aa0875b96021359a06d0b7f52a79fc284f6b2837c8201208763a9144ae5c96e8b7495fd35a5ca5681aa7c8f4ab6bc9b88527c2102b4d398ee7a42e87012de5a76832d9ebfa8532ef267490a7f3fe75b2c2e18cc9152ae677503981a06b175ac6868fd012b02000000000101df6aa4cd4e8877e4b3a363d6180951036d6f18ef214dabd50a6c05a60077d4d80000000000000000000106ae0000000000001600142b8e221121004b248f6551a0c6fb8ce219f4997e03483045022100ecdb8179f92c097594844756ac2bd7948f1c44ae74b54dfda86971800e59bf980220125edbe563f24cde15fa1fa25f4eac7cb938a3ace6f9329eb487938005c7621501008b76a914e0c241e0656088953f84475cbe5c70ded12e05b58763ac6721028876f3e23b21e07f889f10fc2aa0875b96021359a06d0b7f52a79fc284f6b2837c8201208763a9144ae5c96e8b7495fd35a5ca5681aa7c8f4ab6bc9b88527c2102b4d398ee7a42e87012de5a76832d9ebfa8532ef267490a7f3fe75b2c2e18cc9152ae677503981a06b175ac6868981a06000000000000000003" val oldClaimHtlcTimeoutTx = txWithInputInfoCodec.decode(oldClaimHtlcTimeoutTxBin.bits).require.value assert(oldClaimHtlcTimeoutTx.isInstanceOf[ClaimHtlcTimeoutTx]) - assert(oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].confirmBefore == BlockHeight(0)) - val claimHtlcTimeoutTx = oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmBefore = BlockHeight(1105)) - val claimHtlcTimeoutTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(claimHtlcTimeoutTx).require).require.value - assert(claimHtlcTimeoutTx == claimHtlcTimeoutTx2) + assert(oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0))) } }