diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 3566257da7..9665470690 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -4,7 +4,10 @@ ## Major changes - +### Remove support for non-anchor channels + +We remove the code used to support legacy channels that don't use anchor outputs or taproot. +If you still have such channels, eclair won't start: you will need to close those channels, and will only be able to update eclair once they have been successfully closed. ### Configuration changes diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index a6c7b837bd..a6b71251d3 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -39,7 +39,7 @@ eclair { // - unlock: eclair will automatically unlock the corresponding utxos // - ignore: eclair will leave these utxos locked and start startup-locked-utxos-behavior = "stop" - final-pubkey-refresh-delay = 3 seconds + final-address-refresh-delay = 3 seconds // If true, eclair will poll bitcoind for 30 seconds at start-up before giving up. wait-for-bitcoind-up = true // The txid of a transaction that exists in your custom signet network or "" to skip this check. @@ -87,7 +87,7 @@ eclair { // node that you trust using override-init-features (see below). option_zeroconf = disabled keysend = disabled - option_simple_close=optional + option_simple_close = optional trampoline_payment_prototype = disabled async_payment_prototype = disabled on_the_fly_funding = disabled @@ -177,8 +177,6 @@ eclair { channel-opener-whitelist = [] // a list of public keys; we will ignore rate limits on pending channels from these peers } - accept-incoming-static-remote-key-channels = false // whether we accept new incoming static_remote_key channels (which are obsolete, nodes should use anchor_output now) - quiescence-timeout = 1 minutes // maximum time we will stay quiescent (or wait to reach quiescence) before disconnecting channel-update { @@ -292,8 +290,8 @@ eclair { max-closing-feerate = 10 feerate-tolerance { - ratio-low = 0.5 // will allow remote fee rates as low as half our local feerate (only enforced when not using anchor outputs) - ratio-high = 10.0 // will allow remote fee rates as high as 10 times our local feerate (for all commitment formats) + ratio-low = 0.5 // will allow remote fee rates as low as half our local feerate for funding/splice transactions + ratio-high = 10.0 // will allow remote fee rates as high as 10 times our local feerate for commitment transactions and funding/splice transactions // when using anchor outputs, we only need to use a commitment feerate that allows the tx to propagate: we will use CPFP to speed up confirmation if needed. // the following value is the maximum feerate we'll use for our commit tx (in sat/byte) anchor-output-max-commit-feerate = 10 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala b/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala index b6a59cf41c..2a6c9b8b26 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/DBChecker.scala @@ -40,7 +40,7 @@ object DBChecker extends Logging { case _ => () } channels - case Failure(_) => throw IncompatibleDBException + case Failure(t) => throw IncompatibleDBException(t) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index e824e684dd..2bee26cadb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong} import fr.acinq.eclair.Setup.Seeds import fr.acinq.eclair.blockchain.fee._ +import fr.acinq.eclair.channel.ChannelFlags import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy} -import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes} import fr.acinq.eclair.crypto.Noise.KeyPair import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager} import fr.acinq.eclair.db._ @@ -39,7 +39,7 @@ import fr.acinq.eclair.router.Graph.HeuristicsConstants import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf, Router} import fr.acinq.eclair.tor.Socks5ProxyParams -import fr.acinq.eclair.transactions.Transactions +import fr.acinq.eclair.transactions.Transactions.{PhoenixSimpleTaprootChannelCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.wire.protocol._ import grizzled.slf4j.Logging import scodec.bits.ByteVector @@ -129,14 +129,17 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, max = (fundingFeerate * feerateTolerance.ratioHigh).max(minimumFeerate), ) // We use the most likely commitment format, even though there is no guarantee that this is the one that will be used. - val commitmentFormat = ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, announceChannel = false).commitmentFormat + val commitmentFormat = if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsPhoenix)) { + PhoenixSimpleTaprootChannelCommitmentFormat + } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsStaging)) { + ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat + } else { + ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + } val commitmentFeerate = onChainFeeConf.getCommitmentFeerate(currentBitcoinCoreFeerates, remoteNodeId, commitmentFormat) val commitmentRange = RecommendedFeeratesTlv.CommitmentFeerateRange( - min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate), - max = (commitmentFormat match { - case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh - case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate) - }).max(minimumFeerate), + min = Seq(commitmentFeerate * feerateTolerance.ratioLow, minimumFeerate).max, + max = Seq(commitmentFeerate * feerateTolerance.ratioHigh, feerateTolerance.anchorOutputMaxCommitFeerate, minimumFeerate).max, ) RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange)) } @@ -599,7 +602,6 @@ object NodeParams extends Logging { quiescenceTimeout = FiniteDuration(config.getDuration("channel.quiescence-timeout").getSeconds, TimeUnit.SECONDS), balanceThresholds = config.getConfigList("channel.channel-update.balance-thresholds").asScala.map(conf => BalanceThreshold(Satoshi(conf.getLong("available-sat")), Satoshi(conf.getLong("max-htlc-sat")))).toSeq, minTimeBetweenUpdates = FiniteDuration(config.getDuration("channel.channel-update.min-time-between-updates").getSeconds, TimeUnit.SECONDS), - acceptIncomingStaticRemoteKeyChannels = config.getBoolean("channel.accept-incoming-static-remote-key-channels") ), onChainFeeConf = OnChainFeeConf( feeTargets = feeTargets, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 4dd9fc4ab1..0038338984 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -268,9 +268,8 @@ class Setup(val datadir: File, }) _ <- feeratesRetrieved.future - finalPubkey = new AtomicReference[PublicKey](null) finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](null) - pubkeyRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-pubkey-refresh-delay").getSeconds, TimeUnit.SECONDS) + finalAddressRefreshDelay = FiniteDuration(config.getDuration("bitcoind.final-address-refresh-delay").getSeconds, TimeUnit.SECONDS) // there are 3 possibilities regarding onchain key management: // 1) there is no `eclair-signer.conf` file in Eclair's data directory, Eclair will not manage Bitcoin core keys, and Eclair's API will not return bitcoin core descriptors. This is the default mode. // 2) there is an `eclair-signer.conf` file in Eclair's data directory, but the name of the wallet set in `eclair-signer.conf` does not match the `eclair.bitcoind.wallet` setting in `eclair.conf`. @@ -278,14 +277,8 @@ class Setup(val datadir: File, // This is how you would create a new bitcoin wallet whose private keys are managed by Eclair. // 3) there is an `eclair-signer.conf` file in Eclair's data directory, and the name of the wallet set in `eclair-signer.conf` matches the `eclair.bitcoind.wallet` setting in `eclair.conf`. // Eclair will assume that this is a watch-only bitcoin wallet that has been created from descriptors generated by Eclair, and will manage its private keys, and here we pass the onchain key manager to our bitcoin client. - bitcoinClient = new BitcoinCoreClient(bitcoin, nodeParams.liquidityAdsConfig.lockUtxos, if (bitcoin.wallet == onChainKeyManager_opt.map(_.walletName)) onChainKeyManager_opt else None) with OnChainPubkeyCache { - val refresher: typed.ActorRef[OnChainAddressRefresher.Command] = system.spawn(Behaviors.supervise(OnChainAddressRefresher(this, finalPubkey, finalPubkeyScript, pubkeyRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "onchain-address-manager") - - override def getP2wpkhPubkey(renew: Boolean): PublicKey = { - val key = finalPubkey.get() - if (renew) refresher ! OnChainAddressRefresher.RenewPubkey - key - } + bitcoinClient = new BitcoinCoreClient(bitcoin, nodeParams.liquidityAdsConfig.lockUtxos, if (bitcoin.wallet == onChainKeyManager_opt.map(_.walletName)) onChainKeyManager_opt else None) with OnChainAddressCache { + val refresher: typed.ActorRef[OnChainAddressRefresher.Command] = system.spawn(Behaviors.supervise(OnChainAddressRefresher(this, finalPubkeyScript, finalAddressRefreshDelay)).onFailure(typed.SupervisorStrategy.restart), name = "on-chain-address-manager") override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = { val script = finalPubkeyScript.get() @@ -294,8 +287,6 @@ class Setup(val datadir: File, } } _ = if (bitcoinClient.useEclairSigner) logger.info("using eclair to sign bitcoin core transactions") - initialPubkey <- bitcoinClient.getP2wpkhPubkey() - _ = finalPubkey.set(initialPubkey) // We use the default address type configured on the Bitcoin Core node. initialPubkeyScript <- bitcoinClient.getReceivePublicKeyScript(addressType_opt = None) _ = finalPubkeyScript.set(initialPubkeyScript) @@ -494,7 +485,7 @@ case class Kit(nodeParams: NodeParams, postman: typed.ActorRef[Postman.Command], offerManager: typed.ActorRef[OfferManager.Command], defaultOfferHandler: typed.ActorRef[OfferManager.HandlerCommand], - wallet: OnChainWallet with OnChainPubkeyCache) + wallet: OnChainWallet with OnChainAddressCache) object Kit { @@ -518,7 +509,7 @@ case class BitcoinWalletNotLoadedException(wallet: String, loaded: List[String]) case object EmptyAPIPasswordException extends RuntimeException("must set a password for the json-rpc api") -case object IncompatibleDBException extends RuntimeException("database is not compatible with this version of eclair") +case class IncompatibleDBException(t: Throwable) extends RuntimeException(s"database is not compatible with this version of eclair: ${t.getMessage}") case object IncompatibleNetworkDBException extends RuntimeException("network database is not compatible with this version of eclair") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala index caefb87fdb..0d2a217526 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/OnChainWallet.scala @@ -17,7 +17,6 @@ package fr.acinq.eclair.blockchain import fr.acinq.bitcoin.psbt.Psbt -import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, ScriptElt, Transaction, TxId} import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType import fr.acinq.eclair.blockchain.fee.FeeratePerKw @@ -120,18 +119,10 @@ trait OnChainAddressGenerator { /** Generate the public key script for a new wallet address. */ def getReceivePublicKeyScript(addressType_opt: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] - /** Generate a p2wpkh wallet address and return the corresponding public key. */ - def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey] - } /** A caching layer for [[OnChainAddressGenerator]] that provides synchronous access to wallet addresses and keys. */ -trait OnChainPubkeyCache { - - /** - * @param renew applies after requesting the current pubkey, and is asynchronous. - */ - def getP2wpkhPubkey(renew: Boolean): PublicKey +trait OnChainAddressCache { /** * @param renew applies after requesting the current script, and is asynchronous. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresher.scala index 885cfeef0a..b880846a25 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresher.scala @@ -3,7 +3,6 @@ package fr.acinq.eclair.blockchain.bitcoind import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} -import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Script, ScriptElt} import fr.acinq.eclair.blockchain.OnChainAddressGenerator @@ -19,18 +18,16 @@ object OnChainAddressRefresher { // @formatter:off sealed trait Command - case object RenewPubkey extends Command case object RenewPubkeyScript extends Command - private case class SetPubkey(pubkey: PublicKey) extends Command private case class SetPubkeyScript(script: Seq[ScriptElt]) extends Command private case class Error(reason: Throwable) extends Command private case object Done extends Command // @formatter:on - def apply(generator: OnChainAddressGenerator, finalPubkey: AtomicReference[PublicKey], finalPubkeyScript: AtomicReference[Seq[ScriptElt]], delay: FiniteDuration): Behavior[Command] = { + def apply(generator: OnChainAddressGenerator, finalPubkeyScript: AtomicReference[Seq[ScriptElt]], delay: FiniteDuration): Behavior[Command] = { Behaviors.setup { context => Behaviors.withTimers { timers => - val refresher = new OnChainAddressRefresher(generator, finalPubkey, finalPubkeyScript, context, timers, delay) + val refresher = new OnChainAddressRefresher(generator, finalPubkeyScript, context, timers, delay) refresher.idle() } } @@ -38,7 +35,6 @@ object OnChainAddressRefresher { } private class OnChainAddressRefresher(generator: OnChainAddressGenerator, - finalPubkey: AtomicReference[PublicKey], finalPubkeyScript: AtomicReference[Seq[ScriptElt]], context: ActorContext[OnChainAddressRefresher.Command], timers: TimerScheduler[OnChainAddressRefresher.Command], delay: FiniteDuration) { @@ -47,13 +43,6 @@ private class OnChainAddressRefresher(generator: OnChainAddressGenerator, /** In that state, we're ready to renew our on-chain address whenever requested. */ def idle(): Behavior[Command] = Behaviors.receiveMessage { - case RenewPubkey => - context.log.debug("renewing pubkey (current={})", finalPubkey.get()) - context.pipeToSelf(generator.getP2wpkhPubkey()) { - case Success(pubkey) => SetPubkey(pubkey) - case Failure(reason) => Error(reason) - } - renewing() case RenewPubkeyScript => context.log.debug("renewing script (current={})", Script.write(finalPubkeyScript.get()).toHex) context.pipeToSelf(generator.getReceivePublicKeyScript()) { @@ -68,14 +57,11 @@ private class OnChainAddressRefresher(generator: OnChainAddressGenerator, /** We ignore concurrent requests while waiting for bitcoind to respond. */ private def renewing(): Behavior[Command] = Behaviors.receiveMessage { - case SetPubkey(pubkey) => - timers.startSingleTimer(Done, delay) - delaying(Some(pubkey), None) case SetPubkeyScript(script) => timers.startSingleTimer(Done, delay) - delaying(None, Some(script)) + delaying(script) case Error(reason) => - context.log.error("cannot renew public key or script", reason) + context.log.error("cannot renew script", reason) idle() case cmd => context.log.debug("ignoring command={} while waiting for bitcoin core's response", cmd) @@ -83,29 +69,16 @@ private class OnChainAddressRefresher(generator: OnChainAddressGenerator, } /** - * After receiving our new script or pubkey from bitcoind, we wait before updating our current values. + * After receiving our new address from bitcoind, we wait before updating our current value. * While waiting, we ignore additional requests to renew. * * This ensures that a burst of requests during a mass force-close use the same final on-chain address instead of * creating a lot of address churn on our bitcoin wallet. - * - * Note that while we're updating our final script, we will ignore requests to update our final public key (and the - * other way around). This is fine, since the public key is only used: - * - when opening static_remotekey channels, which is disabled by default - * - when closing channels with peers that don't support shutdown_anysegwit (which should be widely supported) - * - * In practice, we most likely always use [[RenewPubkeyScript]]. */ - private def delaying(nextPubkey_opt: Option[PublicKey], nextScript_opt: Option[Seq[ScriptElt]]): Behavior[Command] = Behaviors.receiveMessage { + private def delaying(nextScript: Seq[ScriptElt]): Behavior[Command] = Behaviors.receiveMessage { case Done => - nextPubkey_opt.foreach { nextPubkey => - context.log.info("setting pubkey to {}", nextPubkey) - finalPubkey.set(nextPubkey) - } - nextScript_opt.foreach { nextScript => - context.log.info("setting script to {}", Script.write(nextScript).toHex) - finalPubkeyScript.set(nextScript) - } + context.log.info("setting script to {}", Script.write(nextScript).toHex) + finalPubkeyScript.set(nextScript) idle() case cmd => context.log.debug("rate-limiting command={}", cmd) 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 30ea02c2db..7deea85fbd 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 @@ -65,29 +65,11 @@ case class DustTolerance(maxExposure: Satoshi, closeOnUpdateFeeOverflow: Boolean case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMaxCommitFeerate: FeeratePerKw, dustTolerance: DustTolerance) { /** - * @param commitmentFormat commitment format (anchor outputs allows a much higher tolerance since fees can be adjusted after tx is published) * @param networkFeerate reference fee rate (value we estimate from our view of the network) * @param proposedFeerate fee rate proposed (new proposal through update_fee or previous proposal used in our current commit tx) * @return true if the difference between proposed and reference fee rates is too high. */ - def isFeeDiffTooHigh(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = { - isProposedFeerateTooLow(commitmentFormat, networkFeerate, proposedFeerate) || isProposedFeerateTooHigh(commitmentFormat, networkFeerate, proposedFeerate) - } - - def isProposedFeerateTooHigh(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = { - commitmentFormat match { - case Transactions.DefaultCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate - case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | PhoenixSimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate - } - } - - def isProposedFeerateTooLow(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = { - commitmentFormat match { - case Transactions.DefaultCommitmentFormat => proposedFeerate < networkFeerate * ratioLow - // When using anchor outputs, we allow low feerates: fees will be set with CPFP and RBF at broadcast time. - case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | PhoenixSimpleTaprootChannelCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => false - } - } + def isProposedCommitFeerateTooHigh(networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = networkFeerate * ratioHigh < proposedFeerate } case class OnChainFeeConf(feeTargets: FeeTargets, @@ -120,7 +102,6 @@ case class OnChainFeeConf(feeTargets: FeeTargets, val networkFeerate = feerates.fast val networkMinFee = feerates.minimum commitmentFormat match { - case Transactions.DefaultCommitmentFormat => networkFeerate case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat => val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate) // We make sure the feerate is always greater than the propagation threshold. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 1b0b28637d..81772a8cc6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -786,11 +786,11 @@ object DATA_CLOSED { }, closingAmount = closingType match { case Closing.MutualClose(closingTx) => closingTx.toLocalOutput_opt.map(_.amount).getOrElse(0 sat) - case Closing.LocalClose(_, localCommitPublished) => Closing.closingBalance(d.channelParams, d.commitments.latest.commitmentFormat, d.finalScriptPubKey, localCommitPublished) - case Closing.CurrentRemoteClose(_, remoteCommitPublished) => Closing.closingBalance(d.channelParams, d.commitments.latest.commitmentFormat, d.finalScriptPubKey, remoteCommitPublished) - case Closing.NextRemoteClose(_, remoteCommitPublished) => Closing.closingBalance(d.channelParams, d.commitments.latest.commitmentFormat, d.finalScriptPubKey, remoteCommitPublished) - case Closing.RecoveryClose(remoteCommitPublished) => Closing.closingBalance(d.channelParams, d.commitments.latest.commitmentFormat, d.finalScriptPubKey, remoteCommitPublished) - case Closing.RevokedClose(revokedCommitPublished) => Closing.closingBalance(d.channelParams, d.commitments.latest.commitmentFormat, d.finalScriptPubKey, revokedCommitPublished) + case Closing.LocalClose(_, localCommitPublished) => Closing.closingBalance(d.finalScriptPubKey, localCommitPublished) + case Closing.CurrentRemoteClose(_, remoteCommitPublished) => Closing.closingBalance(d.finalScriptPubKey, remoteCommitPublished) + case Closing.NextRemoteClose(_, remoteCommitPublished) => Closing.closingBalance(d.finalScriptPubKey, remoteCommitPublished) + case Closing.RecoveryClose(remoteCommitPublished) => Closing.closingBalance(d.finalScriptPubKey, remoteCommitPublished) + case Closing.RevokedClose(revokedCommitPublished) => Closing.closingBalance(d.finalScriptPubKey, revokedCommitPublished) } ) } @@ -805,7 +805,6 @@ case class LocalChannelParams(nodeId: PublicKey, isChannelOpener: Boolean, paysCommitTxFees: Boolean, upfrontShutdownScript_opt: Option[ByteVector], - walletStaticPaymentBasepoint: Option[PublicKey], // Current connection features, or last features used if the channel is disconnected. Note that // these features are updated at each reconnection and may be different from the channel permanent // features (see [[ChannelFeatures]]). diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index ed61f8f4ff..884ed42273 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -44,7 +44,7 @@ case class FundingAmountTooHigh (override val channelId: Byte case class InvalidFundingBalances (override val channelId: ByteVector32, fundingAmount: Satoshi, localBalance: MilliSatoshi, remoteBalance: MilliSatoshi) extends ChannelException(channelId, s"invalid balances funding_amount=$fundingAmount local=$localBalance remote=$remoteBalance") case class InvalidPushAmount (override val channelId: ByteVector32, pushAmount: MilliSatoshi, max: MilliSatoshi) extends ChannelException(channelId, s"invalid pushAmount=$pushAmount (max=$max)") case class InvalidMaxAcceptedHtlcs (override val channelId: ByteVector32, maxAcceptedHtlcs: Int, max: Int) extends ChannelException(channelId, s"invalid max_accepted_htlcs=$maxAcceptedHtlcs (max=$max)") -case class InvalidChannelType (override val channelId: ByteVector32, ourChannelType: ChannelType, theirChannelType: ChannelType) extends ChannelException(channelId, s"invalid channel_type=$theirChannelType, expected channel_type=$ourChannelType") +case class InvalidChannelType (override val channelId: ByteVector32, remoteChannelType: ChannelType) extends ChannelException(channelId, s"invalid channel_type=$remoteChannelType") case class MissingChannelType (override val channelId: ByteVector32) extends ChannelException(channelId, "option_channel_type was negotiated but channel_type is missing") case class DustLimitTooSmall (override val channelId: ByteVector32, dustLimit: Satoshi, min: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too small (min=$min)") case class DustLimitTooLarge (override val channelId: ByteVector32, dustLimit: Satoshi, max: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too large (max=$max)") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 7ca5b539b2..e557ceed7b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.channel +import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature} @@ -67,9 +68,6 @@ sealed trait SupportedChannelType extends ChannelType { /** Known channel-type features */ override def features: Set[ChannelTypeFeature] - /** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */ - def paysDirectlyToWallet: Boolean - /** Format of the channel transactions. */ def commitmentFormat: CommitmentFormat } @@ -77,25 +75,6 @@ sealed trait SupportedChannelType extends ChannelType { object ChannelTypes { // @formatter:off - case class Standard(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { - override def features: Set[ChannelTypeFeature] = Set( - if (scidAlias) Some(Features.ScidAlias) else None, - if (zeroConf) Some(Features.ZeroConf) else None, - ).flatten - override def paysDirectlyToWallet: Boolean = false - override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat - override def toString: String = s"standard${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" - } - case class StaticRemoteKey(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { - override def features: Set[ChannelTypeFeature] = Set( - if (scidAlias) Some(Features.ScidAlias) else None, - if (zeroConf) Some(Features.ZeroConf) else None, - Some(Features.StaticRemoteKey) - ).flatten - override def paysDirectlyToWallet: Boolean = true - override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat - override def toString: String = s"static_remotekey${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" - } case class AnchorOutputs(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { override def features: Set[ChannelTypeFeature] = Set( if (scidAlias) Some(Features.ScidAlias) else None, @@ -103,7 +82,6 @@ object ChannelTypes { Some(Features.StaticRemoteKey), Some(Features.AnchorOutputs) ).flatten - override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = UnsafeLegacyAnchorOutputsCommitmentFormat override def toString: String = s"anchor_outputs${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } @@ -114,18 +92,15 @@ object ChannelTypes { Some(Features.StaticRemoteKey), Some(Features.AnchorOutputsZeroFeeHtlcTx) ).flatten - override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType { - /** Known channel-type features */ override def features: Set[ChannelTypeFeature] = Set( if (scidAlias) Some(Features.ScidAlias) else None, if (zeroConf) Some(Features.ZeroConf) else None, Some(Features.SimpleTaprootChannelsStaging), ).flatten - override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat override def toString: String = s"simple_taproot_channel_staging${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } @@ -137,9 +112,7 @@ object ChannelTypes { // Phoenix uses custom channel types, that we may remove in the future. case object SimpleTaprootChannelsPhoenix extends SupportedChannelType { - /** Known channel-type features */ override def features: Set[ChannelTypeFeature] = Set(Features.PhoenixZeroReserve, Features.SimpleTaprootChannelsPhoenix) - override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = PhoenixSimpleTaprootChannelCommitmentFormat override def toString: String = "phoenix_simple_taproot_channel" } @@ -147,14 +120,6 @@ object ChannelTypes { // @formatter:on private val features2ChannelType: Map[Features[_ <: InitFeature], SupportedChannelType] = Set( - Standard(), - Standard(zeroConf = true), - Standard(scidAlias = true), - Standard(scidAlias = true, zeroConf = true), - StaticRemoteKey(), - StaticRemoteKey(zeroConf = true), - StaticRemoteKey(scidAlias = true), - StaticRemoteKey(scidAlias = true, zeroConf = true), AnchorOutputs(), AnchorOutputs(zeroConf = true), AnchorOutputs(scidAlias = true), @@ -163,49 +128,25 @@ object ChannelTypes { AnchorOutputsZeroFeeHtlcTx(zeroConf = true), AnchorOutputsZeroFeeHtlcTx(scidAlias = true), AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), - SimpleTaprootChannelsPhoenix, SimpleTaprootChannelsStaging(), SimpleTaprootChannelsStaging(zeroConf = true), SimpleTaprootChannelsStaging(scidAlias = true), SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true), - ) - .map(channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType) - .toMap + SimpleTaprootChannelsPhoenix, + ).map { + channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType + }.toMap // NB: Bolt 2: features must exactly match in order to identify a channel type. def fromFeatures(features: Features[InitFeature]): ChannelType = features2ChannelType.getOrElse(features, UnsupportedChannelType(features)) - /** Pick the channel type based on local and remote feature bits, as defined by the spec. */ - def defaultFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): SupportedChannelType = { - def canUse(feature: InitFeature): Boolean = Features.canUseFeature(localFeatures, remoteFeatures, feature) - - val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel - val zeroConf = canUse(Features.ZeroConf) - if (canUse(Features.SimpleTaprootChannelsStaging)) { - SimpleTaprootChannelsStaging(scidAlias, zeroConf) - } else if (canUse(Features.SimpleTaprootChannelsPhoenix)) { - SimpleTaprootChannelsPhoenix - } else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) { - AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf) - } else if (canUse(Features.AnchorOutputs)) { - AnchorOutputs(scidAlias, zeroConf) - } else if (canUse(Features.StaticRemoteKey)) { - StaticRemoteKey(scidAlias, zeroConf) - } else { - Standard(scidAlias, zeroConf) - } - } - /** Check if a given channel type is compatible with our features. */ - def areCompatible(localFeatures: Features[InitFeature], remoteChannelType: ChannelType): Option[SupportedChannelType] = remoteChannelType match { - case _: UnsupportedChannelType => None + def areCompatible(channelId: ByteVector32, localFeatures: Features[InitFeature], remoteChannelType_opt: Option[ChannelType]): Either[ChannelException, SupportedChannelType] = remoteChannelType_opt match { + case None => Left(MissingChannelType(channelId)) + case Some(channelType: UnsupportedChannelType) => Left(InvalidChannelType(channelId, channelType)) // We ensure that we support the features necessary for this channel type. - case proposedChannelType: SupportedChannelType => - if (proposedChannelType.features.forall(f => localFeatures.hasFeature(f))) { - Some(proposedChannelType) - } else { - None - } + case Some(proposedChannelType: SupportedChannelType) if proposedChannelType.features.forall(f => localFeatures.hasFeature(f)) => Right(proposedChannelType) + case Some(proposedChannelType: SupportedChannelType) => Left(InvalidChannelType(channelId, proposedChannelType)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 9e87a3a26e..d2b4065fda 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -202,7 +202,7 @@ object LocalCommit { case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) { def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat, remoteNonce_opt: Option[IndividualNonce], batchSize: Int = 1): Either[ChannelException, CommitSig] = { val fundingKey = channelKeys.fundingKey(fundingTxIndex) - val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint, commitmentFormat) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint) val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, commitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) val htlcSigs = sortedHtlcTxs.map(_.localSig(commitKeys)) @@ -278,9 +278,9 @@ case class Commitment(fundingTxIndex: Long, def commitInput(channelKeys: ChannelKeys): InputInfo = commitInput(localFundingKey(channelKeys)) - def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index, commitmentFormat) + def localKeys(params: ChannelParams, channelKeys: ChannelKeys): LocalCommitmentKeys = LocalCommitmentKeys(params, channelKeys, localCommit.index) - def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint, commitmentFormat) + def remoteKeys(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = RemoteCommitmentKeys(params, channelKeys, remotePerCommitmentPoint) /** Channel reserve that applies to our funds. */ def localChannelReserve(params: ChannelParams): Satoshi = if (params.channelFeatures.hasFeature(Features.DualFunding) || fundingTxIndex > 0) { @@ -458,19 +458,7 @@ case class Commitment(fundingTxIndex: Long, localCommit.spec.htlcs.collect(DirectedHtlc.incoming).filter(nearlyExpired) } - def canSendAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf, reputationScore: Reputation.Score): Either[ChannelException, Unit] = { - // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk - // we need to verify that we're not disagreeing on feerates anymore before offering new HTLCs - // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat) - val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - // What we want to avoid is having an HTLC in a commitment transaction that has a very low feerate, which we won't - // be able to confirm in time to claim the HTLC, so we only need to check that the feerate isn't too low. - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { - case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) - case None => - } - + def canSendAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf, reputationScore: Reputation.Score): Either[ChannelException, Unit] = { // let's compute the current commitments *as seen by them* with the additional htlc // we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation val remoteCommit1 = nextRemoteCommit_opt.getOrElse(remoteCommit) @@ -538,17 +526,7 @@ case class Commitment(fundingTxIndex: Long, reputationScore.checkOutgoingChannelOccupancy(params.channelId, this, outgoingHtlcs.toSeq) } - def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { - // we allowed mismatches between our feerates and our remote's as long as commitments didn't contain any HTLC at risk - // we need to verify that we're not disagreeing on feerates anymore before accepting new HTLCs - // NB: there may be a pending update_fee that hasn't been applied yet that needs to be taken into account - val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat) - val remoteFeerate = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw } - remoteFeerate.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, feerate)) match { - case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = feerate)) - case None => - } - + def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges): Either[ChannelException, Unit] = { // let's compute the current commitment *as seen by us* including this additional htlc val reduced = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) val incomingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming) @@ -616,10 +594,7 @@ case class Commitment(fundingTxIndex: Long, def canReceiveFee(targetFeerate: FeeratePerKw, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = { val localFeerate = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, commitmentFormat) - if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooHigh(commitmentFormat, localFeerate, targetFeerate)) { - return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) - } else if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedFeerateTooLow(commitmentFormat, localFeerate, targetFeerate) && hasPendingOrProposedHtlcs(changes)) { - // If the proposed feerate is too low, but we don't have any pending HTLC, we temporarily accept it. + if (feeConf.feerateToleranceFor(params.remoteNodeId).isProposedCommitFeerateTooHigh(localFeerate, targetFeerate)) { return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeerate, remoteFeeratePerKw = targetFeerate)) } else { // let's compute the current commitment *as seen by us* including this change @@ -903,7 +878,7 @@ case class Commitments(channelParams: ChannelParams, * @param cmd add HTLC command * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right(new commitments, updateAddHtlc) */ - def sendAdd(cmd: CMD_ADD_HTLC, currentHeight: BlockHeight, channelConf: ChannelConf, feerates: FeeratesPerKw, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { + def sendAdd(cmd: CMD_ADD_HTLC, currentHeight: BlockHeight, channelConf: ChannelConf, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { // we must ensure we're not relaying htlcs that are already expired, otherwise the downstream channel will instantly close // NB: we add a 3 blocks safety to reduce the probability of running into this when our bitcoin node is slightly outdated val minExpiry = CltvExpiry(currentHeight + 3) @@ -927,9 +902,9 @@ case class Commitments(channelParams: ChannelParams, val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1) val originChannels1 = originChannels + (add.id -> cmd.origin) // we verify that this htlc is allowed in every active commitment - val failures = - (active.map(_.canSendAdd(add.amountMsat, channelParams, changes1, feerates, feeConf, cmd.reputationScore)) - :+ cmd.reputationScore.checkIncomingChannelOccupancy(cmd.origin.upstream.incomingChannelOccupancy, channelId)) + val failures = active.map(_.canSendAdd(add.amountMsat, channelParams, changes1, feeConf, cmd.reputationScore)) + // and that we don't exceed the authorized channel occupancy (jamming) + .appended(cmd.reputationScore.checkIncomingChannelOccupancy(cmd.origin.upstream.incomingChannelOccupancy, channelId)) .collect { case Left(f) => f } if (failures.isEmpty) { Right(copy(changes = changes1, originChannels = originChannels1), add) @@ -953,7 +928,7 @@ case class Commitments(channelParams: ChannelParams, } } - def receiveAdd(add: UpdateAddHtlc, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Commitments] = { + def receiveAdd(add: UpdateAddHtlc): Either[ChannelException, Commitments] = { if (add.id != changes.remoteNextHtlcId) { return Left(UnexpectedHtlcId(channelId, expected = changes.remoteNextHtlcId, actual = add.id)) } @@ -966,7 +941,7 @@ case class Commitments(channelParams: ChannelParams, val changes1 = changes.addRemoteProposal(add).copy(remoteNextHtlcId = changes.remoteNextHtlcId + 1) // we verify that this htlc is allowed in every active commitment - active.map(_.canReceiveAdd(add.amountMsat, channelParams, changes1, feerates, feeConf)) + active.map(_.canReceiveAdd(add.amountMsat, channelParams, changes1)) .collectFirst { case Left(f) => Left(f) } .getOrElse(Right(copy(changes = changes1))) } @@ -1087,8 +1062,8 @@ case class Commitments(channelParams: ChannelParams, remoteNextCommitInfo match { case Right(_) if !changes.localHasChanges => Left(CannotSignWithoutChanges(channelId)) case Right(remoteNextPerCommitmentPoint) => + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint) val (active1, sigs) = active.map(c => { - val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint, c.commitmentFormat) c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, nextRemoteCommitNonces.get(c.fundingTxId)) match { case Left(e) => return Left(e) case Right((c, cs)) => (c, cs) @@ -1118,8 +1093,8 @@ case class Commitments(channelParams: ChannelParams, case commitSig: CommitSig => Seq(commitSig) } // Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments. + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1) val active1 = active.zip(sigs).map { case (commitment, commit) => - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex + 1, commitment.commitmentFormat) commitment.receiveCommit(channelParams, channelKeys, commitKeys, changes, commit) match { case Left(f) => return Left(f) case Right(commitment1) => commitment1 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 230b9befee..bec02e006c 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 @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.scalacompat.Musig2.{IndividualNonce, LocalNonce} import fr.acinq.bitcoin.scalacompat._ import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.OnChainPubkeyCache +import fr.acinq.eclair.blockchain.OnChainAddressCache import fr.acinq.eclair.blockchain.fee._ import fr.acinq.eclair.channel.ChannelSpendSignature.{IndividualSignature, PartialSignatureWithNonce} import fr.acinq.eclair.channel.fsm.Channel @@ -99,7 +99,7 @@ object Helpers { } /** Called by the fundee of a single-funded channel. */ - def validateParamsSingleFundedFundee(nodeParams: NodeParams, channelType: SupportedChannelType, localFeatures: Features[InitFeature], open: OpenChannel, remoteNodeId: PublicKey, remoteFeatures: Features[InitFeature]): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = { + def validateParamsSingleFundedFundee(nodeParams: NodeParams, localFeatures: Features[InitFeature], open: OpenChannel, remoteNodeId: PublicKey, remoteFeatures: Features[InitFeature]): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = { // BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver: // MUST reject the channel. if (nodeParams.chainHash != open.chainHash) return Left(InvalidChainHash(open.temporaryChannelId, local = nodeParams.chainHash, remote = open.chainHash)) @@ -133,15 +133,19 @@ object Helpers { return Left(ChannelReserveNotMet(open.temporaryChannelId, toLocalMsat, toRemoteMsat, open.channelReserveSatoshis)) } + val channelType = ChannelTypes.areCompatible(open.temporaryChannelId, localFeatures, open.channelType_opt) match { + case Left(f) => return Left(f) + case Right(proposedChannelType) => proposedChannelType + } val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) channelType.commitmentFormat match { case _: SimpleTaprootChannelCommitmentFormat => if (open.commitNonce_opt.isEmpty) return Left(MissingCommitNonce(open.temporaryChannelId, TxId(ByteVector32.Zeroes), commitmentNumber = 0)) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => () + case _: AnchorOutputsCommitmentFormat => () } // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isProposedCommitFeerateTooHigh(localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) // we don't check that the funder's amount for the initial commitment transaction is sufficient for full fee payment // now, but it will be done later when we receive `funding_created` @@ -154,7 +158,6 @@ object Helpers { /** Called by the non-initiator of a dual-funded channel. */ def validateParamsDualFundedNonInitiator(nodeParams: NodeParams, - channelType: SupportedChannelType, open: OpenDualFundedChannel, fundingScript: ByteVector, remoteNodeId: PublicKey, @@ -184,11 +187,15 @@ object Helpers { if (open.dustLimit < Channel.MIN_DUST_LIMIT) return Left(DustLimitTooSmall(open.temporaryChannelId, open.dustLimit, Channel.MIN_DUST_LIMIT)) if (open.dustLimit > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimit, nodeParams.channelConf.maxRemoteDustLimit)) + val channelType = ChannelTypes.areCompatible(open.temporaryChannelId, localFeatures, open.channelType_opt) match { + case Left(f) => return Left(f) + case Right(proposedChannelType) => proposedChannelType + } val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat) - if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) + if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isProposedCommitFeerateTooHigh(localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) for { script_opt <- extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt) @@ -196,29 +203,19 @@ object Helpers { } yield (channelFeatures, script_opt, willFund_opt) } - private def validateChannelType(channelId: ByteVector32, channelType: SupportedChannelType, channelFlags: ChannelFlags, openChannelType_opt: Option[ChannelType], acceptChannelType_opt: Option[ChannelType], localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): Option[ChannelException] = { - acceptChannelType_opt match { - case Some(theirChannelType) if acceptChannelType_opt != openChannelType_opt => - // if channel_type is set, and channel_type was set in open_channel, and they are not equal types: MUST reject the channel. - Some(InvalidChannelType(channelId, channelType, theirChannelType)) - case None if Features.canUseFeature(localFeatures, remoteFeatures, Features.ChannelType) => - // Bolt 2: if `option_channel_type` is negotiated: MUST set `channel_type` - Some(MissingChannelType(channelId)) - case None if channelType != ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, channelFlags.announceChannel) => - // If we have overridden the default channel type, but they didn't support explicit channel type negotiation, - // we need to abort because they expect a different channel type than what we offered. - Some(InvalidChannelType(channelId, channelType, ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, channelFlags.announceChannel))) - case _ => - // we agree on channel type - None + private def validateChannelTypeInitiator(channelId: ByteVector32, openChannelType_opt: Option[ChannelType], acceptChannelType_opt: Option[ChannelType]): Either[ChannelException, SupportedChannelType] = { + (openChannelType_opt, acceptChannelType_opt) match { + case (Some(proposed: SupportedChannelType), Some(received)) if proposed == received => Right(proposed) + case (Some(_), Some(received)) => Left(InvalidChannelType(channelId, received)) + case _ => Left(MissingChannelType(channelId)) } } /** Called by the funder of a single-funded channel. */ - def validateParamsSingleFundedFunder(nodeParams: NodeParams, channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], open: OpenChannel, accept: AcceptChannel): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = { - validateChannelType(open.temporaryChannelId, channelType, open.channelFlags, open.channelType_opt, accept.channelType_opt, localFeatures, remoteFeatures) match { - case Some(t) => return Left(t) - case None => // we agree on channel type + def validateParamsSingleFundedFunder(nodeParams: NodeParams, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], open: OpenChannel, accept: AcceptChannel): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = { + val channelType = validateChannelTypeInitiator(open.temporaryChannelId, open.channelType_opt, accept.channelType_opt) match { + case Left(t) => return Left(t) + case Right(channelType) => channelType } if (accept.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) return Left(InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS)) @@ -247,7 +244,7 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) channelType.commitmentFormat match { case _: SimpleTaprootChannelCommitmentFormat => if (accept.commitNonce_opt.isEmpty) return Left(MissingCommitNonce(open.temporaryChannelId, TxId(ByteVector32.Zeroes), commitmentNumber = 0)) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => () + case _: AnchorOutputsCommitmentFormat => () } extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt)) } @@ -255,14 +252,13 @@ object Helpers { /** Called by the initiator of a dual-funded channel. */ def validateParamsDualFundedInitiator(nodeParams: NodeParams, remoteNodeId: PublicKey, - channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], open: OpenDualFundedChannel, accept: AcceptDualFundedChannel): Either[ChannelException, (ChannelFeatures, Option[ByteVector], Option[LiquidityAds.Purchase])] = { - validateChannelType(open.temporaryChannelId, channelType, open.channelFlags, open.channelType_opt, accept.channelType_opt, localFeatures, remoteFeatures) match { - case Some(t) => return Left(t) - case None => // we agree on channel type + val channelType = validateChannelTypeInitiator(open.temporaryChannelId, open.channelType_opt, accept.channelType_opt) match { + case Left(t) => return Left(t) + case Right(channelType) => channelType } // BOLT #2: Channel funding limits @@ -687,14 +683,8 @@ object Helpers { object MutualClose { - def generateFinalScriptPubKey(wallet: OnChainPubkeyCache, allowAnySegwit: Boolean, renew: Boolean = true): ByteVector = { - if (!allowAnySegwit) { - // If our peer only supports segwit v0, we cannot let bitcoind choose the address type: we always use p2wpkh. - val finalPubKey = wallet.getP2wpkhPubkey(renew) - Script.write(Script.pay2wpkh(finalPubKey)) - } else { - Script.write(wallet.getReceivePublicKeyScript(renew)) - } + def generateFinalScriptPubKey(wallet: OnChainAddressCache, renew: Boolean = true): ByteVector = { + Script.write(wallet.getReceivePublicKeyScript(renew)) } def isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean = { @@ -721,15 +711,9 @@ object Helpers { def firstClosingFee(channelKeys: ChannelKeys, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = { val requestedFeerate = onChainFeeConf.getClosingFeerate(feerates, maxClosingFeerateOverride_opt = None) - val preferredFeerate = commitment.commitmentFormat match { - case DefaultCommitmentFormat => - // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - requestedFeerate.min(commitment.localCommit.spec.commitTxFeerate) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => requestedFeerate - } // NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can // always use CPFP to speed up confirmation if necessary. - val closingFeerates = ClosingFeerates(preferredFeerate, preferredFeerate.min(ConfirmationPriority.Slow.getFeerate(feerates)), preferredFeerate * 2) + val closingFeerates = ClosingFeerates(requestedFeerate, requestedFeerate.min(ConfirmationPriority.Slow.getFeerate(feerates)), requestedFeerate * 2) firstClosingFee(channelKeys, commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates) } @@ -778,7 +762,7 @@ object Helpers { dummyClosingTxs.preferred_opt match { case Some(dummyTx) => commitment.commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val dummyPubkey = commitment.remoteFundingPubKey val dummySig = IndividualSignature(Transactions.PlaceHolderSig) val dummySignedTx = dummyTx.aggregateSigs(dummyPubkey, dummyPubkey, dummySig, dummySig) @@ -816,7 +800,7 @@ object Helpers { closingTxs.remoteOnly_opt.flatMap(tx => localSig(tx, localNonces.remoteOnly)).map(ClosingCompleteTlv.CloseeOutputOnlyPartialSignature(_)), ).flatten[ClosingCompleteTlv]) } - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => TlvStream(Set( + case _: AnchorOutputsCommitmentFormat => TlvStream(Set( closingTxs.localAndRemote_opt.map(tx => ClosingTlv.CloserAndCloseeOutputs(tx.sign(localFundingKey, commitment.remoteFundingPubKey).sig)), closingTxs.localOnly_opt.map(tx => ClosingTlv.CloserOutputOnly(tx.sign(localFundingKey, commitment.remoteFundingPubKey).sig)), closingTxs.remoteOnly_opt.map(tx => ClosingTlv.CloseeOutputOnly(tx.sign(localFundingKey, commitment.remoteFundingPubKey).sig)), @@ -870,7 +854,7 @@ object Helpers { case None => Left(MissingCloseSignature(commitment.channelId)) } } - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => (closingTxs.localAndRemote_opt, closingTxs.localOnly_opt) match { case (Some(_), Some(_)) if closingComplete.closerAndCloseeOutputsSig_opt.isEmpty && closingComplete.closeeOutputOnlySig_opt.isEmpty => return Left(MissingCloseSignature(commitment.channelId)) case (Some(_), None) if closingComplete.closerAndCloseeOutputsSig_opt.isEmpty => return Left(MissingCloseSignature(commitment.channelId)) @@ -1186,7 +1170,7 @@ object Helpers { object RemoteClose { /** Transactions spending outputs of a remote commitment transaction. */ - case class SecondStageTransactions(mainTx_opt: Option[ClaimRemoteCommitMainOutputTx], anchorTx_opt: Option[ClaimRemoteAnchorTx], htlcTxs: Seq[ClaimHtlcTx]) + case class SecondStageTransactions(mainTx_opt: Option[ClaimRemoteDelayedOutputTx], anchorTx_opt: Option[ClaimRemoteAnchorTx], htlcTxs: Seq[ClaimHtlcTx]) /** Claim all the outputs that belong to us in the remote commitment transaction (which can be either their current or next commitment). */ def claimCommitTxOutputs(channelKeys: ChannelKeys, commitment: FullCommitment, remoteCommit: RemoteCommit, commitTx: Transaction, closingFeerate: FeeratePerKw, finalScriptPubKey: ByteVector, spendAnchorWithoutHtlcs: Boolean)(implicit log: LoggingAdapter): (RemoteCommitPublished, SecondStageTransactions) = { @@ -1223,11 +1207,8 @@ object Helpers { } /** Claim our main output from the remote commitment transaction, if available. */ - def claimMainOutput(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, dustLimit: Satoshi, commitmentFormat: CommitmentFormat, feerate: FeeratePerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = { + def claimMainOutput(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, dustLimit: Satoshi, commitmentFormat: CommitmentFormat, feerate: FeeratePerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteDelayedOutputTx] = { commitmentFormat match { - case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) - } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerate, commitmentFormat) } @@ -1348,7 +1329,7 @@ object Helpers { object RevokedClose { /** Transactions spending outputs of a revoked remote commitment transactions. */ - case class SecondStageTransactions(mainTx_opt: Option[ClaimRemoteCommitMainOutputTx], mainPenaltyTx_opt: Option[MainPenaltyTx], htlcPenaltyTxs: Seq[HtlcPenaltyTx]) + case class SecondStageTransactions(mainTx_opt: Option[ClaimRemoteDelayedOutputTx], mainPenaltyTx_opt: Option[MainPenaltyTx], htlcPenaltyTxs: Seq[HtlcPenaltyTx]) /** Transactions spending outputs of confirmed remote HTLC transactions. */ case class ThirdStageTransactions(htlcDelayedPenaltyTxs: Seq[ClaimHtlcDelayedOutputPenaltyTx]) @@ -1368,7 +1349,7 @@ object Helpers { // a valid tx will always have at least one input, but this ensures we don't throw in tests val sequence = commitTx.txIn.headOption.map(_.sequence).getOrElse(0L) val obscuredTxNumber = Transactions.decodeTxNumber(sequence, commitTx.lockTime) - val localPaymentPoint = params.localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint) + val localPaymentPoint = channelKeys.paymentBasePoint // this tx has been published by remote, so we need to invert local/remote params val txNumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !params.localParams.isChannelOpener, params.remoteParams.paymentBasepoint, localPaymentPoint) if (txNumber > 0xffffffffffffL) { @@ -1387,7 +1368,7 @@ object Helpers { def claimCommitTxOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, db: ChannelsDb, dustLimit: Satoshi, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, SecondStageTransactions) = { log.warning("a revoked commit has been published with commitmentNumber={}", commitmentNumber) - val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) + val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val feerateMain = onChainFeeConf.getClosingFeerate(feerates, maxClosingFeerateOverride_opt = None) @@ -1396,9 +1377,6 @@ object Helpers { // First we will claim our main output right away. val mainTx_opt = commitmentFormat match { - case DefaultCommitmentFormat => withTxGenerationLog("remote-main") { - ClaimP2WPKHOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) - } case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") { ClaimRemoteDelayedOutputTx.createUnsignedTx(commitKeys, commitTx, dustLimit, finalScriptPubKey, feerateMain, commitmentFormat) } @@ -1450,7 +1428,7 @@ object Helpers { if (spendsHtlcOutput) { getRemotePerCommitmentSecret(channelParams, channelKeys, remotePerCommitmentSecrets, revokedCommitPublished.commitTx).map { case (_, remotePerCommitmentSecret) => - val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val penaltyTxs = claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey) val revokedCommitPublished1 = revokedCommitPublished.copy(htlcDelayedOutputs = revokedCommitPublished.htlcDelayedOutputs ++ penaltyTxs.map(_.input.outPoint)) @@ -1474,7 +1452,7 @@ object Helpers { * Claim the outputs of all 2nd-stage HTLC transactions that have been confirmed. */ def claimHtlcTxsOutputs(channelParams: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentSecret: PrivateKey, toSelfDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat, revokedCommitPublished: RevokedCommitPublished, dustLimit: Satoshi, feerates: FeeratesPerKw, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): ThirdStageTransactions = { - val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey, commitmentFormat) + val commitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentSecret.publicKey) val revocationKey = channelKeys.revocationKey(remotePerCommitmentSecret) val confirmedHtlcTxs = revokedCommitPublished.htlcOutputs.flatMap(htlcOutput => revokedCommitPublished.irrevocablySpent.get(htlcOutput)) val penaltyTxs = confirmedHtlcTxs.flatMap(htlcTx => claimHtlcTxOutputs(commitmentKeys, revocationKey, toSelfDelay, commitmentFormat, htlcTx, dustLimit, feerates, finalScriptPubKey)) @@ -1702,20 +1680,11 @@ object Helpers { } /** Returns the amount we've successfully claimed from a force-closed channel. */ - def closingBalance(channelParams: ChannelParams, commitmentFormat: CommitmentFormat, closingScript: ByteVector, commit: CommitPublished): Satoshi = { - val toLocal = commit.localOutput_opt match { - case Some(o) if o.index < commit.commitTx.txOut.size => commit.commitTx.txOut(o.index.toInt).amount - case _ => 0 sat - } - val toClosingScript = commit.irrevocablySpent.values.flatMap(_.txOut) + def closingBalance(closingScript: ByteVector, commit: CommitPublished): Satoshi = { + commit.irrevocablySpent.values.flatMap(_.txOut) .filter(_.publicKeyScript == closingScript) .map(_.amount) .sum - commitmentFormat match { - case DefaultCommitmentFormat if channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty => toLocal + toClosingScript - case DefaultCommitmentFormat => toClosingScript - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => toClosingScript - } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index bef8a0c3aa..6d92187dd2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -109,8 +109,7 @@ object Channel { remoteRbfLimits: RemoteRbfLimits, quiescenceTimeout: FiniteDuration, balanceThresholds: Seq[BalanceThreshold], - minTimeBetweenUpdates: FiniteDuration, - acceptIncomingStaticRemoteKeyChannels: Boolean) { + minTimeBetweenUpdates: FiniteDuration) { require(0 <= maxHtlcValueInFlightPercent && maxHtlcValueInFlightPercent <= 100, "max-htlc-value-in-flight-percent must be between 0 and 100") require(balanceThresholds.sortBy(_.available) == balanceThresholds, "channel-update.balance-thresholds must be sorted by available-sat") @@ -147,7 +146,7 @@ object Channel { } } - def props(nodeParams: NodeParams, channelKeys: ChannelKeys, wallet: OnChainChannelFunder with OnChainPubkeyCache, remoteNodeId: PublicKey, blockchain: typed.ActorRef[ZmqWatcher.Command], relayer: ActorRef, txPublisherFactory: TxPublisherFactory): Props = + def props(nodeParams: NodeParams, channelKeys: ChannelKeys, wallet: OnChainChannelFunder with OnChainAddressCache, remoteNodeId: PublicKey, blockchain: typed.ActorRef[ZmqWatcher.Command], relayer: ActorRef, txPublisherFactory: TxPublisherFactory): Props = Props(new Channel(nodeParams, channelKeys, wallet, remoteNodeId, blockchain, relayer, txPublisherFactory)) // https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#requirements @@ -212,7 +211,7 @@ object Channel { } -class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wallet: OnChainChannelFunder with OnChainPubkeyCache, val remoteNodeId: PublicKey, val blockchain: typed.ActorRef[ZmqWatcher.Command], val relayer: ActorRef, val txPublisherFactory: Channel.TxPublisherFactory)(implicit val ec: ExecutionContext = ExecutionContext.Implicits.global) +class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wallet: OnChainChannelFunder with OnChainAddressCache, val remoteNodeId: PublicKey, val blockchain: typed.ActorRef[ZmqWatcher.Command], val relayer: ActorRef, val txPublisherFactory: Channel.TxPublisherFactory)(implicit val ec: ExecutionContext = ExecutionContext.Implicits.global) extends FSM[ChannelState, ChannelData] with FSMDiagnosticActorLogging[ChannelState, ChannelData] with ChannelOpenSingleFunded @@ -317,11 +316,6 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(INPUT_RESTORED(data), _) => log.debug("restoring channel") - data match { - case data: ChannelDataWithCommitments if data.commitments.active.exists(_.commitmentFormat == DefaultCommitmentFormat) => - log.warning("channel is not using anchor outputs: please close it and re-open an anchor output channel before updating to the next version of eclair") - case _ => () - } context.system.eventStream.publish(ChannelRestored(self, data.channelId, peer, remoteNodeId, data)) txPublisher ! SetChannelId(remoteNodeId, data.channelId) // We watch all unconfirmed funding txs, whatever our state is. @@ -536,7 +530,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall handleAddHtlcCommandError(c, error, Some(d.channelUpdate)) case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => - d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match { + d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.onChainFeeConf) match { case Right((commitments1, add)) => if (c.commit) self ! CMD_SIGN() context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.aliases, commitments1, d.lastAnnouncement_opt)) @@ -548,7 +542,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall } case Event(add: UpdateAddHtlc, d: DATA_NORMAL) => - d.commitments.receiveAdd(add, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match { + d.commitments.receiveAdd(add) match { case Right(commitments1) => stay() using d.copy(commitments = commitments1) case Left(cause) => handleLocalError(cause, d, Some(add)) } @@ -670,7 +664,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case (s: SpliceStatus.SpliceInProgress, sig: CommitSig) => log.debug("received their commit_sig, deferring message") stay() using d.copy(spliceStatus = s.copy(remoteCommitSig = Some(sig))) - case (SpliceStatus.SpliceAborted, sig: CommitSig) => + case (SpliceStatus.SpliceAborted, _: CommitSig) => log.warning("received commit_sig after sending tx_abort, they probably sent it before receiving our tx_abort, ignoring...") stay() case (SpliceStatus.SpliceWaitingForSigs(signingSession), sig: CommitSig) => @@ -866,7 +860,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(ProcessCurrentBlockHeight(c), d: DATA_NORMAL) => handleNewBlock(c, d) - case Event(c: CurrentFeerates.BitcoinCore, d: DATA_NORMAL) => handleCurrentFeerate(c, d) + case Event(c: CurrentFeerates.BitcoinCore, d: DATA_NORMAL) => handleCurrentFeerate(d) case Event(_: ChannelReady, d: DATA_NORMAL) => // After a reconnection, if the channel hasn't been used yet, our peer cannot be sure we received their channel_ready @@ -1114,10 +1108,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // commitment format and will simply apply the previous commitment format. val nextCommitmentFormat = msg.channelType_opt match { case Some(ChannelTypes.SimpleTaprootChannelsPhoenix) if parentCommitment.commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat => - log.info(s"accepting upgrade to SimpleTaprootChannelsPhoenix during splice from commitment format ${parentCommitment.commitmentFormat}") + log.info("accepting upgrade to {} during splice from commitment format {}", ChannelTypes.SimpleTaprootChannelsPhoenix, parentCommitment.commitmentFormat) PhoenixSimpleTaprootChannelCommitmentFormat case Some(channelType) => - log.info(s"rejecting upgrade to $channelType during splice from commitment format ${parentCommitment.commitmentFormat}") + log.info("rejecting upgrade to {} during splice from commitment format {}", channelType, parentCommitment.commitmentFormat) parentCommitment.commitmentFormat case _ => parentCommitment.commitmentFormat @@ -1768,7 +1762,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(ProcessCurrentBlockHeight(c), d: DATA_SHUTDOWN) => handleNewBlock(c, d) - case Event(c: CurrentFeerates.BitcoinCore, d: DATA_SHUTDOWN) => handleCurrentFeerate(c, d) + case Event(c: CurrentFeerates.BitcoinCore, d: DATA_SHUTDOWN) => handleCurrentFeerate(d) case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) => val useSimpleClose = Features.canUseFeature(d.commitments.localChannelParams.initFeatures, d.commitments.remoteChannelParams.initFeatures, Features.SimpleClose) @@ -1989,11 +1983,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall // We are already watching the corresponding outputs: no need to set additional watches. d.localCommitPublished.foreach(lcp => { val commitKeys = commitment.localKeys(channelKeys) - Closing.LocalClose.claimHtlcsWithPreimage(channelKeys, commitKeys, commitment, c.r).foreach(htlcTx => commitment.commitmentFormat match { - case Transactions.DefaultCommitmentFormat => - txPublisher ! TxPublisher.PublishFinalTx(htlcTx, Some(lcp.commitTx.txid)) - case _: Transactions.AnchorOutputsCommitmentFormat | _: Transactions.SimpleTaprootChannelCommitmentFormat => - txPublisher ! TxPublisher.PublishReplaceableTx(htlcTx, lcp.commitTx, commitment, Closing.confirmationTarget(htlcTx)) + Closing.LocalClose.claimHtlcsWithPreimage(channelKeys, commitKeys, commitment, c.r).foreach(htlcTx => { + txPublisher ! TxPublisher.PublishReplaceableTx(htlcTx, lcp.commitTx, commitment, Closing.confirmationTarget(htlcTx)) }) }) d.remoteCommitPublished.foreach(rcp => { @@ -2525,7 +2516,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d) - case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d) + case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => stay() case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => handleAddDisconnected(c, d) @@ -2830,7 +2821,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d) - case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d) + case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => stay() case Event(getTxResponse: GetTxWithMetaResponse, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if getTxResponse.txid == d.commitments.latest.fundingTxId => handleGetFundingTx(getTxResponse, d.waitingSince, d.fundingTx_opt) @@ -3224,50 +3215,15 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall 888 888 d88P 888 888 Y888 8888888P" 88888888 8888888888 888 T88b "Y8888P" */ - private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = { + private def handleCurrentFeerate(d: ChannelDataWithCommitments) = { val commitments = d.commitments.latest val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat) val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate val shouldUpdateFee = d.commitments.localChannelParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) - val shouldClose = !d.commitments.localChannelParams.paysCommitTxFees && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && - d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk if (shouldUpdateFee) { self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) - stay() - } else if (shouldClose) { - handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = commitments.localCommit.spec.commitTxFeerate), d, Some(c)) - } else { - stay() - } - } - - /** - * This is used to check for the commitment fees when the channel is not operational but we have something at stake - * - * @param c the new feerates - * @param d the channel commtiments - * @return - */ - private def handleCurrentFeerateDisconnected(c: CurrentFeerates, d: ChannelDataWithCommitments) = { - val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.latest.commitmentFormat) - val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate - // if the network fees are too high we risk to not be able to confirm our current commitment - val shouldClose = networkFeeratePerKw > currentFeeratePerKw && - nodeParams.onChainFeeConf.feerateToleranceFor(d.commitments.remoteNodeId).isProposedFeerateTooLow(d.commitments.latest.commitmentFormat, networkFeeratePerKw, currentFeeratePerKw) && - d.commitments.hasPendingOrProposedHtlcs // we close only if we have HTLCs potentially at risk - if (shouldClose) { - if (nodeParams.onChainFeeConf.closeOnOfflineMismatch) { - log.warning(s"closing OFFLINE channel due to fee mismatch: currentFeeratePerKw=$currentFeeratePerKw networkFeeratePerKw=$networkFeeratePerKw") - handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = currentFeeratePerKw, remoteFeeratePerKw = networkFeeratePerKw), d, Some(c)) - } else { - log.warning(s"channel is OFFLINE but its fee mismatch is over the threshold: currentFeeratePerKw=$currentFeeratePerKw networkFeeratePerKw=$networkFeeratePerKw") - stay() - } - } else { - stay() } + stay() } private def handleCommandSuccess(c: channel.Command, newData: ChannelData) = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala index 783bc626b0..586b14a560 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala @@ -127,7 +127,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { lockTime = nodeParams.currentBlockHeight.toLong, fundingPubkey = fundingPubKey, revocationBasepoint = channelKeys.revocationBasePoint, - paymentBasepoint = input.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + paymentBasepoint = channelKeys.paymentBasePoint, delayedPaymentBasepoint = channelKeys.delayedPaymentBasePoint, htlcBasepoint = channelKeys.htlcBasePoint, firstPerCommitmentPoint = channelKeys.commitmentPoint(0), @@ -141,7 +141,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case Event(open: OpenDualFundedChannel, d: DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL) => val localFundingPubkey = channelKeys.fundingKey(fundingTxIndex = 0).publicKey val fundingScript = Transactions.makeFundingScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat).pubkeyScript - Helpers.validateParamsDualFundedNonInitiator(nodeParams, d.init.channelType, open, fundingScript, remoteNodeId, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.init.fundingContribution_opt) match { + Helpers.validateParamsDualFundedNonInitiator(nodeParams, open, fundingScript, remoteNodeId, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.init.fundingContribution_opt) match { case Left(t) => handleLocalError(t, d, Some(open)) case Right((channelFeatures, remoteShutdownScript, willFund_opt)) => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.commitmentFeerate, Some(open.fundingFeerate))) @@ -179,7 +179,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = localFundingPubkey, revocationBasepoint = channelKeys.revocationBasePoint, - paymentBasepoint = d.init.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + paymentBasepoint = channelKeys.paymentBasePoint, delayedPaymentBasepoint = channelKeys.delayedPaymentBasePoint, htlcBasepoint = channelKeys.htlcBasePoint, firstPerCommitmentPoint = channelKeys.commitmentPoint(0), @@ -224,7 +224,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { when(WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL)(handleExceptions { case Event(accept: AcceptDualFundedChannel, d: DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL) => - Helpers.validateParamsDualFundedInitiator(nodeParams, remoteNodeId, d.init.channelType, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.lastSent, accept) match { + Helpers.validateParamsDualFundedInitiator(nodeParams, remoteNodeId, d.init.localChannelParams.initFeatures, d.init.remoteInit.features, d.lastSent, accept) match { case Left(t) => d.init.replyTo ! OpenChannelResponse.Rejected(t.getMessage) handleLocalError(t, d, Some(accept)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index f0510ce8a2..1f10caa287 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentK import fr.acinq.eclair.crypto.{NonceGenerator, ShaChain} import fr.acinq.eclair.io.Peer.OpenChannelResponse import fr.acinq.eclair.transactions.Transactions -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.wire.protocol.{AcceptChannel, AcceptChannelTlv, AnnouncementSignatures, ChannelReady, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, OpenChannelTlv, TlvStream} import fr.acinq.eclair.{MilliSatoshiLong, randomKey, toLongId} import scodec.bits.ByteVector @@ -79,7 +79,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val localShutdownScript = input.localChannelParams.upfrontShutdownScript_opt.getOrElse(ByteVector.empty) val localNonce = input.channelType.commitmentFormat match { case _: SimpleTaprootChannelCommitmentFormat => Some(NonceGenerator.verificationNonce(NonceGenerator.dummyFundingTxId, fundingKey, NonceGenerator.dummyRemoteFundingPubKey, 0).publicNonce) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => None + case _: AnchorOutputsCommitmentFormat => None } val open = OpenChannel( chainHash = nodeParams.chainHash, @@ -95,7 +95,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { maxAcceptedHtlcs = input.proposedCommitParams.localMaxAcceptedHtlcs, fundingPubkey = fundingKey.publicKey, revocationBasepoint = channelKeys.revocationBasePoint, - paymentBasepoint = input.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + paymentBasepoint = channelKeys.paymentBasePoint, delayedPaymentBasepoint = channelKeys.delayedPaymentBasePoint, htlcBasepoint = channelKeys.htlcBasePoint, firstPerCommitmentPoint = channelKeys.commitmentPoint(0), @@ -112,7 +112,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions { case Event(open: OpenChannel, d: DATA_WAIT_FOR_OPEN_CHANNEL) => - Helpers.validateParamsSingleFundedFundee(nodeParams, d.initFundee.channelType, d.initFundee.localChannelParams.initFeatures, open, remoteNodeId, d.initFundee.remoteInit.features) match { + Helpers.validateParamsSingleFundedFundee(nodeParams, d.initFundee.localChannelParams.initFeatures, open, remoteNodeId, d.initFundee.remoteInit.features) match { case Left(t) => handleLocalError(t, d, Some(open)) case Right((channelFeatures, remoteShutdownScript)) => context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isOpener = false, open.temporaryChannelId, open.feeratePerKw, None)) @@ -134,7 +134,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val localShutdownScript = d.initFundee.localChannelParams.upfrontShutdownScript_opt.getOrElse(ByteVector.empty) val localNonce = d.initFundee.channelType.commitmentFormat match { case _: SimpleTaprootChannelCommitmentFormat => Some(NonceGenerator.verificationNonce(NonceGenerator.dummyFundingTxId, fundingKey, NonceGenerator.dummyRemoteFundingPubKey, 0).publicNonce) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => None + case _: AnchorOutputsCommitmentFormat => None } val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, dustLimitSatoshis = localCommitParams.dustLimit, @@ -146,7 +146,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { maxAcceptedHtlcs = localCommitParams.maxAcceptedHtlcs, fundingPubkey = fundingKey.publicKey, revocationBasepoint = channelKeys.revocationBasePoint, - paymentBasepoint = d.initFundee.localChannelParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + paymentBasepoint = channelKeys.paymentBasePoint, delayedPaymentBasepoint = channelKeys.delayedPaymentBasePoint, htlcBasepoint = channelKeys.htlcBasePoint, firstPerCommitmentPoint = channelKeys.commitmentPoint(0), @@ -168,7 +168,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { case Event(accept: AcceptChannel, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => - Helpers.validateParamsSingleFundedFunder(nodeParams, d.initFunder.channelType, d.initFunder.localChannelParams.initFeatures, d.initFunder.remoteInit.features, d.lastSent, accept) match { + Helpers.validateParamsSingleFundedFunder(nodeParams, d.initFunder.localChannelParams.initFeatures, d.initFunder.remoteInit.features, d.lastSent, accept) match { case Left(t) => d.initFunder.replyTo ! OpenChannelResponse.Rejected(t.getMessage) handleLocalError(t, d, Some(accept)) @@ -215,8 +215,8 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val temporaryChannelId = d.channelParams.channelId // let's create the first commitment tx that spends the yet uncommitted funding tx val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = d.fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = d.pushAmount, remotePushAmount = 0 msat, d.commitTxFeerate, d.commitmentFormat, fundingTx.txid, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => @@ -233,7 +233,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { } case None => Left(MissingCommitNonce(d.channelId, NonceGenerator.dummyFundingTxId, commitmentNumber = 0)) } - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => Right(remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey)) + case _: AnchorOutputsCommitmentFormat => Right(remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey)) } localSigOfRemoteTx match { case Left(f) => handleLocalError(f, d, None) @@ -275,8 +275,8 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { case Event(fc@FundingCreated(_, fundingTxId, fundingTxOutputIndex, _, _), d: DATA_WAIT_FOR_FUNDING_CREATED) => val temporaryChannelId = d.channelParams.channelId val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0) - val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0, d.commitmentFormat) - val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint, d.commitmentFormat) + val localCommitmentKeys = LocalCommitmentKeys(d.channelParams, channelKeys, localCommitIndex = 0) + val remoteCommitmentKeys = RemoteCommitmentKeys(d.channelParams, channelKeys, d.remoteFirstPerCommitmentPoint) Funding.makeFirstCommitTxs(d.channelParams, d.localCommitParams, d.remoteCommitParams, localFundingAmount = 0 sat, remoteFundingAmount = d.fundingAmount, localPushAmount = 0 msat, remotePushAmount = d.pushAmount, d.commitTxFeerate, d.commitmentFormat, fundingTxId, fundingTxOutputIndex, fundingKey, d.remoteFundingPubKey, localCommitmentKeys, remoteCommitmentKeys) match { case Left(ex) => handleLocalError(ex, d, Some(fc)) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => @@ -303,7 +303,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { } case None => Left(MissingCommitNonce(channelId, NonceGenerator.dummyFundingTxId, commitmentNumber = 0)) } - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => Right(remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey)) + case _: AnchorOutputsCommitmentFormat => Right(remoteCommitTx.sign(fundingKey, d.remoteFundingPubKey)) } localSigOfRemoteTx match { case Left(f) => handleLocalError(f, d, Some(fc)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala index 0cdfb2a4fa..0ce656c82e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonFundingHandlers.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel.{BroadcastChannelUpdate, PeriodicRefresh, REFRESH_CHANNEL_UPDATE_INTERVAL} import fr.acinq.eclair.crypto.NonceGenerator import fr.acinq.eclair.db.RevokedHtlcInfoCleaner -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{RealShortChannelId, ShortChannelId} @@ -132,7 +132,7 @@ trait CommonFundingHandlers extends CommonHandlers { val localFundingKey = channelKeys.fundingKey(fundingTxIndex = 0) val nextLocalNonce = NonceGenerator.verificationNonce(commitments.latest.fundingTxId, localFundingKey, commitments.latest.remoteFundingPubKey, 1) ChannelReady(params.channelId, nextPerCommitmentPoint, aliases.localAlias, nextLocalNonce.publicNonce) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => ChannelReady(params.channelId, nextPerCommitmentPoint, aliases.localAlias) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonHandlers.scala index 56ff91ab41..451150db44 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/CommonHandlers.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.NonceGenerator import fr.acinq.eclair.db.PendingCommandsDb import fr.acinq.eclair.io.Peer -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.wire.protocol.{ClosingComplete, HtlcSettlementMessage, LightningMessage, Shutdown, UpdateMessage} import scodec.bits.ByteVector @@ -142,7 +142,7 @@ trait CommonHandlers { val localCloseeNonce = NonceGenerator.signingNonce(localFundingPubKey, commitments.latest.remoteFundingPubKey, commitments.latest.fundingTxId) localCloseeNonce_opt = Some(localCloseeNonce) Shutdown(commitments.channelId, finalScriptPubKey, localCloseeNonce.publicNonce) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => Shutdown(commitments.channelId, finalScriptPubKey) } } 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 7b0869eccd..6807f366ff 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 @@ -240,10 +240,7 @@ trait ErrorHandlers extends CommonHandlers { case _ => None } val publishMainDelayedTx_opt = txs.mainDelayedTx_opt.map(tx => PublishFinalTx(tx, None)) - val publishHtlcTxs = txs.htlcTxs.map(htlcTx => commitment.commitmentFormat match { - case DefaultCommitmentFormat => PublishFinalTx(htlcTx, Some(lcp.commitTx.txid)) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => PublishReplaceableTx(htlcTx, lcp.commitTx, commitment, Closing.confirmationTarget(htlcTx)) - }) + val publishHtlcTxs = txs.htlcTxs.map(htlcTx => PublishReplaceableTx(htlcTx, lcp.commitTx, commitment, Closing.confirmationTarget(htlcTx))) val publishQueue = Seq(publishCommitTx) ++ publishAnchorTx_opt ++ publishMainDelayedTx_opt ++ publishHtlcTxs publishIfNeeded(publishQueue, lcp.irrevocablySpent) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala index 7b841b1859..8eb18a7a2c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala @@ -894,8 +894,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon val fundingTx = completeTx.buildUnsignedTx() val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingPubkeyScript) val liquidityFee = fundingParams.liquidityFees(liquidityPurchase_opt) - val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex, fundingParams.commitmentFormat) - val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint, fundingParams.commitmentFormat) + val localCommitmentKeys = LocalCommitmentKeys(channelParams, channelKeys, purpose.localCommitIndex) + val remoteCommitmentKeys = RemoteCommitmentKeys(channelParams, channelKeys, purpose.remotePerCommitmentPoint) Funding.makeCommitTxs(channelParams, localCommitParams, remoteCommitParams, fundingAmount = fundingParams.fundingAmount, toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount - liquidityFee, @@ -1218,7 +1218,7 @@ object InteractiveTxSigningSession { localCommit match { case Left(unsignedLocalCommit) => val fundingKey = localFundingKey(channelKeys) - val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex, fundingParams.commitmentFormat) + val commitKeys = LocalCommitmentKeys(channelParams, channelKeys, localCommitIndex) val fundingOutput = commitInput(fundingKey) LocalCommit.fromCommitSig(channelParams, localCommitParams, commitKeys, fundingTx.txId, fundingKey, fundingParams.remoteFundingPubKey, fundingOutput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, fundingParams.commitmentFormat).map { signedLocalCommit => if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala index 540650ce38..dc07cfaa07 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/CommitmentKeys.scala @@ -17,9 +17,7 @@ package fr.acinq.eclair.crypto.keymanager import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.Features import fr.acinq.eclair.channel.ChannelParams -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} /** * Created by t-bast on 10/04/2025. @@ -67,16 +65,12 @@ case class LocalCommitmentKeys(ourDelayedPaymentKey: PrivateKey, } object LocalCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long, commitmentFormat: CommitmentFormat): LocalCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, localCommitIndex: Long): LocalCommitmentKeys = { val localPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex) LocalCommitmentKeys( ourDelayedPaymentKey = channelKeys.delayedPaymentKey(localPerCommitmentPoint), - theirPaymentPublicKey = commitmentFormat match { - case DefaultCommitmentFormat if params.localParams.walletStaticPaymentBasepoint.nonEmpty => params.remoteParams.paymentBasepoint - case DefaultCommitmentFormat => ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.paymentBasepoint, localPerCommitmentPoint) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => params.remoteParams.paymentBasepoint - }, - ourPaymentBasePoint = params.localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + theirPaymentPublicKey = params.remoteParams.paymentBasepoint, + ourPaymentBasePoint = channelKeys.paymentBasePoint, ourHtlcKey = channelKeys.htlcKey(localPerCommitmentPoint), theirHtlcPublicKey = ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.htlcBasepoint, localPerCommitmentPoint), revocationPublicKey = ChannelKeys.revocationPublicKey(params.remoteParams.revocationBasepoint, localPerCommitmentPoint) @@ -92,23 +86,16 @@ object LocalCommitmentKeys { * to a p2wpkh address created by our bitcoin node. We thus don't need the private key, as the output can immediately * be spent by our bitcoin node (no need for 2nd-stage transactions to send it back to our wallet). */ -case class RemoteCommitmentKeys(ourPaymentKey: Either[PublicKey, PrivateKey], +case class RemoteCommitmentKeys(ourPaymentKey: PrivateKey, theirDelayedPaymentPublicKey: PublicKey, ourPaymentBasePoint: PublicKey, ourHtlcKey: PrivateKey, theirHtlcPublicKey: PublicKey, revocationPublicKey: PublicKey) { - val ourPaymentPublicKey: PublicKey = ourPaymentKey match { - case Left(publicKey) => publicKey - case Right(privateKey) => privateKey.publicKey - } // Since this is the remote commitment, local is them and remote is us. val publicKeys: CommitmentPublicKeys = CommitmentPublicKeys( localDelayedPaymentPublicKey = theirDelayedPaymentPublicKey, - remotePaymentPublicKey = ourPaymentKey match { - case Left(publicKey) => publicKey - case Right(privateKey) => privateKey.publicKey - }, + remotePaymentPublicKey = ourPaymentKey.publicKey, localHtlcPublicKey = theirHtlcPublicKey, remoteHtlcPublicKey = ourHtlcKey.publicKey, revocationPublicKey = revocationPublicKey @@ -116,18 +103,11 @@ case class RemoteCommitmentKeys(ourPaymentKey: Either[PublicKey, PrivateKey], } object RemoteCommitmentKeys { - def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey, commitmentFormat: CommitmentFormat): RemoteCommitmentKeys = { + def apply(params: ChannelParams, channelKeys: ChannelKeys, remotePerCommitmentPoint: PublicKey): RemoteCommitmentKeys = { RemoteCommitmentKeys( - ourPaymentKey = params.localParams.walletStaticPaymentBasepoint match { - case Some(walletPublicKey) => Left(walletPublicKey) - case None => commitmentFormat match { - // Note that if we're using option_static_remotekey, a walletStaticPaymentBasepoint will be provided. - case DefaultCommitmentFormat => Right(channelKeys.paymentKey(remotePerCommitmentPoint)) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Right(channelKeys.paymentBaseSecret) - } - }, + ourPaymentKey = channelKeys.paymentBaseSecret, theirDelayedPaymentPublicKey = ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint), - ourPaymentBasePoint = params.localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + ourPaymentBasePoint = channelKeys.paymentBasePoint, ourHtlcKey = channelKeys.htlcKey(remotePerCommitmentPoint), theirHtlcPublicKey = ChannelKeys.remotePerCommitmentPublicKey(params.remoteParams.htlcBasepoint, remotePerCommitmentPoint), revocationPublicKey = ChannelKeys.revocationPublicKey(channelKeys.revocationBasePoint, remotePerCommitmentPoint) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala index ba4d2048de..8c50e398ee 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala @@ -24,7 +24,7 @@ import akka.actor.typed.{ActorRef, Behavior} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Script} import fr.acinq.eclair.Features.Wumbo -import fr.acinq.eclair.blockchain.OnChainPubkeyCache +import fr.acinq.eclair.blockchain.OnChainAddressCache import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.io.Peer.{OpenChannelResponse, SpawnChannelNonInitiator} @@ -70,14 +70,14 @@ object OpenChannelInterceptor { private case object PluginTimeout extends QueryPluginCommands // @formatter:on - def apply(peer: ActorRef[Any], nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainPubkeyCache, pendingChannelsRateLimiter: ActorRef[PendingChannelsRateLimiter.Command], pluginTimeout: FiniteDuration = 1 minute): Behavior[Command] = + def apply(peer: ActorRef[Any], nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainAddressCache, pendingChannelsRateLimiter: ActorRef[PendingChannelsRateLimiter.Command], pluginTimeout: FiniteDuration = 1 minute): Behavior[Command] = Behaviors.setup { context => Behaviors.withMdc(Logs.mdc(remoteNodeId_opt = Some(remoteNodeId))) { new OpenChannelInterceptor(peer, pendingChannelsRateLimiter, pluginTimeout, nodeParams, wallet, context).waitForRequest() } } - def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { + def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { LocalChannelParams( nodeParams.nodeId, nodeParams.channelKeyManager.newFundingKeyPath(isChannelOpener), @@ -85,7 +85,6 @@ object OpenChannelInterceptor { isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, upfrontShutdownScript_opt = upfrontShutdownScript_opt, - walletStaticPaymentBasepoint = walletStaticPaymentBasepoint_opt, initFeatures = initFeatures ) } @@ -96,7 +95,7 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], pendingChannelsRateLimiter: ActorRef[PendingChannelsRateLimiter.Command], pluginTimeout: FiniteDuration, nodeParams: NodeParams, - wallet: OnChainPubkeyCache, + wallet: OnChainAddressCache, context: ActorContext[OpenChannelInterceptor.Command]) { import OpenChannelInterceptor._ @@ -115,30 +114,23 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], } else if (request.open.fundingAmount >= Channel.MAX_FUNDING_WITHOUT_WUMBO && !request.remoteFeatures.hasFeature(Wumbo)) { request.replyTo ! OpenChannelResponse.Rejected(s"fundingAmount=${request.open.fundingAmount} is too big, the remote peer doesn't support wumbo") waitForRequest() + } else if (request.open.channelType_opt.isEmpty) { + request.replyTo ! OpenChannelResponse.Rejected("channel_type must be provided") + waitForRequest() } else { - // If a channel type was provided, we directly use it instead of computing it based on local and remote features. - val channelFlags = request.open.channelFlags_opt.getOrElse(nodeParams.channelConf.channelFlags) - val channelType = request.open.channelType_opt.getOrElse(ChannelTypes.defaultFromFeatures(request.localFeatures, request.remoteFeatures, channelFlags.announceChannel)) + val channelType = request.open.channelType_opt.get val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding) val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript) // If we're purchasing liquidity, we expect our peer to contribute at least the amount we're purchasing, otherwise we'll cancel the funding attempt. val expectedFundingAmount = request.open.fundingAmount + request.open.requestFunding_opt.map(_.requestedAmount).getOrElse(0 sat) - val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount) + val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount) peer ! Peer.SpawnChannelInitiator(request.replyTo, request.open, ChannelConfig.standard, channelType, localParams) waitForRequest() } } private def sanityCheckNonInitiator(request: OpenChannelNonInitiator): Behavior[Command] = { - validateRemoteChannelType(request.temporaryChannelId, request.channelFlags, request.channelType_opt, request.localFeatures, request.remoteFeatures) match { - case Right(_: ChannelTypes.Standard) => - context.log.warn("rejecting incoming channel: anchor outputs must be used for new channels") - sendFailure("rejecting incoming channel: anchor outputs must be used for new channels", request) - waitForRequest() - case Right(_: ChannelTypes.StaticRemoteKey) if !nodeParams.channelConf.acceptIncomingStaticRemoteKeyChannels => - context.log.warn("rejecting static_remote_key incoming static_remote_key channels") - sendFailure("rejecting incoming static_remote_key channel: anchor outputs must be used for new channels", request) - waitForRequest() + ChannelTypes.areCompatible(request.temporaryChannelId, request.localFeatures, request.channelType_opt) match { case Right(channelType) => val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding) val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript) @@ -152,7 +144,6 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], nodeParams, request.localFeatures, upfrontShutdownScript, - channelType, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = dualFunded, @@ -280,29 +271,15 @@ private class OpenChannelInterceptor(peer: ActorRef[Any], } } - private def validateRemoteChannelType(temporaryChannelId: ByteVector32, channelFlags: ChannelFlags, remoteChannelType_opt: Option[ChannelType], localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): Either[ChannelException, SupportedChannelType] = { - remoteChannelType_opt match { - // remote explicitly specifies a channel type: we check whether we want to allow it - case Some(remoteChannelType) => ChannelTypes.areCompatible(localFeatures, remoteChannelType) match { - case Some(acceptedChannelType) => Right(acceptedChannelType) - case None => Left(InvalidChannelType(temporaryChannelId, ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, channelFlags.announceChannel), remoteChannelType)) - } - // Bolt 2: if `option_channel_type` is negotiated: MUST set `channel_type` - case None if Features.canUseFeature(localFeatures, remoteFeatures, Features.ChannelType) => Left(MissingChannelType(temporaryChannelId)) - // remote doesn't specify a channel type: we use spec-defined defaults - case None => Right(ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, channelFlags.announceChannel)) - } - } - - private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, channelType: SupportedChannelType, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { + private def createLocalParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript: Boolean, isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi): LocalChannelParams = { makeChannelParams( - nodeParams, initFeatures, + nodeParams, + initFeatures, // Note that if our bitcoin node is configured to use taproot, this will generate a taproot script. // If our peer doesn't support option_shutdown_anysegwit, the channel open will fail. // This is fine: "serious" nodes should support option_shutdown_anysegwit, and if we want to use taproot, we // most likely don't want to open channels with nodes that don't support it. if (upfrontShutdownScript) Some(Script.write(wallet.getReceivePublicKeyScript(renew = true))) else None, - if (channelType.paysDirectlyToWallet) Some(wallet.getP2wpkhPubkey(renew = true)) else None, isChannelOpener = isChannelOpener, paysCommitTxFees = paysCommitTxFees, dualFunded = dualFunded, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index bd9dd6c0bc..555a053002 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -29,7 +29,7 @@ import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates, OnChainChannelFunder, OnChainPubkeyCache} +import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates, OnChainAddressCache, OnChainChannelFunder} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.crypto.Sphinx @@ -61,7 +61,7 @@ import scodec.bits.ByteVector */ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, - wallet: OnChainPubkeyCache, + wallet: OnChainAddressCache, channelFactory: Peer.ChannelFactory, switchboard: ActorRef, register: ActorRef, @@ -1024,12 +1024,12 @@ object Peer { def spawn(context: ActorContext, remoteNodeId: PublicKey, channelKeys: ChannelKeys): ActorRef } - case class SimpleChannelFactory(nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], relayer: ActorRef, wallet: OnChainChannelFunder with OnChainPubkeyCache, txPublisherFactory: Channel.TxPublisherFactory) extends ChannelFactory { + case class SimpleChannelFactory(nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], relayer: ActorRef, wallet: OnChainChannelFunder with OnChainAddressCache, txPublisherFactory: Channel.TxPublisherFactory) extends ChannelFactory { override def spawn(context: ActorContext, remoteNodeId: PublicKey, channelKeys: ChannelKeys): ActorRef = context.actorOf(Channel.props(nodeParams, channelKeys, wallet, remoteNodeId, watcher, relayer, txPublisherFactory)) } - def props(nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainPubkeyCache, channelFactory: ChannelFactory, switchboard: ActorRef, register: ActorRef, router: typed.ActorRef[Router.GetNodeId], pendingChannelsRateLimiter: typed.ActorRef[PendingChannelsRateLimiter.Command]): Props = + def props(nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainAddressCache, channelFactory: ChannelFactory, switchboard: ActorRef, register: ActorRef, router: typed.ActorRef[Router.GetNodeId], pendingChannelsRateLimiter: typed.ActorRef[PendingChannelsRateLimiter.Command]): Props = Props(new Peer(nodeParams, remoteNodeId, wallet, channelFactory, switchboard, register, router, pendingChannelsRateLimiter)) // @formatter:off @@ -1051,7 +1051,7 @@ object Peer { def peerStorage: PeerStorage } case object Nothing extends Data { - override def channels = Map.empty + override def channels: Map[_ <: ChannelId, ActorRef] = Map.empty override def activeChannels: Set[ByteVector32] = Set.empty override def peerStorage: PeerStorage = PeerStorage(None, written = true) } @@ -1112,7 +1112,7 @@ object Peer { * the channel has been opened. */ case class Created(channelId: ByteVector32, fundingTxId: TxId, fee: Satoshi) extends OpenChannelResponse { override def toString = s"created channel $channelId with fundingTxId=$fundingTxId and fees=$fee" } - case class Rejected(reason: String) extends OpenChannelResponse { override def toString = reason } + case class Rejected(reason: String) extends OpenChannelResponse { override def toString: String = reason } case object Cancelled extends OpenChannelResponse { override def toString = "channel creation cancelled" } case object Disconnected extends OpenChannelResponse { override def toString = "disconnected" } case object TimedOut extends OpenChannelResponse { override def toString = "open channel cancelled, took too long" } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala index 6b4adfcc16..c455056c66 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Switchboard.scala @@ -22,7 +22,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, ClassicActorRe import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, OneForOneStrategy, Props, Stash, Status, SupervisorStrategy, typed} import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.eclair.blockchain.OnChainPubkeyCache +import fr.acinq.eclair.blockchain.OnChainAddressCache import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel._ import fr.acinq.eclair.io.IncomingConnectionsTracker.TrackIncomingConnection @@ -180,7 +180,7 @@ object Switchboard { def spawn(context: ActorContext, remoteNodeId: PublicKey): ActorRef } - case class SimplePeerFactory(nodeParams: NodeParams, wallet: OnChainPubkeyCache, channelFactory: Peer.ChannelFactory, pendingChannelsRateLimiter: typed.ActorRef[PendingChannelsRateLimiter.Command], register: ActorRef, router: typed.ActorRef[Router.GetNodeId]) extends PeerFactory { + case class SimplePeerFactory(nodeParams: NodeParams, wallet: OnChainAddressCache, channelFactory: Peer.ChannelFactory, pendingChannelsRateLimiter: typed.ActorRef[PendingChannelsRateLimiter.Command], register: ActorRef, router: typed.ActorRef[Router.GetNodeId]) extends PeerFactory { override def spawn(context: ActorContext, remoteNodeId: PublicKey): ActorRef = context.actorOf(Peer.props(nodeParams, remoteNodeId, wallet, channelFactory, context.self, register, router, pendingChannelsRateLimiter), name = peerActorName(remoteNodeId)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala index eb517841ef..8793de795e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.scalacompat.{LexicographicalOrdering, SatoshiLong, TxOut} import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, PhoenixSimpleTaprootChannelCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ /** @@ -95,7 +95,6 @@ final case class CommitmentSpec(htlcs: Set[DirectedHtlc], commitTxFeerate: Feera def htlcTxFeerate(commitmentFormat: CommitmentFormat): FeeratePerKw = commitmentFormat match { case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat => FeeratePerKw(0 sat) case UnsafeLegacyAnchorOutputsCommitmentFormat | PhoenixSimpleTaprootChannelCommitmentFormat => commitTxFeerate - case DefaultCommitmentFormat => commitTxFeerate } def findIncomingHtlcById(id: Long): Option[IncomingHtlc] = htlcs.collectFirst { case htlc: IncomingHtlc if htlc.add.id == id => htlc } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 874e2ba19a..b64c842ec7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -43,7 +43,6 @@ object Scripts { def der(sig: ByteVector64, sighashType: Int = SIGHASH_ALL): ByteVector = Crypto.compact2der(sig) :+ sighashType.toByte private def htlcRemoteSighash(commitmentFormat: CommitmentFormat): Int = commitmentFormat match { - case DefaultCommitmentFormat => SIGHASH_ALL case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY } @@ -204,7 +203,6 @@ object Scripts { def htlcOffered(keys: CommitmentPublicKeys, paymentHash: ByteVector32, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = { val addCsvDelay = commitmentFormat match { - case DefaultCommitmentFormat => false case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => true } // @formatter:off @@ -263,7 +261,6 @@ object Scripts { def htlcReceived(keys: CommitmentPublicKeys, paymentHash: ByteVector32, lockTime: CltvExpiry, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = { val addCsvDelay = commitmentFormat match { - case DefaultCommitmentFormat => false case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => true } // @formatter:off 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 9608b64a0b..2375315af0 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 @@ -81,7 +81,7 @@ object Transactions { def claimHtlcTimeoutWeight: Int /** Weight of a fully signed [[ClaimLocalDelayedOutputTx]] transaction. */ def toLocalDelayedWeight: Int - /** Weight of a fully signed [[ClaimRemoteCommitMainOutputTx]] transaction. */ + /** Weight of a fully signed [[ClaimRemoteDelayedOutputTx]] transaction. */ def toRemoteWeight: Int /** Weight of a fully signed [[HtlcDelayedTx]] 3rd-stage transaction (spending the output of an [[HtlcTx]]). */ def htlcDelayedWeight: Int @@ -100,30 +100,6 @@ object Transactions { override val fundingInputWeight = 384 } - /** - * Commitment format as defined in the v1.0 specification (https://github.com/lightningnetwork/lightning-rfc/tree/v1.0). - */ - case object DefaultCommitmentFormat extends SegwitV0CommitmentFormat { - override val commitWeight = 724 - override val anchorInputWeight = 0 - override val htlcOutputWeight = 172 - override val htlcTimeoutInputWeight = 449 - override val htlcTimeoutWeight = 663 - override val htlcSuccessInputWeight = 488 - override val htlcSuccessWeight = 703 - override val claimHtlcSuccessWeight = 571 - override val claimHtlcTimeoutWeight = 544 - override val toLocalDelayedWeight = 483 - override val toRemoteWeight = 438 - override val htlcDelayedWeight = 483 - override val mainPenaltyWeight = 484 - override val htlcOfferedPenaltyWeight = 572 - override val htlcReceivedPenaltyWeight = 577 - override val claimHtlcPenaltyWeight = 484 - - override def toString: String = "legacy" - } - /** * Commitment format that adds anchor outputs to the commitment transaction and uses custom sighash flags for HTLC * transactions to allow unilateral fee bumping (https://github.com/lightningnetwork/lightning-rfc/pull/688). @@ -212,11 +188,6 @@ object Transactions { } object RedeemInfo { sealed trait SegwitV0 extends RedeemInfo { def redeemScript: ByteVector } - /** @param publicKey the public key for this p2wpkh input. */ - case class P2wpkh(publicKey: PublicKey) extends SegwitV0 { - override val redeemScript: ByteVector = Script.write(Script.pay2pkh(publicKey)) - override val pubkeyScript: ByteVector = Script.write(Script.pay2wpkh(publicKey)) - } /** @param redeemScript the actual script must be known to redeem pay2wsh inputs. */ case class P2wsh(redeemScript: ByteVector) extends SegwitV0 { override val pubkeyScript: ByteVector = Script.write(Script.pay2wsh(redeemScript)) @@ -491,14 +462,12 @@ object Transactions { * Transactions spending a remote [[CommitTx]] or one of its descendants. * * When a current remote [[CommitTx]] is published: - * - When using the default commitment format, [[ClaimP2WPKHOutputTx]] spends the to-local output of [[CommitTx]] * - When using anchor outputs, [[ClaimRemoteDelayedOutputTx]] spends the to-local output of [[CommitTx]] * - When using anchor outputs, [[ClaimRemoteAnchorTx]] spends the to-local anchor of [[CommitTx]] * - [[ClaimHtlcSuccessTx]] spends received htlc outputs of [[CommitTx]] for which we have the preimage * - [[ClaimHtlcTimeoutTx]] spends sent htlc outputs of [[CommitTx]] after a timeout * * When a revoked remote [[CommitTx]] is published: - * - When using the default commitment format, [[ClaimP2WPKHOutputTx]] spends the to-local output of [[CommitTx]] * - When using anchor outputs, [[ClaimRemoteDelayedOutputTx]] spends the to-local output of [[CommitTx]] * - [[MainPenaltyTx]] spends the remote main output using the revocation secret * - [[HtlcPenaltyTx]] spends all htlc outputs using the revocation secret (and competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] published by the remote node) @@ -538,7 +507,6 @@ object Transactions { // @formatter:on def sighash(txOwner: TxOwner): Int = commitmentFormat match { - case DefaultCommitmentFormat => SIGHASH_ALL case _: AnchorOutputsCommitmentFormat => txOwner match { case TxOwner.Local => SIGHASH_ALL case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY @@ -632,7 +600,7 @@ object Transactions { } def redeemInfo(commitKeys: CommitmentPublicKeys, paymentHash: ByteVector32, htlcExpiry: CltvExpiry, commitmentFormat: CommitmentFormat): RedeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(htlcReceived(commitKeys, paymentHash, htlcExpiry, commitmentFormat)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -686,7 +654,7 @@ object Transactions { } def redeemInfo(commitKeys: CommitmentPublicKeys, paymentHash: ByteVector32, commitmentFormat: CommitmentFormat): RedeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(htlcOffered(commitKeys, paymentHash, commitmentFormat)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -702,7 +670,7 @@ object Transactions { override def sign(): Transaction = { val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toLocalDelay)) val sig = sign(commitKeys.ourDelayedPaymentKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) witnessToLocalDelayedAfterDelay(sig, redeemScript) @@ -736,7 +704,7 @@ object Transactions { } def redeemInfo(commitKeys: CommitmentPublicKeys, toLocalDelay: CltvExpiryDelta, commitmentFormat: CommitmentFormat): RedeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys, toLocalDelay)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -762,7 +730,7 @@ object Transactions { override def sign(): Transaction = { // Note that in/out HTLCs are inverted in the remote commitment: from their point of view it's an offered (outgoing) HTLC. val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(htlcOffered(commitKeys.publicKeys, paymentHash, commitmentFormat)) val sig = sign(commitKeys.ourHtlcKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) witnessClaimHtlcSuccessFromCommitTx(sig, preimage, redeemScript) @@ -822,7 +790,7 @@ object Transactions { override def sign(): Transaction = { // Note that in/out HTLCs are inverted in the remote commitment: from their point of view it's a received (incoming) HTLC. val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(htlcReceived(commitKeys.publicKeys, paymentHash, htlcExpiry, commitmentFormat)) val sig = sign(commitKeys.ourHtlcKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) witnessClaimHtlcTimeoutFromCommitTx(sig, redeemScript) @@ -882,7 +850,7 @@ object Transactions { object ClaimAnchorTx { def redeemInfo(fundingKey: PublicKey, paymentKey: PublicKey, commitmentFormat: CommitmentFormat): RedeemInfo = { commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => RedeemInfo.P2wsh(anchor(fundingKey)) + case _: AnchorOutputsCommitmentFormat => RedeemInfo.P2wsh(anchor(fundingKey)) case _: SimpleTaprootChannelCommitmentFormat => RedeemInfo.TaprootKeyPath(paymentKey.xOnly, Some(Taproot.anchorScriptTree)) } } @@ -905,7 +873,7 @@ object Transactions { override def sign(walletInputs: WalletInputs): Transaction = { val toSign = copy(tx = setWalletInputs(walletInputs)) val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(anchor(fundingKey.publicKey)) val sig = toSign.sign(fundingKey, sighash, RedeemInfo.P2wsh(redeemScript), walletInputs.spentUtxos) witnessAnchor(sig, redeemScript) @@ -942,14 +910,13 @@ object Transactions { override def sign(walletInputs: WalletInputs): Transaction = { val toSign = copy(tx = setWalletInputs(walletInputs)) val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(anchor(fundingKey.publicKey)) val sig = toSign.sign(fundingKey, sighash, RedeemInfo.P2wsh(redeemScript), walletInputs.spentUtxos) witnessAnchor(sig, redeemScript) case _: SimpleTaprootChannelCommitmentFormat => - val Right(anchorKey) = commitKeys.ourPaymentKey - val redeemInfo = RedeemInfo.TaprootKeyPath(anchorKey.xOnlyPublicKey(), Some(Taproot.anchorScriptTree)) - val sig = toSign.sign(anchorKey, sighash, redeemInfo, walletInputs.spentUtxos) + val redeemInfo = RedeemInfo.TaprootKeyPath(commitKeys.ourPaymentKey.xOnlyPublicKey(), Some(Taproot.anchorScriptTree)) + val sig = toSign.sign(commitKeys.ourPaymentKey, sighash, redeemInfo, walletInputs.spentUtxos) Script.witnessKeyPathPay2tr(sig) } toSign.tx.updateWitness(toSign.inputIndex, witness) @@ -971,62 +938,21 @@ object Transactions { } } - sealed trait ClaimRemoteCommitMainOutputTx extends RemoteCommitForceCloseTransaction - - /** This transaction claims our main balance from the remote commitment without any delay, when using the [[DefaultCommitmentFormat]]. */ - case class ClaimP2WPKHOutputTx(commitKeys: RemoteCommitmentKeys, input: InputInfo, tx: Transaction, commitmentFormat: CommitmentFormat) extends ClaimRemoteCommitMainOutputTx { - override val desc: String = "remote-main" - override val expectedWeight: Int = commitmentFormat.toRemoteWeight - - override def sign(): Transaction = { - val Right(paymentKey) = commitKeys.ourPaymentKey - val redeemInfo = RedeemInfo.P2wpkh(paymentKey.publicKey) - val sig = sign(paymentKey, sighash, redeemInfo, extraUtxos = Map.empty) - val witness = Script.witnessPay2wpkh(paymentKey.publicKey, der(sig)) - tx.updateWitness(inputIndex, witness) - } - } - - object ClaimP2WPKHOutputTx { - def createUnsignedTx(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimP2WPKHOutputTx] = { - val redeemInfo = RedeemInfo.P2wpkh(commitKeys.ourPaymentPublicKey) - findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { - case Left(skip) => Left(skip) - case Right(outputIndex) => - commitKeys.ourPaymentKey match { - case Left(_) => Left(OutputAlreadyInWallet) - case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) - val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil, - txOut = TxOut(amount, localFinalScriptPubKey) :: Nil, - lockTime = 0 - ) - val unsignedTx = ClaimP2WPKHOutputTx(commitKeys, input, tx, commitmentFormat) - skipTxIfBelowDust(unsignedTx, localDustLimit) - } - } - } - } - /** This transaction spends our main balance from the remote commitment with a 1-block relative delay. */ - case class ClaimRemoteDelayedOutputTx(commitKeys: RemoteCommitmentKeys, input: InputInfo, tx: Transaction, commitmentFormat: CommitmentFormat) extends ClaimRemoteCommitMainOutputTx { + case class ClaimRemoteDelayedOutputTx(commitKeys: RemoteCommitmentKeys, input: InputInfo, tx: Transaction, commitmentFormat: CommitmentFormat) extends RemoteCommitForceCloseTransaction { override val desc: String = "remote-main-delayed" override val expectedWeight: Int = commitmentFormat.toRemoteWeight override def sign(): Transaction = { - val Right(paymentKey) = commitKeys.ourPaymentKey val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toRemoteDelayed(commitKeys.publicKeys)) - val sig = sign(paymentKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) + val sig = sign(commitKeys.ourPaymentKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) witnessClaimToRemoteDelayedFromCommitTx(sig, redeemScript) case _: SimpleTaprootChannelCommitmentFormat => val scriptTree: ScriptTree.Leaf = Taproot.toRemoteScriptTree(commitKeys.publicKeys) val redeemInfo = RedeemInfo.TaprootScriptPath(NUMS_POINT.xOnly, scriptTree, scriptTree.hash()) - val sig = sign(paymentKey, sighash, redeemInfo, extraUtxos = Map.empty) + val sig = sign(commitKeys.ourPaymentKey, sighash, redeemInfo, extraUtxos = Map.empty) Script.witnessScriptPathPay2tr(redeemInfo.internalKey, scriptTree, ScriptWitness(Seq(sig)), scriptTree) } tx.updateWitness(inputIndex, witness) @@ -1036,7 +962,7 @@ object Transactions { object ClaimRemoteDelayedOutputTx { def createUnsignedTx(commitKeys: RemoteCommitmentKeys, commitTx: Transaction, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimRemoteDelayedOutputTx] = { val redeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toRemoteDelayed(commitKeys.publicKeys)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -1046,20 +972,16 @@ object Transactions { findPubKeyScriptIndex(commitTx, redeemInfo.pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - commitKeys.ourPaymentKey match { - case Left(_) => Left(OutputAlreadyInWallet) - case Right(_) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) - val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) - val tx = Transaction( - version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, 1) :: Nil, - txOut = TxOut(amount, localFinalScriptPubKey) :: Nil, - lockTime = 0 - ) - val unsignedTx = ClaimRemoteDelayedOutputTx(commitKeys, input, tx, commitmentFormat) - skipTxIfBelowDust(unsignedTx, localDustLimit) - } + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val amount = input.txOut.amount - weight2fee(feerate, commitmentFormat.toRemoteWeight) + val tx = Transaction( + version = 2, + txIn = TxIn(input.outPoint, ByteVector.empty, 1) :: Nil, + txOut = TxOut(amount, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) + val unsignedTx = ClaimRemoteDelayedOutputTx(commitKeys, input, tx, commitmentFormat) + skipTxIfBelowDust(unsignedTx, localDustLimit) } } } @@ -1071,7 +993,7 @@ object Transactions { override def sign(): Transaction = { val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toLocalDelay)) val sig = sign(commitKeys.ourDelayedPaymentKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) witnessToLocalDelayedAfterDelay(sig, redeemScript) @@ -1088,7 +1010,7 @@ object Transactions { object ClaimLocalDelayedOutputTx { def createUnsignedTx(commitKeys: LocalCommitmentKeys, commitTx: Transaction, localDustLimit: Satoshi, toLocalDelay: CltvExpiryDelta, localFinalScriptPubKey: ByteVector, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimLocalDelayedOutputTx] = { val redeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toLocalDelay)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -1119,7 +1041,7 @@ object Transactions { override def sign(): Transaction = { val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toRemoteDelay)) val sig = sign(revocationKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) Scripts.witnessToLocalDelayedWithRevocationSig(sig, redeemScript) @@ -1136,7 +1058,7 @@ object Transactions { object MainPenaltyTx { def createUnsignedTx(commitKeys: RemoteCommitmentKeys, revocationKey: PrivateKey, commitTx: Transaction, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, toRemoteDelay: CltvExpiryDelta, feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, MainPenaltyTx] = { val redeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toRemoteDelay)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -1171,7 +1093,6 @@ object Transactions { override def sign(): Transaction = { val sig = sign(revocationKey, sighash, redeemInfo, extraUtxos = Map.empty) val witness = redeemInfo match { - case RedeemInfo.P2wpkh(_) => Script.witnessPay2wpkh(revocationKey.publicKey, der(sig)) case RedeemInfo.P2wsh(redeemScript) => Scripts.witnessHtlcWithRevocationSig(commitKeys, sig, redeemScript) case _: RedeemInfo.TaprootKeyPath => Script.witnessKeyPathPay2tr(sig, sighash) case s: RedeemInfo.TaprootScriptPath => Script.witnessScriptPathPay2tr(s.internalKey, s.leaf, ScriptWitness(Seq(sig)), s.scriptTree) @@ -1194,7 +1115,7 @@ object Transactions { case (paymentHash, htlcExpiry) => // We don't know if this was an incoming or outgoing HTLC, so we try both cases. val (offered, received) = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => (RedeemInfo.P2wsh(Script.write(htlcOffered(commitKeys.publicKeys, paymentHash, commitmentFormat))), RedeemInfo.P2wsh(Script.write(htlcReceived(commitKeys.publicKeys, paymentHash, htlcExpiry, commitmentFormat)))) case _: SimpleTaprootChannelCommitmentFormat => @@ -1243,7 +1164,7 @@ object Transactions { override def sign(): Transaction = { val witness = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toRemoteDelay)) val sig = sign(revocationKey, sighash, RedeemInfo.P2wsh(redeemScript), extraUtxos = Map.empty) Scripts.witnessToLocalDelayedWithRevocationSig(sig, redeemScript) @@ -1266,7 +1187,7 @@ object Transactions { feerate: FeeratePerKw, commitmentFormat: CommitmentFormat): Seq[Either[TxGenerationSkipped, ClaimHtlcDelayedOutputPenaltyTx]] = { val redeemInfo = commitmentFormat match { - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val redeemScript = Script.write(toLocalDelayed(commitKeys.publicKeys, toRemoteDelay)) RedeemInfo.P2wsh(redeemScript) case _: SimpleTaprootChannelCommitmentFormat => @@ -1367,7 +1288,6 @@ object Transactions { // When using anchor outputs, the channel initiator pays for *both* anchors all the time, even if only one anchor is present. // This is not technically a fee (it doesn't go to miners) but it also has to be deduced from the channel initiator's main output. val anchorsCost = commitmentFormat match { - case DefaultCommitmentFormat => Satoshi(0) case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => AnchorOutputsCommitmentFormat.anchorAmount * 2 } txFee + anchorsCost @@ -1421,7 +1341,6 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) private def getHtlcTxInputSequence(commitmentFormat: CommitmentFormat): Long = commitmentFormat match { - case DefaultCommitmentFormat => 0 // htlc txs immediately spend the commit tx case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 1 // htlc txs have a 1-block delay to allow CPFP carve-out on anchors } @@ -1461,7 +1380,7 @@ object Transactions { if (toLocalAmount >= dustLimit) { val redeemInfo = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => RedeemInfo.P2wsh(toLocalDelayed(commitmentKeys, toSelfDelay)) case _: SimpleTaprootChannelCommitmentFormat => val toLocalTree = Taproot.toLocalScriptTree(commitmentKeys, toSelfDelay) @@ -1472,8 +1391,6 @@ object Transactions { if (toRemoteAmount >= dustLimit) { val redeemInfo = commitmentFormat match { - case DefaultCommitmentFormat => - RedeemInfo.P2wpkh(commitmentKeys.remotePaymentPublicKey) case _: AnchorOutputsCommitmentFormat => RedeemInfo.P2wsh(toRemoteDelayed(commitmentKeys)) case _: SimpleTaprootChannelCommitmentFormat => @@ -1484,7 +1401,6 @@ object Transactions { } commitmentFormat match { - case DefaultCommitmentFormat => () case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => if (toLocalAmount >= dustLimit || hasHtlcs) { val redeemInfo = ClaimLocalAnchorTx.redeemInfo(localFundingPublicKey, commitmentKeys, commitmentFormat) @@ -1649,7 +1565,6 @@ object Transactions { txInfo match { case txInfo: ClaimLocalDelayedOutputTx => Right(txInfo.copy(tx = updatedTx)) case txInfo: ClaimRemoteDelayedOutputTx => Right(txInfo.copy(tx = updatedTx)) - case txInfo: ClaimP2WPKHOutputTx => Right(txInfo.copy(tx = updatedTx)) // Anchor transaction don't have any output: wallet inputs must be used to pay fees. case txInfo: ClaimAnchorTx => Left(CannotUpdateFee(txInfo)) // HTLC transactions are pre-signed, we can't update their fee by lowering the output amount. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala index 657e02d3b6..82a9cd32a9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala @@ -29,7 +29,7 @@ import fr.acinq.eclair.wire.protocol.{LiquidityAds, UpdateAddHtlc, UpdateMessage import fr.acinq.eclair.{FeatureSupport, Features, PermanentChannelFeature} import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec} +import scodec.{Attempt, Codec, Err} /** * Created by t-bast on 18/06/2025. @@ -78,11 +78,18 @@ private[channel] object ChannelCodecs5 { ) private val commitmentFormatCodec: Codec[Transactions.CommitmentFormat] = discriminated[Transactions.CommitmentFormat].by(uint8) - .typecase(0x00, provide(Transactions.DefaultCommitmentFormat)) .typecase(0x01, provide(Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) .typecase(0x02, provide(Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)) .typecase(0x03, provide(Transactions.PhoenixSimpleTaprootChannelCommitmentFormat)) .typecase(0x04, provide(Transactions.ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)) + // 0x00 was used for pre-anchor channels, which have been deprecated after eclair v0.13.1. + .typecase(0x00, fail[Transactions.CommitmentFormat](Err("some of your channels are not using anchor outputs: you must restart with your previous eclair version and close those channels before updating to this version of eclair (see the release notes for more details)"))) + + // The walletStaticPaymentBasepoint field was used for static_remotekey channels, which have been deprecated: we can + // thus safely ignore that field, and encode as if it wasn't provided. + // By keeping this codec, we can potentially re-introduce that field in the future if necessary for future channel + // types, without breaking backwards-compatibility. + private val ignoreWalletStaticPaymentBasepoint: Codec[Unit] = optional(bool8, publicKey).xmap[Unit](_ => (), _ => None) private val localChannelParamsCodec: Codec[LocalChannelParams] = ( ("nodeId" | publicKey) :: @@ -91,7 +98,7 @@ private[channel] object ChannelCodecs5 { // We pad to keep codecs byte-aligned. ("isChannelOpener" | bool) :: ("paysCommitTxFees" | bool) :: ignore(6) :: ("upfrontShutdownScript_opt" | optional(bool8, lengthDelimited(bytes))) :: - ("walletStaticPaymentBasepoint" | optional(bool8, publicKey)) :: + ("walletStaticPaymentBasepoint" | ignoreWalletStaticPaymentBasepoint) :: ("features" | combinedFeaturesCodec)).as[LocalChannelParams] val remoteChannelParamsCodec: Codec[RemoteChannelParams] = ( diff --git a/eclair-core/src/test/resources/bolt3-tx-test-vectors-default-commitment-format.txt b/eclair-core/src/test/resources/bolt3-tx-test-vectors-default-commitment-format.txt deleted file mode 100644 index e4c7148f9e..0000000000 --- a/eclair-core/src/test/resources/bolt3-tx-test-vectors-default-commitment-format.txt +++ /dev/null @@ -1,367 +0,0 @@ - name: simple commitment tx with no HTLCs - to_local_msat: 7000000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 15000 - # base commitment transaction fee = 10860 - # actual commitment transaction fee = 10860 - # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0 - # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with all five HTLCs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 0 - # base commitment transaction fee = 0 - # actual commitment transaction fee = 0 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #0 received amount 1000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6988000 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606 - # local_signature = 30440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f06 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 5 - # signature for output #0 (htlc-success for htlc #0) - remote_htlc_signature = 304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a6 - # local_htlc_signature = 304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5 - htlc_success_tx (htlc #0): 020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219700000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a6e59f18764a5bf8d4fa45eebc591566689441229c918b480fb2af8cc6a4aeb02205248f273be447684b33e3c8d1d85a8e0ca9fa0bae9ae33f0527ada9c162919a60147304402207cb324fa0de88f452ffa9389678127ebcf4cabe1dd848b8e076c1a1962bf34720220116ed922b12311bd602d67e60d2529917f21c5b82f25ff6506c0f87886b4dfd5012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b - # local_htlc_signature = 3045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be5 - htlc_timeout_tx (htlc #2): 020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219701000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d5275b3619953cb0c3b5aa577f04bc512380e60fa551762ce3d7a1bb7401cff9022037237ab0dac3fe100cde094e82e2bed9ba0ed1bb40154b48e56aa70f259e608b01483045022100c89172099507ff50f4c925e6c5150e871fb6e83dd73ff9fbb72f6ce829a9633f02203a63821d9162e99f9be712a68f9e589483994feae2661e4546cd5b6cec007be501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #2 (htlc-success for htlc #1) - remote_htlc_signature = 304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f202 - # local_htlc_signature = 3045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da - htlc_success_tx (htlc #1): 020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219702000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402201b63ec807771baf4fdff523c644080de17f1da478989308ad13a58b51db91d360220568939d38c9ce295adba15665fa68f51d967e8ed14a007b751540a80b325f20201483045022100def389deab09cee69eaa1ec14d9428770e45bcbe9feb46468ecf481371165c2f022015d2e3c46600b2ebba8dcc899768874cc6851fd1ecb3fffd15db1cc3de7e10da012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #3 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554 - # local_htlc_signature = 30440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac08727 - htlc_timeout_tx (htlc #3): 020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219703000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100daee1808f9861b6c3ecd14f7b707eca02dd6bdfc714ba2f33bc8cdba507bb182022026654bf8863af77d74f51f4e0b62d461a019561bb12acb120d3f7195d148a554014730440220643aacb19bbb72bd2b635bc3f7375481f5981bace78cdd8319b2988ffcc6704202203d27784ec8ad51ed3bd517a05525a5139bb0b755dd719e0054332d186ac0872701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #4 (htlc-success for htlc #4) - remote_htlc_signature = 304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d - # local_htlc_signature = 30440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e - htlc_success_tx (htlc #4): 020000000001018154ecccf11a5fb56c39654c4deb4d2296f83c69268280b94d021370c94e219704000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e0410e45454b0978a623f36a10626ef17b27d9ad44e2760f98cfa3efb37924f0220220bd8acd43ecaa916a80bd4f919c495a2c58982ce7c8625153f8596692a801d014730440220549e80b4496803cbc4a1d09d46df50109f546d43fbbf86cd90b174b1484acd5402205f12a4f995cb9bded597eabfee195a285986aa6d93ae5bb72507ebc6a4e2349e012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with seven outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 647 - # base commitment transaction fee = 1024 - # actual commitment transaction fee = 1024 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #0 received amount 1000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6986976 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b - # local_signature = 304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d1163 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e09c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040048304502210094bfd8f5572ac0157ec76a9551b6c5216a4538c07cd13a51af4a54cb26fa14320220768efce8ce6f4a5efac875142ff19237c011343670adf9c7ac69704a120d116301483045022100a5c01383d3ec646d97e40f44318d49def817fcd61a0ef18008a665b3e151785502203e648efddd5838981ef55ec954be69c4a652d021e6081a100d034de366815e9b01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 5 - # signature for output #0 (htlc-success for htlc #0) - remote_htlc_signature = 30440220385a5afe75632f50128cbb029ee95c80156b5b4744beddc729ad339c9ca432c802202ba5f48550cad3379ac75b9b4fedb86a35baa6947f16ba5037fb8b11ab343740 - # local_htlc_signature = 304402205999590b8a79fa346e003a68fd40366397119b2b0cdf37b149968d6bc6fbcc4702202b1e1fb5ab7864931caed4e732c359e0fe3d86a548b557be2246efb1708d579a - htlc_success_tx (htlc #0): 020000000001018323148ce2419f21ca3d6780053747715832e18ac780931a514b187768882bb60000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220385a5afe75632f50128cbb029ee95c80156b5b4744beddc729ad339c9ca432c802202ba5f48550cad3379ac75b9b4fedb86a35baa6947f16ba5037fb8b11ab3437400147304402205999590b8a79fa346e003a68fd40366397119b2b0cdf37b149968d6bc6fbcc4702202b1e1fb5ab7864931caed4e732c359e0fe3d86a548b557be2246efb1708d579a012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #2) - remote_htlc_signature = 304402207ceb6678d4db33d2401fdc409959e57c16a6cb97a30261d9c61f29b8c58d34b90220084b4a17b4ca0e86f2d798b3698ca52de5621f2ce86f80bed79afa66874511b0 - # local_htlc_signature = 304402207ff03eb0127fc7c6cae49cc29e2a586b98d1e8969cf4a17dfa50b9c2647720b902205e2ecfda2252956c0ca32f175080e75e4e390e433feb1f8ce9f2ba55648a1dac - htlc_timeout_tx (htlc #2): 020000000001018323148ce2419f21ca3d6780053747715832e18ac780931a514b187768882bb60100000000000000000124060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207ceb6678d4db33d2401fdc409959e57c16a6cb97a30261d9c61f29b8c58d34b90220084b4a17b4ca0e86f2d798b3698ca52de5621f2ce86f80bed79afa66874511b00147304402207ff03eb0127fc7c6cae49cc29e2a586b98d1e8969cf4a17dfa50b9c2647720b902205e2ecfda2252956c0ca32f175080e75e4e390e433feb1f8ce9f2ba55648a1dac01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #2 (htlc-success for htlc #1) - remote_htlc_signature = 304402206a401b29a0dff0d18ec903502c13d83e7ec019450113f4a7655a4ce40d1f65ba0220217723a084e727b6ca0cc8b6c69c014a7e4a01fcdcba3e3993f462a3c574d833 - # local_htlc_signature = 3045022100d50d067ca625d54e62df533a8f9291736678d0b86c28a61bb2a80cf42e702d6e02202373dde7e00218eacdafb9415fe0e1071beec1857d1af3c6a201a44cbc47c877 - htlc_success_tx (htlc #1): 020000000001018323148ce2419f21ca3d6780053747715832e18ac780931a514b187768882bb6020000000000000000010a060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206a401b29a0dff0d18ec903502c13d83e7ec019450113f4a7655a4ce40d1f65ba0220217723a084e727b6ca0cc8b6c69c014a7e4a01fcdcba3e3993f462a3c574d83301483045022100d50d067ca625d54e62df533a8f9291736678d0b86c28a61bb2a80cf42e702d6e02202373dde7e00218eacdafb9415fe0e1071beec1857d1af3c6a201a44cbc47c877012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #3 (htlc-timeout for htlc #3) - remote_htlc_signature = 30450221009b1c987ba599ee3bde1dbca776b85481d70a78b681a8d84206723e2795c7cac002207aac84ad910f8598c4d1c0ea2e3399cf6627a4e3e90131315bc9f038451ce39d - # local_htlc_signature = 3045022100db9dc65291077a52728c622987e9895b7241d4394d6dcb916d7600a3e8728c22022036ee3ee717ba0bb5c45ee84bc7bbf85c0f90f26ae4e4a25a6b4241afa8a3f1cb - htlc_timeout_tx (htlc #3): 020000000001018323148ce2419f21ca3d6780053747715832e18ac780931a514b187768882bb6030000000000000000010c0a0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221009b1c987ba599ee3bde1dbca776b85481d70a78b681a8d84206723e2795c7cac002207aac84ad910f8598c4d1c0ea2e3399cf6627a4e3e90131315bc9f038451ce39d01483045022100db9dc65291077a52728c622987e9895b7241d4394d6dcb916d7600a3e8728c22022036ee3ee717ba0bb5c45ee84bc7bbf85c0f90f26ae4e4a25a6b4241afa8a3f1cb01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #4 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100cc28030b59f0914f45b84caa983b6f8effa900c952310708c2b5b00781117022022027ba2ccdf94d03c6d48b327f183f6e28c8a214d089b9227f94ac4f85315274f0 - # local_htlc_signature = 304402202d1a3c0d31200265d2a2def2753ead4959ae20b4083e19553acfffa5dfab60bf022020ede134149504e15b88ab261a066de49848411e15e70f9e6a5462aec2949f8f - htlc_success_tx (htlc #4): 020000000001018323148ce2419f21ca3d6780053747715832e18ac780931a514b187768882bb604000000000000000001da0d0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100cc28030b59f0914f45b84caa983b6f8effa900c952310708c2b5b00781117022022027ba2ccdf94d03c6d48b327f183f6e28c8a214d089b9227f94ac4f85315274f00147304402202d1a3c0d31200265d2a2def2753ead4959ae20b4083e19553acfffa5dfab60bf022020ede134149504e15b88ab261a066de49848411e15e70f9e6a5462aec2949f8f012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with six outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 648 - # base commitment transaction fee = 914 - # actual commitment transaction fee = 1914 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6987086 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b057 - # local_signature = 3045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431104e9d6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a2270d5950c89ae0841233f6efea9c951898b301b2e89e0adbd2c687b9f32efa02207943d90f95b9610458e7c65a576e149750ff3accaacad004cd85e70b235e27de01473044022072714e2fbb93cdd1c42eb0828b4f2eff143f717d8f26e79d6ada4f0dcb681bbe02200911be4e5161dd6ebe59ff1c58e1997c4aea804f81db6b698821db6093d7b05701475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 4 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3044022062ef2e77591409d60d7817d9bb1e71d3c4a2931d1a6c7c8307422c84f001a251022022dad9726b0ae3fe92bda745a06f2c00f92342a186d84518588cf65f4dfaada8 - # local_htlc_signature = 3045022100a4c574f00411dd2f978ca5cdc1b848c311cd7849c087ad2f21a5bce5e8cc5ae90220090ae39a9bce2fb8bc879d7e9f9022df249f41e25e51f1a9bf6447a9eeffc098 - htlc_timeout_tx (htlc #2): 02000000000101579c183eca9e8236a5d7f5dcd79cfec32c497fdc0ec61533cde99ecd436cadd10000000000000000000123060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022062ef2e77591409d60d7817d9bb1e71d3c4a2931d1a6c7c8307422c84f001a251022022dad9726b0ae3fe92bda745a06f2c00f92342a186d84518588cf65f4dfaada801483045022100a4c574f00411dd2f978ca5cdc1b848c311cd7849c087ad2f21a5bce5e8cc5ae90220090ae39a9bce2fb8bc879d7e9f9022df249f41e25e51f1a9bf6447a9eeffc09801008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-success for htlc #1) - remote_htlc_signature = 3045022100e968cbbb5f402ed389fdc7f6cd2a80ed650bb42c79aeb2a5678444af94f6c78502204b47a1cb24ab5b0b6fe69fe9cfc7dba07b9dd0d8b95f372c1d9435146a88f8d4 - # local_htlc_signature = 304402207679cf19790bea76a733d2fa0672bd43ab455687a068f815a3d237581f57139a0220683a1a799e102071c206b207735ca80f627ab83d6616b4bcd017c5d79ef3e7d0 - htlc_success_tx (htlc #1): 02000000000101579c183eca9e8236a5d7f5dcd79cfec32c497fdc0ec61533cde99ecd436cadd10100000000000000000109060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100e968cbbb5f402ed389fdc7f6cd2a80ed650bb42c79aeb2a5678444af94f6c78502204b47a1cb24ab5b0b6fe69fe9cfc7dba07b9dd0d8b95f372c1d9435146a88f8d40147304402207679cf19790bea76a733d2fa0672bd43ab455687a068f815a3d237581f57139a0220683a1a799e102071c206b207735ca80f627ab83d6616b4bcd017c5d79ef3e7d0012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #2 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100aa91932e305292cf9969cc23502bbf6cef83a5df39c95ad04a707c4f4fed5c7702207099fc0f3a9bfe1e7683c0e9aa5e76c5432eb20693bf4cb182f04d383dc9c8c2 - # local_htlc_signature = 304402200df76fea718745f3c529bac7fd37923e7309ce38b25c0781e4cf514dd9ef8dc802204172295739dbae9fe0474dcee3608e3433b4b2af3a2e6787108b02f894dcdda3 - htlc_timeout_tx (htlc #3): 02000000000101579c183eca9e8236a5d7f5dcd79cfec32c497fdc0ec61533cde99ecd436cadd1020000000000000000010b0a0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100aa91932e305292cf9969cc23502bbf6cef83a5df39c95ad04a707c4f4fed5c7702207099fc0f3a9bfe1e7683c0e9aa5e76c5432eb20693bf4cb182f04d383dc9c8c20147304402200df76fea718745f3c529bac7fd37923e7309ce38b25c0781e4cf514dd9ef8dc802204172295739dbae9fe0474dcee3608e3433b4b2af3a2e6787108b02f894dcdda301008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #3 (htlc-success for htlc #4) - remote_htlc_signature = 3044022035cac88040a5bba420b1c4257235d5015309113460bc33f2853cd81ca36e632402202fc94fd3e81e9d34a9d01782a0284f3044370d03d60f3fc041e2da088d2de58f - # local_htlc_signature = 304402200daf2eb7afd355b4caf6fb08387b5f031940ea29d1a9f35071288a839c9039e4022067201b562456e7948616c13acb876b386b511599b58ac1d94d127f91c50463a6 - htlc_success_tx (htlc #4): 02000000000101579c183eca9e8236a5d7f5dcd79cfec32c497fdc0ec61533cde99ecd436cadd103000000000000000001d90d0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022035cac88040a5bba420b1c4257235d5015309113460bc33f2853cd81ca36e632402202fc94fd3e81e9d34a9d01782a0284f3044370d03d60f3fc041e2da088d2de58f0147304402200daf2eb7afd355b4caf6fb08387b5f031940ea29d1a9f35071288a839c9039e4022067201b562456e7948616c13acb876b386b511599b58ac1d94d127f91c50463a6012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with six outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2069 - # base commitment transaction fee = 2921 - # actual commitment transaction fee = 3921 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985079 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb4 - # local_signature = 304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311077956a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203ca8f31c6a47519f83255dc69f1894d9a6d7476a19f498d31eaf0cd3a85eeb63022026fd92dc752b33905c4c838c528b692a8ad4ced959990b5d5ee2ff940fa90eea01473044022001d55e488b8b035b2dd29d50b65b530923a416d47f377284145bc8767b1b6a75022019bb53ddfe1cefaf156f924777eaaf8fdca1810695a7d0a247ad2afba8232eb401475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 4 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100d1cf354de41c1369336cf85b225ed033f1f8982a01be503668df756a7e668b66022001254144fb4d0eecc61908fccc3388891ba17c5d7a1a8c62bdd307e5a513f992 - # local_htlc_signature = 3044022056eb1af429660e45a1b0b66568cb8c4a3aa7e4c9c292d5d6c47f86ebf2c8838f022065c3ac4ebe980ca7a41148569be4ad8751b0a724a41405697ec55035dae66402 - htlc_timeout_tx (htlc #2): 02000000000101ca94a9ad516ebc0c4bdd7b6254871babfa978d5accafb554214137d398bfcf6a0000000000000000000175020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d1cf354de41c1369336cf85b225ed033f1f8982a01be503668df756a7e668b66022001254144fb4d0eecc61908fccc3388891ba17c5d7a1a8c62bdd307e5a513f99201473044022056eb1af429660e45a1b0b66568cb8c4a3aa7e4c9c292d5d6c47f86ebf2c8838f022065c3ac4ebe980ca7a41148569be4ad8751b0a724a41405697ec55035dae6640201008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-success for htlc #1) - remote_htlc_signature = 3045022100d065569dcb94f090345402736385efeb8ea265131804beac06dd84d15dd2d6880220664feb0b4b2eb985fadb6ec7dc58c9334ea88ce599a9be760554a2d4b3b5d9f4 - # local_htlc_signature = 3045022100914bb232cd4b2690ee3d6cb8c3713c4ac9c4fb925323068d8b07f67c8541f8d9022057152f5f1615b793d2d45aac7518989ae4fe970f28b9b5c77504799d25433f7f - htlc_success_tx (htlc #1): 02000000000101ca94a9ad516ebc0c4bdd7b6254871babfa978d5accafb554214137d398bfcf6a0100000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d065569dcb94f090345402736385efeb8ea265131804beac06dd84d15dd2d6880220664feb0b4b2eb985fadb6ec7dc58c9334ea88ce599a9be760554a2d4b3b5d9f401483045022100914bb232cd4b2690ee3d6cb8c3713c4ac9c4fb925323068d8b07f67c8541f8d9022057152f5f1615b793d2d45aac7518989ae4fe970f28b9b5c77504799d25433f7f012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #2 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100d4e69d363de993684eae7b37853c40722a4c1b4a7b588ad7b5d8a9b5006137a102207a069c628170ee34be5612747051bdcc087466dbaa68d5756ea81c10155aef18 - # local_htlc_signature = 304402200e362443f7af830b419771e8e1614fc391db3a4eb799989abfc5ab26d6fcd032022039ab0cad1c14dfbe9446bf847965e56fe016e0cbcf719fd18c1bfbf53ecbd9f9 - htlc_timeout_tx (htlc #3): 02000000000101ca94a9ad516ebc0c4bdd7b6254871babfa978d5accafb554214137d398bfcf6a020000000000000000015d060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d4e69d363de993684eae7b37853c40722a4c1b4a7b588ad7b5d8a9b5006137a102207a069c628170ee34be5612747051bdcc087466dbaa68d5756ea81c10155aef180147304402200e362443f7af830b419771e8e1614fc391db3a4eb799989abfc5ab26d6fcd032022039ab0cad1c14dfbe9446bf847965e56fe016e0cbcf719fd18c1bfbf53ecbd9f901008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #3 (htlc-success for htlc #4) - remote_htlc_signature = 30450221008ec888e36e4a4b3dc2ed6b823319855b2ae03006ca6ae0d9aa7e24bfc1d6f07102203b0f78885472a67ff4fe5916c0bb669487d659527509516fc3a08e87a2cc0a7c - # local_htlc_signature = 304402202c3e14282b84b02705dfd00a6da396c9fe8a8bcb1d3fdb4b20a4feba09440e8b02202b058b39aa9b0c865b22095edcd9ff1f71bbfe20aa4993755e54d042755ed0d5 - htlc_success_tx (htlc #4): 02000000000101ca94a9ad516ebc0c4bdd7b6254871babfa978d5accafb554214137d398bfcf6a03000000000000000001f2090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221008ec888e36e4a4b3dc2ed6b823319855b2ae03006ca6ae0d9aa7e24bfc1d6f07102203b0f78885472a67ff4fe5916c0bb669487d659527509516fc3a08e87a2cc0a7c0147304402202c3e14282b84b02705dfd00a6da396c9fe8a8bcb1d3fdb4b20a4feba09440e8b02202b058b39aa9b0c865b22095edcd9ff1f71bbfe20aa4993755e54d042755ed0d5012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with five outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2070 - # base commitment transaction fee = 2566 - # actual commitment transaction fee = 5566 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985434 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f37526 - # local_signature = 30440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd0 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110da966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220443cb07f650aebbba14b8bc8d81e096712590f524c5991ac0ed3bbc8fd3bd0c7022028a635f548e3ca64b19b69b1ea00f05b22752f91daf0b6dab78e62ba52eb7fd001483045022100f2377f7a67b7fc7f4e2c0c9e3a7de935c32417f5668eda31ea1db401b7dc53030220415fdbc8e91d0f735e70c21952342742e25249b0d062d43efbfc564499f3752601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100eed143b1ee4bed5dc3cde40afa5db3e7354cbf9c44054b5f713f729356f08cf7022077161d171c2bbd9badf3c9934de65a4918de03bbac1450f715275f75b103f891 - # local_htlc_signature = 3045022100a0d043ed533e7fb1911e0553d31a8e2f3e6de19dbc035257f29d747c5e02f1f5022030cd38d8e84282175d49c1ebe0470db3ebd59768cf40780a784e248a43904fb8 - htlc_timeout_tx (htlc #2): 0200000000010140a83ce364747ff277f4d7595d8d15f708418798922c40bc2b056aca5485a2180000000000000000000174020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100eed143b1ee4bed5dc3cde40afa5db3e7354cbf9c44054b5f713f729356f08cf7022077161d171c2bbd9badf3c9934de65a4918de03bbac1450f715275f75b103f89101483045022100a0d043ed533e7fb1911e0553d31a8e2f3e6de19dbc035257f29d747c5e02f1f5022030cd38d8e84282175d49c1ebe0470db3ebd59768cf40780a784e248a43904fb801008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-timeout for htlc #3) - remote_htlc_signature = 3044022071e9357619fd8d29a411dc053b326a5224c5d11268070e88ecb981b174747c7a02202b763ae29a9d0732fa8836dd8597439460b50472183f420021b768981b4f7cf6 - # local_htlc_signature = 3045022100adb1d679f65f96178b59f23ed37d3b70443118f345224a07ecb043eee2acc157022034d24524fe857144a3bcfff3065a9994d0a6ec5f11c681e49431d573e242612d - htlc_timeout_tx (htlc #3): 0200000000010140a83ce364747ff277f4d7595d8d15f708418798922c40bc2b056aca5485a218010000000000000000015c060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022071e9357619fd8d29a411dc053b326a5224c5d11268070e88ecb981b174747c7a02202b763ae29a9d0732fa8836dd8597439460b50472183f420021b768981b4f7cf601483045022100adb1d679f65f96178b59f23ed37d3b70443118f345224a07ecb043eee2acc157022034d24524fe857144a3bcfff3065a9994d0a6ec5f11c681e49431d573e242612d01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #2 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100c9458a4d2cbb741705577deb0a890e5cb90ee141be0400d3162e533727c9cb2102206edcf765c5dc5e5f9b976ea8149bf8607b5a0efb30691138e1231302b640d2a4 - # local_htlc_signature = 304402200831422aa4e1ee6d55e0b894201770a8f8817a189356f2d70be76633ffa6a6f602200dd1b84a4855dc6727dd46c98daae43dfc70889d1ba7ef0087529a57c06e5e04 - htlc_success_tx (htlc #4): 0200000000010140a83ce364747ff277f4d7595d8d15f708418798922c40bc2b056aca5485a21802000000000000000001f1090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100c9458a4d2cbb741705577deb0a890e5cb90ee141be0400d3162e533727c9cb2102206edcf765c5dc5e5f9b976ea8149bf8607b5a0efb30691138e1231302b640d2a40147304402200831422aa4e1ee6d55e0b894201770a8f8817a189356f2d70be76633ffa6a6f602200dd1b84a4855dc6727dd46c98daae43dfc70889d1ba7ef0087529a57c06e5e04012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with five outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2194 - # base commitment transaction fee = 2720 - # actual commitment transaction fee = 5720 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985280 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec86203953348 - # local_signature = 304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f7061 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311040966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b1b010c109c2ecbe7feb2d259b9c4126bd5dc99ee693c422ec0a5781fe161ba0220571fe4e2c649dea9c7aaf7e49b382962f6a3494963c97d80fef9a430ca3f706101483045022100d33c4e541aa1d255d41ea9a3b443b3b822ad8f7f86862638aac1f69f8f760577022007e2a18e6931ce3d3a804b1c78eda1de17dbe1fb7a95488c9a4ec8620395334801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 30450221009ed2f0a67f99e29c3c8cf45c08207b765980697781bb727fe0b1416de0e7622902206052684229bc171419ed290f4b615c943f819c0262414e43c5b91dcf72ddcf44 - # local_htlc_signature = 3044022004ad5f04ae69c71b3b141d4db9d0d4c38d84009fb3cfeeae6efdad414487a9a0022042d3fe1388c1ff517d1da7fb4025663d372c14728ed52dc88608363450ff6a2f - htlc_timeout_tx (htlc #2): 02000000000101fb824d4e4dafc0f567789dee3a6bce8d411fe80f5563d8cdfdcc7d7e4447d43a0000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221009ed2f0a67f99e29c3c8cf45c08207b765980697781bb727fe0b1416de0e7622902206052684229bc171419ed290f4b615c943f819c0262414e43c5b91dcf72ddcf4401473044022004ad5f04ae69c71b3b141d4db9d0d4c38d84009fb3cfeeae6efdad414487a9a0022042d3fe1388c1ff517d1da7fb4025663d372c14728ed52dc88608363450ff6a2f01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-timeout for htlc #3) - remote_htlc_signature = 30440220155d3b90c67c33a8321996a9be5b82431b0c126613be751d400669da9d5c696702204318448bcd48824439d2c6a70be6e5747446be47ff45977cf41672bdc9b6b12d - # local_htlc_signature = 304402201707050c870c1f77cc3ed58d6d71bf281de239e9eabd8ef0955bad0d7fe38dcc02204d36d80d0019b3a71e646a08fa4a5607761d341ae8be371946ebe437c289c915 - htlc_timeout_tx (htlc #3): 02000000000101fb824d4e4dafc0f567789dee3a6bce8d411fe80f5563d8cdfdcc7d7e4447d43a010000000000000000010a060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220155d3b90c67c33a8321996a9be5b82431b0c126613be751d400669da9d5c696702204318448bcd48824439d2c6a70be6e5747446be47ff45977cf41672bdc9b6b12d0147304402201707050c870c1f77cc3ed58d6d71bf281de239e9eabd8ef0955bad0d7fe38dcc02204d36d80d0019b3a71e646a08fa4a5607761d341ae8be371946ebe437c289c91501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #2 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100a12a9a473ece548584aabdd051779025a5ed4077c4b7aa376ec7a0b1645e5a48022039490b333f53b5b3e2ddde1d809e492cba2b3e5fc3a436cd3ffb4cd3d500fa5a - # local_htlc_signature = 3045022100ff200bc934ab26ce9a559e998ceb0aee53bc40368e114ab9d3054d9960546e2802202496856ca163ac12c143110b6b3ac9d598df7254f2e17b3b94c3ab5301f4c3b0 - htlc_success_tx (htlc #4): 02000000000101fb824d4e4dafc0f567789dee3a6bce8d411fe80f5563d8cdfdcc7d7e4447d43a020000000000000000019a090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100a12a9a473ece548584aabdd051779025a5ed4077c4b7aa376ec7a0b1645e5a48022039490b333f53b5b3e2ddde1d809e492cba2b3e5fc3a436cd3ffb4cd3d500fa5a01483045022100ff200bc934ab26ce9a559e998ceb0aee53bc40368e114ab9d3054d9960546e2802202496856ca163ac12c143110b6b3ac9d598df7254f2e17b3b94c3ab5301f4c3b0012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with four outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2195 - # base commitment transaction fee = 2344 - # actual commitment transaction fee = 7344 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985656 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb7924298 - # local_signature = 304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a5429 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110b8976a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402203b12d44254244b8ff3bb4129b0920fd45120ab42f553d9976394b099d500c99e02205e95bb7a3164852ef0c48f9e0eaf145218f8e2c41251b231f03cbdc4f29a54290147304402205e2f76d4657fb732c0dfc820a18a7301e368f5799e06b7828007633741bda6df0220458009ae59d0c6246065c419359e05eb2a4b4ef4a1b310cc912db44eb792429801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 2 - # signature for output #0 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100a8a78fa1016a5c5c3704f2e8908715a3cef66723fb95f3132ec4d2d05cd84fb4022025ac49287b0861ec21932405f5600cbce94313dbde0e6c5d5af1b3366d8afbfc - # local_htlc_signature = 3045022100be6ae1977fd7b630a53623f3f25c542317ccfc2b971782802a4f1ef538eb22b402207edc4d0408f8f38fd3c7365d1cfc26511b7cd2d4fecd8b005fba3cd5bc704390 - htlc_timeout_tx (htlc #3): 020000000001014e16c488fa158431c1a82e8f661240ec0a71ba0ce92f2721a6538c510226ad5c0000000000000000000109060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100a8a78fa1016a5c5c3704f2e8908715a3cef66723fb95f3132ec4d2d05cd84fb4022025ac49287b0861ec21932405f5600cbce94313dbde0e6c5d5af1b3366d8afbfc01483045022100be6ae1977fd7b630a53623f3f25c542317ccfc2b971782802a4f1ef538eb22b402207edc4d0408f8f38fd3c7365d1cfc26511b7cd2d4fecd8b005fba3cd5bc70439001008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #1 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100e769cb156aa2f7515d126cef7a69968629620ce82afcaa9e210969de6850df4602200b16b3f3486a229a48aadde520dbee31ae340dbadaffae74fbb56681fef27b92 - # local_htlc_signature = 30440220665b9cb4a978c09d1ca8977a534999bc8a49da624d0c5439451dd69cde1a003d022070eae0620f01f3c1bd029cc1488da13fb40fdab76f396ccd335479a11c5276d8 - htlc_success_tx (htlc #4): 020000000001014e16c488fa158431c1a82e8f661240ec0a71ba0ce92f2721a6538c510226ad5c0100000000000000000199090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100e769cb156aa2f7515d126cef7a69968629620ce82afcaa9e210969de6850df4602200b16b3f3486a229a48aadde520dbee31ae340dbadaffae74fbb56681fef27b92014730440220665b9cb4a978c09d1ca8977a534999bc8a49da624d0c5439451dd69cde1a003d022070eae0620f01f3c1bd029cc1488da13fb40fdab76f396ccd335479a11c5276d8012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with four outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 3702 - # base commitment transaction fee = 3953 - # actual commitment transaction fee = 8953 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6984047 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c1 - # local_signature = 304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b0 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431106f916a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402200e930a43c7951162dc15a2b7344f48091c74c70f7024e7116e900d8bcfba861c022066fa6cbda3929e21daa2e7e16a4b948db7e8919ef978402360d1095ffdaff7b001483045022100c1a3b0b60ca092ed5080121f26a74a20cec6bdee3f8e47bae973fcdceb3eda5502207d467a9873c939bf3aa758014ae67295fedbca52412633f7e5b2670fc7c381c101475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 2 - # signature for output #0 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100dfb73b4fe961b31a859b2bb1f4f15cabab9265016dd0272323dc6a9e85885c54022059a7b87c02861ee70662907f25ce11597d7b68d3399443a831ae40e777b76bdb - # local_htlc_signature = 304402202765b9c9ece4f127fa5407faf66da4c5ce2719cdbe47cd3175fc7d48b482e43d02205605125925e07bad1e41c618a4b434d72c88a164981c4b8af5eaf4ee9142ec3a - htlc_timeout_tx (htlc #3): 02000000000101b8de11eb51c22498fe39722c7227b6e55ff1a94146cf638458cb9bc6a060d3a30000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100dfb73b4fe961b31a859b2bb1f4f15cabab9265016dd0272323dc6a9e85885c54022059a7b87c02861ee70662907f25ce11597d7b68d3399443a831ae40e777b76bdb0147304402202765b9c9ece4f127fa5407faf66da4c5ce2719cdbe47cd3175fc7d48b482e43d02205605125925e07bad1e41c618a4b434d72c88a164981c4b8af5eaf4ee9142ec3a01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #1 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100ea9dc2a7c3c3640334dab733bb4e036e32a3106dc707b24227874fa4f7da746802204d672f7ac0fe765931a8df10b81e53a3242dd32bd9dc9331eb4a596da87954e9 - # local_htlc_signature = 30440220048a41c660c4841693de037d00a407810389f4574b3286afb7bc392a438fa3f802200401d71fa87c64fe621b49ac07e3bf85157ac680acb977124da28652cc7f1a5c - htlc_success_tx (htlc #4): 02000000000101b8de11eb51c22498fe39722c7227b6e55ff1a94146cf638458cb9bc6a060d3a30100000000000000000176050000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100ea9dc2a7c3c3640334dab733bb4e036e32a3106dc707b24227874fa4f7da746802204d672f7ac0fe765931a8df10b81e53a3242dd32bd9dc9331eb4a596da87954e9014730440220048a41c660c4841693de037d00a407810389f4574b3286afb7bc392a438fa3f802200401d71fa87c64fe621b49ac07e3bf85157ac680acb977124da28652cc7f1a5c012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with three outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 3703 - # base commitment transaction fee = 3317 - # actual commitment transaction fee = 11317 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6984683 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 30450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb0 - # local_signature = 3044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110eb936a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022047305531dd44391dce03ae20f8735005c615eb077a974edb0059ea1a311857d602202e0ed6972fbdd1e8cb542b06e0929bc41b2ddf236e04cb75edd56151f4197506014830450221008b7c191dd46893b67b628e618d2dc8e81169d38bade310181ab77d7c94c6675e02203b4dd131fd7c9deb299560983dcdc485545c98f989f7ae8180c28289f9e6bdb001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 1 - # signature for output #0 (htlc-success for htlc #4) - remote_htlc_signature = 3044022044f65cf833afdcb9d18795ca93f7230005777662539815b8a601eeb3e57129a902206a4bf3e53392affbba52640627defa8dc8af61c958c9e827b2798ab45828abdd - # local_htlc_signature = 3045022100b94d931a811b32eeb885c28ddcf999ae1981893b21dd1329929543fe87ce793002206370107fdd151c5f2384f9ceb71b3107c69c74c8ed5a28a94a4ab2d27d3b0724 - htlc_success_tx (htlc #4): 020000000001011c076aa7fb3d7460d10df69432c904227ea84bbf3134d4ceee5fb0f135ef206d0000000000000000000175050000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022044f65cf833afdcb9d18795ca93f7230005777662539815b8a601eeb3e57129a902206a4bf3e53392affbba52640627defa8dc8af61c958c9e827b2798ab45828abdd01483045022100b94d931a811b32eeb885c28ddcf999ae1981893b21dd1329929543fe87ce793002206370107fdd151c5f2384f9ceb71b3107c69c74c8ed5a28a94a4ab2d27d3b0724012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with three outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 4914 - # base commitment transaction fee = 4402 - # actual commitment transaction fee = 12402 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6983598 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e - # local_signature = 304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe26 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110ae8f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402206a2679efa3c7aaffd2a447fd0df7aba8792858b589750f6a1203f9259173198a022008d52a0e77a99ab533c36206cb15ad7aeb2aa72b93d4b571e728cb5ec2f6fe260147304402206d6cb93969d39177a09d5d45b583f34966195b77c7e585cf47ac5cce0c90cefb022031d71ae4e33a4e80df7f981d696fbdee517337806a3c7138b7491e2cbb077a0e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 1 - # signature for output #0 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100fcb38506bfa11c02874092a843d0cc0a8613c23b639832564a5f69020cb0f6ba02206508b9e91eaa001425c190c68ee5f887e1ad5b1b314002e74db9dbd9e42dbecf - # local_htlc_signature = 304502210086e76b460ddd3cea10525fba298405d3fe11383e56966a5091811368362f689a02200f72ee75657915e0ede89c28709acd113ede9e1b7be520e3bc5cda425ecd6e68 - htlc_success_tx (htlc #4): 0200000000010110a3fdcbcd5db477cd3ad465e7f501ffa8c437e8301f00a6061138590add757f0000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100fcb38506bfa11c02874092a843d0cc0a8613c23b639832564a5f69020cb0f6ba02206508b9e91eaa001425c190c68ee5f887e1ad5b1b314002e74db9dbd9e42dbecf0148304502210086e76b460ddd3cea10525fba298405d3fe11383e56966a5091811368362f689a02200f72ee75657915e0ede89c28709acd113ede9e1b7be520e3bc5cda425ecd6e68012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with two outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 4915 - # base commitment transaction fee = 3558 - # actual commitment transaction fee = 15558 - # to_local amount 6984442 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f8765552 - # local_signature = 3045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b9 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100a012691ba6cea2f73fa8bac37750477e66363c6d28813b0bb6da77c8eb3fb0270220365e99c51304b0b1a6ab9ea1c8500db186693e39ec1ad5743ee231b0138384b90147304402200769ba89c7330dfa4feba447b6e322305f12ac7dac70ec6ba997ed7c1b598d0802204fe8d337e7fee781f9b7b1a06e580b22f4f79d740059560191d7db53f876555201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with two outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651180 - # base commitment transaction fee = 6987454 - # actual commitment transaction fee = 6999454 - # to_local amount 546 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd - # local_signature = 30440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b9 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311004004730440220514f977bf7edc442de8ce43ace9686e5ebdc0f893033f13e40fb46c8b8c6e1f90220188006227d175f5c35da0b092c57bea82537aed89f7778204dc5bacf4f29f2b901473044022037f83ff00c8e5fb18ae1f918ffc24e54581775a20ff1ae719297ef066c71caa9022039c529cccd89ff6c5ed1db799614533844bd6d101da503761c45c713996e3bbd01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with one output untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651181 - # base commitment transaction fee = 6987455 - # actual commitment transaction fee = 7000000 - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e - # local_signature = 3044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b1 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with fee greater than funder amount - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651936 - # base commitment transaction fee = 6988001 - # actual commitment transaction fee = 7000000 - # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = 3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e - # local_signature = 3044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b1 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage - to_local_msat: 6987999999 - to_remote_msat: 3000000000 - local_feerate_per_kw: 253 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #5 offered amount 5000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868 - # HTLC #6 offered amount 5000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868 - # HTLC #5 and 6 have CLTV 506 and 505, respectively, and preimage 0505050505050505050505050505050505050505050505050505050505050505 - remote_signature = 304402206cda85b2811a211aa70fb74abf23303d87c4355ccf2c2c7954d4137c4fb26a830220719402ab3fef1cbaf42ba42fe437e9bed1e45f84547d603bf7af3fb88f501933 - # local_signature = 3045022100d25455151be075bae8b3400d0825341a3c25a1a5258b84ad2546c09539a83bc602203c1a4ac19c3ac415af7f6a98348f8d7e94fc0ab82dbed54c3c40134f465d027f - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2d8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121b8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121bc0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110a69f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100d25455151be075bae8b3400d0825341a3c25a1a5258b84ad2546c09539a83bc602203c1a4ac19c3ac415af7f6a98348f8d7e94fc0ab82dbed54c3c40134f465d027f0147304402206cda85b2811a211aa70fb74abf23303d87c4355ccf2c2c7954d4137c4fb26a830220719402ab3fef1cbaf42ba42fe437e9bed1e45f84547d603bf7af3fb88f50193301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-success for htlc #1) - remote_htlc_signature = 30450221008117770f1d750a8af7a23f27c51a22f66386ea8f074115d0feeac7fe0f077f6102203d4d8dab53ef695b634786224ae5138c81d223b0493fd4637a448e31f734bb30 - # local_htlc_signature = 3045022100f2868e4380ac389960b96a8d01bd0d7ae845d24ad67993f1680e207864d5d3ae0220779ad0a943f73951d628a4f931c3044c51d393de1ab6f8283df41b68485f2b1b - htlc_success_tx (htlc #1): 0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb9000000000000000000011f070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221008117770f1d750a8af7a23f27c51a22f66386ea8f074115d0feeac7fe0f077f6102203d4d8dab53ef695b634786224ae5138c81d223b0493fd4637a448e31f734bb3001483045022100f2868e4380ac389960b96a8d01bd0d7ae845d24ad67993f1680e207864d5d3ae0220779ad0a943f73951d628a4f931c3044c51d393de1ab6f8283df41b68485f2b1b012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #6) - remote_htlc_signature = 30440220620b37379e587447e7ee9c8eed9e71b2d2ec29cadc89a4b5411ee6f9b013ca1b02201aaf26d1a03d901425fc44562bff77d5645cb54498e4d9a56c39456825974a3d - # local_htlc_signature = 304402204db4a1266aa8f0df66d35cd8b4269feef47f1cccebf7fdbb2b64cef35d72d79f022021b6d270a6d623cfec4cd0186096596d8deb17505d8c1c9785c719e0dbfa7e84 - htlc_timeout_tx (htlc #6): 0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb901000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220620b37379e587447e7ee9c8eed9e71b2d2ec29cadc89a4b5411ee6f9b013ca1b02201aaf26d1a03d901425fc44562bff77d5645cb54498e4d9a56c39456825974a3d0147304402204db4a1266aa8f0df66d35cd8b4269feef47f1cccebf7fdbb2b64cef35d72d79f022021b6d270a6d623cfec4cd0186096596d8deb17505d8c1c9785c719e0dbfa7e8401008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868f9010000 - # signature for output #2 (htlc-timeout for htlc #5) - remote_htlc_signature = 30450221009353b1d7e6313dc57a3781671b7ed9dde668b30e6f438ff08b13580937264de202200654599d2b9839fe9c88553f7c164a517fc8f0892e942b615460325fee7e0747 - # local_htlc_signature = 3045022100c11a3f26356524b556bdfaeeae80f3cdef2cdb1c42c49047014a56af6916e2cb0220230d0667dc5b86015056a2ae5158ff29cbaf9cde419677e058d23c59a0a07d31 - htlc_timeout_tx (htlc #5): 0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb902000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221009353b1d7e6313dc57a3781671b7ed9dde668b30e6f438ff08b13580937264de202200654599d2b9839fe9c88553f7c164a517fc8f0892e942b615460325fee7e074701483045022100c11a3f26356524b556bdfaeeae80f3cdef2cdb1c42c49047014a56af6916e2cb0220230d0667dc5b86015056a2ae5158ff29cbaf9cde419677e058d23c59a0a07d3101008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868fa010000 diff --git a/eclair-core/src/test/resources/bolt3-tx-test-vectors-static-remotekey-format.txt b/eclair-core/src/test/resources/bolt3-tx-test-vectors-static-remotekey-format.txt deleted file mode 100644 index 52a450bb5a..0000000000 --- a/eclair-core/src/test/resources/bolt3-tx-test-vectors-static-remotekey-format.txt +++ /dev/null @@ -1,367 +0,0 @@ - name: simple commitment tx with no HTLCs - to_local_msat: 7000000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 15000 - # base commitment transaction fee = 10860 - # actual commitment transaction fee = 10860 - # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 3045022100c3127b33dcc741dd6b05b1e63cbd1a9a7d816f37af9b6756fa2376b056f032370220408b96279808fe57eb7e463710804cdf4f108388bc5cf722d8c848d2c7f9f3b0 - # local_signature = 30440220616210b2cc4d3afb601013c373bbd8aac54febd9f15400379a8cb65ce7deca60022034236c010991beb7ff770510561ae8dc885b8d38d1947248c38f2ae055647142 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e48454a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220616210b2cc4d3afb601013c373bbd8aac54febd9f15400379a8cb65ce7deca60022034236c010991beb7ff770510561ae8dc885b8d38d1947248c38f2ae05564714201483045022100c3127b33dcc741dd6b05b1e63cbd1a9a7d816f37af9b6756fa2376b056f032370220408b96279808fe57eb7e463710804cdf4f108388bc5cf722d8c848d2c7f9f3b001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with all five HTLCs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 0 - # base commitment transaction fee = 0 - # actual commitment transaction fee = 0 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #0 received amount 1000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6988000 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 3044022009b048187705a8cbc9ad73adbe5af148c3d012e1f067961486c822c7af08158c022006d66f3704cfab3eb2dc49dae24e4aa22a6910fc9b424007583204e3621af2e5 - # local_signature = 304402206fc2d1f10ea59951eefac0b4b7c396a3c3d87b71ff0b019796ef4535beaf36f902201765b0181e514d04f4c8ad75659d7037be26cdb3f8bb6f78fe61decef484c3ea - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402206fc2d1f10ea59951eefac0b4b7c396a3c3d87b71ff0b019796ef4535beaf36f902201765b0181e514d04f4c8ad75659d7037be26cdb3f8bb6f78fe61decef484c3ea01473044022009b048187705a8cbc9ad73adbe5af148c3d012e1f067961486c822c7af08158c022006d66f3704cfab3eb2dc49dae24e4aa22a6910fc9b424007583204e3621af2e501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 5 - # signature for output #0 (htlc-success for htlc #0) - remote_htlc_signature = 3045022100d9e29616b8f3959f1d3d7f7ce893ffedcdc407717d0de8e37d808c91d3a7c50d022078c3033f6d00095c8720a4bc943c1b45727818c082e4e3ddbc6d3116435b624b - # local_htlc_signature = 30440220636de5682ef0c5b61f124ec74e8aa2461a69777521d6998295dcea36bc3338110220165285594b23c50b28b82df200234566628a27bcd17f7f14404bd865354eb3ce - htlc_success_tx (htlc #0): 02000000000101ab84ff284f162cfbfef241f853b47d4368d171f9e2a1445160cd591c4c7d882b00000000000000000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d9e29616b8f3959f1d3d7f7ce893ffedcdc407717d0de8e37d808c91d3a7c50d022078c3033f6d00095c8720a4bc943c1b45727818c082e4e3ddbc6d3116435b624b014730440220636de5682ef0c5b61f124ec74e8aa2461a69777521d6998295dcea36bc3338110220165285594b23c50b28b82df200234566628a27bcd17f7f14404bd865354eb3ce012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #2) - remote_htlc_signature = 30440220649fe8b20e67e46cbb0d09b4acea87dbec001b39b08dee7bdd0b1f03922a8640022037c462dff79df501cecfdb12ea7f4de91f99230bb544726f6e04527b1f896004 - # local_htlc_signature = 3045022100803159dee7935dba4a1d36a61055ce8fd62caa528573cc221ae288515405a252022029c59e7cffce374fe860100a4a63787e105c3cf5156d40b12dd53ff55ac8cf3f - htlc_timeout_tx (htlc #2): 02000000000101ab84ff284f162cfbfef241f853b47d4368d171f9e2a1445160cd591c4c7d882b01000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220649fe8b20e67e46cbb0d09b4acea87dbec001b39b08dee7bdd0b1f03922a8640022037c462dff79df501cecfdb12ea7f4de91f99230bb544726f6e04527b1f89600401483045022100803159dee7935dba4a1d36a61055ce8fd62caa528573cc221ae288515405a252022029c59e7cffce374fe860100a4a63787e105c3cf5156d40b12dd53ff55ac8cf3f01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #2 (htlc-success for htlc #1) - remote_htlc_signature = 30440220770fc321e97a19f38985f2e7732dd9fe08d16a2efa4bcbc0429400a447faf49102204d40b417f3113e1b0944ae0986f517564ab4acd3d190503faf97a6e420d43352 - # local_htlc_signature = 3045022100a437cc2ce77400ecde441b3398fea3c3ad8bdad8132be818227fe3c5b8345989022069d45e7fa0ae551ec37240845e2c561ceb2567eacf3076a6a43a502d05865faa - htlc_success_tx (htlc #1): 02000000000101ab84ff284f162cfbfef241f853b47d4368d171f9e2a1445160cd591c4c7d882b02000000000000000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220770fc321e97a19f38985f2e7732dd9fe08d16a2efa4bcbc0429400a447faf49102204d40b417f3113e1b0944ae0986f517564ab4acd3d190503faf97a6e420d4335201483045022100a437cc2ce77400ecde441b3398fea3c3ad8bdad8132be818227fe3c5b8345989022069d45e7fa0ae551ec37240845e2c561ceb2567eacf3076a6a43a502d05865faa012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #3 (htlc-timeout for htlc #3) - remote_htlc_signature = 304402207bcbf4f60a9829b05d2dbab84ed593e0291836be715dc7db6b72a64caf646af802201e489a5a84f7c5cc130398b841d138d031a5137ac8f4c49c770a4959dc3c1363 - # local_htlc_signature = 304402203121d9b9c055f354304b016a36662ee99e1110d9501cb271b087ddb6f382c2c80220549882f3f3b78d9c492de47543cb9a697cecc493174726146536c5954dac7487 - htlc_timeout_tx (htlc #3): 02000000000101ab84ff284f162cfbfef241f853b47d4368d171f9e2a1445160cd591c4c7d882b03000000000000000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207bcbf4f60a9829b05d2dbab84ed593e0291836be715dc7db6b72a64caf646af802201e489a5a84f7c5cc130398b841d138d031a5137ac8f4c49c770a4959dc3c13630147304402203121d9b9c055f354304b016a36662ee99e1110d9501cb271b087ddb6f382c2c80220549882f3f3b78d9c492de47543cb9a697cecc493174726146536c5954dac748701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #4 (htlc-success for htlc #4) - remote_htlc_signature = 3044022076dca5cb81ba7e466e349b7128cdba216d4d01659e29b96025b9524aaf0d1899022060de85697b88b21c749702b7d2cfa7dfeaa1f472c8f1d7d9c23f2bf968464b87 - # local_htlc_signature = 3045022100d9080f103cc92bac15ec42464a95f070c7fb6925014e673ee2ea1374d36a7f7502200c65294d22eb20d48564954d5afe04a385551919d8b2ddb4ae2459daaeee1d95 - htlc_success_tx (htlc #4): 02000000000101ab84ff284f162cfbfef241f853b47d4368d171f9e2a1445160cd591c4c7d882b04000000000000000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022076dca5cb81ba7e466e349b7128cdba216d4d01659e29b96025b9524aaf0d1899022060de85697b88b21c749702b7d2cfa7dfeaa1f472c8f1d7d9c23f2bf968464b8701483045022100d9080f103cc92bac15ec42464a95f070c7fb6925014e673ee2ea1374d36a7f7502200c65294d22eb20d48564954d5afe04a385551919d8b2ddb4ae2459daaeee1d95012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with seven outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 647 - # base commitment transaction fee = 1024 - # actual commitment transaction fee = 1024 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #0 received amount 1000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6986976 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 3045022100a135f9e8a5ed25f7277446c67956b00ce6f610ead2bdec2c2f686155b7814772022059f1f6e1a8b336a68efcc1af3fe4d422d4827332b5b067501b099c47b7b5b5ee - # local_signature = 30450221009ec15c687898bb4da8b3a833e5ab8bfc51ec6e9202aaa8e66611edfd4a85ed1102203d7183e45078b9735c93450bc3415d3e5a8c576141a711ec6ddcb4a893926bb7 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484e09c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221009ec15c687898bb4da8b3a833e5ab8bfc51ec6e9202aaa8e66611edfd4a85ed1102203d7183e45078b9735c93450bc3415d3e5a8c576141a711ec6ddcb4a893926bb701483045022100a135f9e8a5ed25f7277446c67956b00ce6f610ead2bdec2c2f686155b7814772022059f1f6e1a8b336a68efcc1af3fe4d422d4827332b5b067501b099c47b7b5b5ee01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 5 - # signature for output #0 (htlc-success for htlc #0) - remote_htlc_signature = 30450221008437627f9ad84ac67052e2a414a4367b8556fd1f94d8b02590f89f50525cd33502205b9c21ff6e7fc864f2352746ad8ba59182510819acb644e25b8a12fc37bbf24f - # local_htlc_signature = 30440220344b0deb055230d01703e6c7acd45853c4af2328b49b5d8af4f88a060733406602202ea64f2a43d5751edfe75503cbc35a62e3141b5ed032fa03360faf4ca66f670b - htlc_success_tx (htlc #0): 020000000001012cfb3e4788c206881d38f2996b6cb2109b5935acb527d14bdaa7b908afa9b2fe0000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221008437627f9ad84ac67052e2a414a4367b8556fd1f94d8b02590f89f50525cd33502205b9c21ff6e7fc864f2352746ad8ba59182510819acb644e25b8a12fc37bbf24f014730440220344b0deb055230d01703e6c7acd45853c4af2328b49b5d8af4f88a060733406602202ea64f2a43d5751edfe75503cbc35a62e3141b5ed032fa03360faf4ca66f670b012000000000000000000000000000000000000000000000000000000000000000008a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #2) - remote_htlc_signature = 304402205a67f92bf6845cf2892b48d874ac1daf88a36495cf8a06f93d83180d930a6f75022031da1621d95c3f335cc06a3056cf960199dae600b7cf89088f65fc53cdbef28c - # local_htlc_signature = 30450221009e5e3822b0185c6799a95288c597b671d6cc69ab80f43740f00c6c3d0752bdda02206da947a74bd98f3175324dc56fdba86cc783703a120a6f0297537e60632f4c7f - htlc_timeout_tx (htlc #2): 020000000001012cfb3e4788c206881d38f2996b6cb2109b5935acb527d14bdaa7b908afa9b2fe0100000000000000000124060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402205a67f92bf6845cf2892b48d874ac1daf88a36495cf8a06f93d83180d930a6f75022031da1621d95c3f335cc06a3056cf960199dae600b7cf89088f65fc53cdbef28c014830450221009e5e3822b0185c6799a95288c597b671d6cc69ab80f43740f00c6c3d0752bdda02206da947a74bd98f3175324dc56fdba86cc783703a120a6f0297537e60632f4c7f01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #2 (htlc-success for htlc #1) - remote_htlc_signature = 30440220437e21766054a3eef7f65690c5bcfa9920babbc5af92b819f772f6ea96df6c7402207173622024bd97328cfb26c6665e25c2f5d67c319443ccdc60c903217005d8c8 - # local_htlc_signature = 3045022100fcfc47e36b712624677626cef3dc1d67f6583bd46926a6398fe6b00b0c9a37760220525788257b187fc775c6370d04eadf34d06f3650a63f8df851cee0ecb47a1673 - htlc_success_tx (htlc #1): 020000000001012cfb3e4788c206881d38f2996b6cb2109b5935acb527d14bdaa7b908afa9b2fe020000000000000000010a060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220437e21766054a3eef7f65690c5bcfa9920babbc5af92b819f772f6ea96df6c7402207173622024bd97328cfb26c6665e25c2f5d67c319443ccdc60c903217005d8c801483045022100fcfc47e36b712624677626cef3dc1d67f6583bd46926a6398fe6b00b0c9a37760220525788257b187fc775c6370d04eadf34d06f3650a63f8df851cee0ecb47a1673012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #3 (htlc-timeout for htlc #3) - remote_htlc_signature = 304402207436e10737e4df499fc051686d3e11a5bb2310e4d1f1e691d287cef66514791202207cb58e71a6b7a42dd001b7e3ae672ea4f71ea3e1cd412b742e9124abb0739c64 - # local_htlc_signature = 3045022100e78211b8409afb7255ffe37337da87f38646f1faebbdd61bc1920d69e3ead67a02201a626305adfcd16bfb7e9340928d9b6305464eab4aa4c4a3af6646e9b9f69dee - htlc_timeout_tx (htlc #3): 020000000001012cfb3e4788c206881d38f2996b6cb2109b5935acb527d14bdaa7b908afa9b2fe030000000000000000010c0a0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207436e10737e4df499fc051686d3e11a5bb2310e4d1f1e691d287cef66514791202207cb58e71a6b7a42dd001b7e3ae672ea4f71ea3e1cd412b742e9124abb0739c6401483045022100e78211b8409afb7255ffe37337da87f38646f1faebbdd61bc1920d69e3ead67a02201a626305adfcd16bfb7e9340928d9b6305464eab4aa4c4a3af6646e9b9f69dee01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #4 (htlc-success for htlc #4) - remote_htlc_signature = 30450221009acd6a827a76bfee50806178dfe0495cd4e1d9c58279c194c7b01520fe68cb8d022024d439047c368883e570997a7d40f0b430cb5a742f507965e7d3063ae3feccca - # local_htlc_signature = 3044022048762cf546bbfe474f1536365ea7c416e3c0389d60558bc9412cb148fb6ab68202207215d7083b75c96ff9d2b08c59c34e287b66820f530b486a9aa4cdd9c347d5b9 - htlc_success_tx (htlc #4): 020000000001012cfb3e4788c206881d38f2996b6cb2109b5935acb527d14bdaa7b908afa9b2fe04000000000000000001da0d0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221009acd6a827a76bfee50806178dfe0495cd4e1d9c58279c194c7b01520fe68cb8d022024d439047c368883e570997a7d40f0b430cb5a742f507965e7d3063ae3feccca01473044022048762cf546bbfe474f1536365ea7c416e3c0389d60558bc9412cb148fb6ab68202207215d7083b75c96ff9d2b08c59c34e287b66820f530b486a9aa4cdd9c347d5b9012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with six outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 648 - # base commitment transaction fee = 914 - # actual commitment transaction fee = 1914 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6987086 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402203948f900a5506b8de36a4d8502f94f21dd84fd9c2314ab427d52feaa7a0a19f2022059b6a37a4adaa2c5419dc8aea63c6e2a2ec4c4bde46207f6dc1fcd22152fc6e5 - # local_signature = 3045022100b15f72908ba3382a34ca5b32519240a22300cc6015b6f9418635fb41f3d01d8802207adb331b9ed1575383dca0f2355e86c173802feecf8298fbea53b9d4610583e9 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e4844e9d6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100b15f72908ba3382a34ca5b32519240a22300cc6015b6f9418635fb41f3d01d8802207adb331b9ed1575383dca0f2355e86c173802feecf8298fbea53b9d4610583e90147304402203948f900a5506b8de36a4d8502f94f21dd84fd9c2314ab427d52feaa7a0a19f2022059b6a37a4adaa2c5419dc8aea63c6e2a2ec4c4bde46207f6dc1fcd22152fc6e501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 4 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100a031202f3be94678f0e998622ee95ebb6ada8da1e9a5110228b5e04a747351e4022010ca6a21e18314ed53cfaae3b1f51998552a61a468e596368829a50ce40110e0 - # local_htlc_signature = 304502210097e1873b57267730154595187a34949d3744f52933070c74757005e61ce2112e02204ecfba2aa42d4f14bdf8bad4206bb97217b702e6c433e0e1b0ce6587e6d46ec6 - htlc_timeout_tx (htlc #2): 020000000001010f44041fdfba175987cf4e6135ba2a154e3b7fb96483dc0ed5efc0678e5b6bf10000000000000000000123060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100a031202f3be94678f0e998622ee95ebb6ada8da1e9a5110228b5e04a747351e4022010ca6a21e18314ed53cfaae3b1f51998552a61a468e596368829a50ce40110e00148304502210097e1873b57267730154595187a34949d3744f52933070c74757005e61ce2112e02204ecfba2aa42d4f14bdf8bad4206bb97217b702e6c433e0e1b0ce6587e6d46ec601008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-success for htlc #1) - remote_htlc_signature = 304402202361012a634aee7835c5ecdd6413dcffa8f404b7e77364c792cff984e4ee71e90220715c5e90baa08daa45a7439b1ee4fa4843ed77b19c058240b69406606d384124 - # local_htlc_signature = 3044022019de73b00f1d818fb388e83b2c8c31f6bce35ac624e215bc12f88f9dc33edf48022006ff814bb9f700ee6abc3294e146fac3efd4f13f0005236b41c0a946ee00c9ae - htlc_success_tx (htlc #1): 020000000001010f44041fdfba175987cf4e6135ba2a154e3b7fb96483dc0ed5efc0678e5b6bf10100000000000000000109060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402202361012a634aee7835c5ecdd6413dcffa8f404b7e77364c792cff984e4ee71e90220715c5e90baa08daa45a7439b1ee4fa4843ed77b19c058240b69406606d38412401473044022019de73b00f1d818fb388e83b2c8c31f6bce35ac624e215bc12f88f9dc33edf48022006ff814bb9f700ee6abc3294e146fac3efd4f13f0005236b41c0a946ee00c9ae012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #2 (htlc-timeout for htlc #3) - remote_htlc_signature = 304402207e8e82cd71ed4febeb593732c260456836e97d81896153ecd2b3cf320ca6861702202dd4a30f68f98ced7cc56a36369ac1fdd978248c5ff4ed204fc00cc625532989 - # local_htlc_signature = 3045022100bd0be6100c4fd8f102ec220e1b053e4c4e2ecca25615490150007b40d314dc3902201a1e0ea266965b43164d9e6576f58fa6726d42883dd1c3996d2925c2e2260796 - htlc_timeout_tx (htlc #3): 020000000001010f44041fdfba175987cf4e6135ba2a154e3b7fb96483dc0ed5efc0678e5b6bf1020000000000000000010b0a0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207e8e82cd71ed4febeb593732c260456836e97d81896153ecd2b3cf320ca6861702202dd4a30f68f98ced7cc56a36369ac1fdd978248c5ff4ed204fc00cc62553298901483045022100bd0be6100c4fd8f102ec220e1b053e4c4e2ecca25615490150007b40d314dc3902201a1e0ea266965b43164d9e6576f58fa6726d42883dd1c3996d2925c2e226079601008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #3 (htlc-success for htlc #4) - remote_htlc_signature = 3044022024cd52e4198c8ae0e414a86d86b5a65ea7450f2eb4e783096736d93395eca5ce022078f0094745b45be4d4b2b04dd5978c9e66ba49109e5704403e84aaf5f387d6be - # local_htlc_signature = 3045022100bbfb9d0a946d420807c86e985d636cceb16e71c3694ed186316251a00cbd807202207773223f9a337e145f64673825be9b30d07ef1542c82188b264bedcf7cda78c6 - htlc_success_tx (htlc #4): 020000000001010f44041fdfba175987cf4e6135ba2a154e3b7fb96483dc0ed5efc0678e5b6bf103000000000000000001d90d0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022024cd52e4198c8ae0e414a86d86b5a65ea7450f2eb4e783096736d93395eca5ce022078f0094745b45be4d4b2b04dd5978c9e66ba49109e5704403e84aaf5f387d6be01483045022100bbfb9d0a946d420807c86e985d636cceb16e71c3694ed186316251a00cbd807202207773223f9a337e145f64673825be9b30d07ef1542c82188b264bedcf7cda78c6012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with six outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2069 - # base commitment transaction fee = 2921 - # actual commitment transaction fee = 3921 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985079 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304502210090b96a2498ce0c0f2fadbec2aab278fed54c1a7838df793ec4d2c78d96ec096202204fdd439c50f90d483baa7b68feeef4bd33bc277695405447bcd0bfb2ca34d7bc - # local_signature = 3045022100ad9a9bbbb75d506ca3b716b336ee3cf975dd7834fcf129d7dd188146eb58a8b4022061a759ee417339f7fe2ea1e8deb83abb6a74db31a09b7648a932a639cda23e33 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8006d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e48477956a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100ad9a9bbbb75d506ca3b716b336ee3cf975dd7834fcf129d7dd188146eb58a8b4022061a759ee417339f7fe2ea1e8deb83abb6a74db31a09b7648a932a639cda23e330148304502210090b96a2498ce0c0f2fadbec2aab278fed54c1a7838df793ec4d2c78d96ec096202204fdd439c50f90d483baa7b68feeef4bd33bc277695405447bcd0bfb2ca34d7bc01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 4 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100f33513ee38abf1c582876f921f8fddc06acff48e04515532a32d3938de938ffd02203aa308a2c1863b7d6fdf53159a1465bf2e115c13152546cc5d74483ceaa7f699 - # local_htlc_signature = 3045022100a637902a5d4c9ba9e7c472a225337d5aac9e2e3f6744f76e237132e7619ba0400220035c60d784a031c0d9f6df66b7eab8726a5c25397399ee4aa960842059eb3f9d - htlc_timeout_tx (htlc #2): 02000000000101adbe717a63fb658add30ada1e6e12ed257637581898abe475c11d7bbcd65bd4d0000000000000000000175020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100f33513ee38abf1c582876f921f8fddc06acff48e04515532a32d3938de938ffd02203aa308a2c1863b7d6fdf53159a1465bf2e115c13152546cc5d74483ceaa7f69901483045022100a637902a5d4c9ba9e7c472a225337d5aac9e2e3f6744f76e237132e7619ba0400220035c60d784a031c0d9f6df66b7eab8726a5c25397399ee4aa960842059eb3f9d01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-success for htlc #1) - remote_htlc_signature = 3045022100ce07682cf4b90093c22dc2d9ab2a77ad6803526b655ef857221cc96af5c9e0bf02200f501cee22e7a268af40b555d15a8237c9f36ad67ef1841daf9f6a0267b1e6df - # local_htlc_signature = 3045022100e57e46234f8782d3ff7aa593b4f7446fb5316c842e693dc63ee324fd49f6a1c302204a2f7b44c48bd26e1554422afae13153eb94b29d3687b733d18930615fb2db61 - htlc_success_tx (htlc #1): 02000000000101adbe717a63fb658add30ada1e6e12ed257637581898abe475c11d7bbcd65bd4d0100000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100ce07682cf4b90093c22dc2d9ab2a77ad6803526b655ef857221cc96af5c9e0bf02200f501cee22e7a268af40b555d15a8237c9f36ad67ef1841daf9f6a0267b1e6df01483045022100e57e46234f8782d3ff7aa593b4f7446fb5316c842e693dc63ee324fd49f6a1c302204a2f7b44c48bd26e1554422afae13153eb94b29d3687b733d18930615fb2db61012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #2 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100e3e35492e55f82ec0bc2f317ffd7a486d1f7024330fe9743c3559fc39f32ef0c02203d1d4db651fc388a91d5ad8ecdd8e83673063bc8eefe27cfd8c189090e3a23e0 - # local_htlc_signature = 3044022068613fb1b98eb3aec7f44c5b115b12343c2f066c4277c82b5f873dfe68f37f50022028109b4650f3f528ca4bfe9a467aff2e3e43893b61b5159157119d5d95cf1c18 - htlc_timeout_tx (htlc #3): 02000000000101adbe717a63fb658add30ada1e6e12ed257637581898abe475c11d7bbcd65bd4d020000000000000000015d060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100e3e35492e55f82ec0bc2f317ffd7a486d1f7024330fe9743c3559fc39f32ef0c02203d1d4db651fc388a91d5ad8ecdd8e83673063bc8eefe27cfd8c189090e3a23e001473044022068613fb1b98eb3aec7f44c5b115b12343c2f066c4277c82b5f873dfe68f37f50022028109b4650f3f528ca4bfe9a467aff2e3e43893b61b5159157119d5d95cf1c1801008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #3 (htlc-success for htlc #4) - remote_htlc_signature = 304402207475aeb0212ef9bf5130b60937817ad88c9a87976988ef1f323f026148cc4a850220739fea17ad3257dcad72e509c73eebe86bee30b178467b9fdab213d631b109df - # local_htlc_signature = 3045022100d315522e09e7d53d2a659a79cb67fef56d6c4bddf3f46df6772d0d20a7beb7c8022070bcc17e288607b6a72be0bd83368bb6d53488db266c1cdb4d72214e4f02ac33 - htlc_success_tx (htlc #4): 02000000000101adbe717a63fb658add30ada1e6e12ed257637581898abe475c11d7bbcd65bd4d03000000000000000001f2090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402207475aeb0212ef9bf5130b60937817ad88c9a87976988ef1f323f026148cc4a850220739fea17ad3257dcad72e509c73eebe86bee30b178467b9fdab213d631b109df01483045022100d315522e09e7d53d2a659a79cb67fef56d6c4bddf3f46df6772d0d20a7beb7c8022070bcc17e288607b6a72be0bd83368bb6d53488db266c1cdb4d72214e4f02ac33012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with five outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2070 - # base commitment transaction fee = 2566 - # actual commitment transaction fee = 5566 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985434 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402204ca1ba260dee913d318271d86e10ca0f5883026fb5653155cff600fb40895223022037b145204b7054a40e08bb1fefbd826f827b40838d3e501423bcc57924bcb50c - # local_signature = 3044022001014419b5ba00e083ac4e0a85f19afc848aacac2d483b4b525d15e2ae5adbfe022015ebddad6ee1e72b47cb09f3e78459da5be01ccccd95dceca0e056a00cc773c1 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484da966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022001014419b5ba00e083ac4e0a85f19afc848aacac2d483b4b525d15e2ae5adbfe022015ebddad6ee1e72b47cb09f3e78459da5be01ccccd95dceca0e056a00cc773c10147304402204ca1ba260dee913d318271d86e10ca0f5883026fb5653155cff600fb40895223022037b145204b7054a40e08bb1fefbd826f827b40838d3e501423bcc57924bcb50c01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 304402205f6b6d12d8d2529fb24f4445630566cf4abbd0f9330ab6c2bdb94222d6a2a0c502202f556258ae6f05b193749e4c541dfcc13b525a5422f6291f073f15617ba8579b - # local_htlc_signature = 30440220150b11069454da70caf2492ded9e0065c9a57f25ac2a4c52657b1d15b6c6ed85022068a38833b603c8892717206383611bad210f1cbb4b1f87ea29c6c65b9e1cb3e5 - htlc_timeout_tx (htlc #2): 02000000000101403ad7602b43293497a3a2235a12ecefda4f3a1f1d06e49b1786d945685de1ff0000000000000000000174020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402205f6b6d12d8d2529fb24f4445630566cf4abbd0f9330ab6c2bdb94222d6a2a0c502202f556258ae6f05b193749e4c541dfcc13b525a5422f6291f073f15617ba8579b014730440220150b11069454da70caf2492ded9e0065c9a57f25ac2a4c52657b1d15b6c6ed85022068a38833b603c8892717206383611bad210f1cbb4b1f87ea29c6c65b9e1cb3e501008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100f960dfb1c9aee7ce1437efa65b523e399383e8149790e05d8fed27ff6e42fe0002202fe8613e062ffe0b0c518cc4101fba1c6de70f64a5bcc7ae663f2efae43b8546 - # local_htlc_signature = 30450221009a6ed18e6873bc3644332a6ee21c152a5b102821865350df7a8c74451a51f9f2022050d801fb4895d7d7fbf452824c0168347f5c0cbe821cf6a97a63af5b8b2563c6 - htlc_timeout_tx (htlc #3): 02000000000101403ad7602b43293497a3a2235a12ecefda4f3a1f1d06e49b1786d945685de1ff010000000000000000015c060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100f960dfb1c9aee7ce1437efa65b523e399383e8149790e05d8fed27ff6e42fe0002202fe8613e062ffe0b0c518cc4101fba1c6de70f64a5bcc7ae663f2efae43b8546014830450221009a6ed18e6873bc3644332a6ee21c152a5b102821865350df7a8c74451a51f9f2022050d801fb4895d7d7fbf452824c0168347f5c0cbe821cf6a97a63af5b8b2563c601008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #2 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100ae5fc7717ae684bc1fcf9020854e5dbe9842c9e7472879ac06ff95ac2bb10e4e022057728ada4c00083a3e65493fb5d50a232165948a1a0f530ef63185c2c8c56504 - # local_htlc_signature = 30440220408ad3009827a8fccf774cb285587686bfb2ed041f89a89453c311ce9c8ee0f902203c7392d9f8306d3a46522a66bd2723a7eb2628cb2d9b34d4c104f1766bf37502 - htlc_success_tx (htlc #4): 02000000000101403ad7602b43293497a3a2235a12ecefda4f3a1f1d06e49b1786d945685de1ff02000000000000000001f1090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100ae5fc7717ae684bc1fcf9020854e5dbe9842c9e7472879ac06ff95ac2bb10e4e022057728ada4c00083a3e65493fb5d50a232165948a1a0f530ef63185c2c8c56504014730440220408ad3009827a8fccf774cb285587686bfb2ed041f89a89453c311ce9c8ee0f902203c7392d9f8306d3a46522a66bd2723a7eb2628cb2d9b34d4c104f1766bf37502012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with five outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2194 - # base commitment transaction fee = 2720 - # actual commitment transaction fee = 5720 - # HTLC #2 offered amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985280 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402204bb3d6e279d71d9da414c82de42f1f954267c762b2e2eb8b76bc3be4ea07d4b0022014febc009c5edc8c3fc5d94015de163200f780046f1c293bfed8568f08b70fb3 - # local_signature = 3044022072c2e2b1c899b2242656a537dde2892fa3801be0d6df0a87836c550137acde8302201654aa1974d37a829083c3ba15088689f30b56d6a4f6cb14c7bad0ee3116d398 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e48440966a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022072c2e2b1c899b2242656a537dde2892fa3801be0d6df0a87836c550137acde8302201654aa1974d37a829083c3ba15088689f30b56d6a4f6cb14c7bad0ee3116d3980147304402204bb3d6e279d71d9da414c82de42f1f954267c762b2e2eb8b76bc3be4ea07d4b0022014febc009c5edc8c3fc5d94015de163200f780046f1c293bfed8568f08b70fb301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-timeout for htlc #2) - remote_htlc_signature = 3045022100939726680351a7856c1bc386d4a1f422c7d29bd7b56afc139570f508474e6c40022023175a799ccf44c017fbaadb924c40b2a12115a5b7d0dfd3228df803a2de8450 - # local_htlc_signature = 304502210099c98c2edeeee6ec0fb5f3bea8b79bb016a2717afa9b5072370f34382de281d302206f5e2980a995e045cf90a547f0752a7ee99d48547bc135258fe7bc07e0154301 - htlc_timeout_tx (htlc #2): 02000000000101153cd825fdb3aa624bfe513e8031d5d08c5e582fb3d1d1fe8faf27d3eed410cd0000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100939726680351a7856c1bc386d4a1f422c7d29bd7b56afc139570f508474e6c40022023175a799ccf44c017fbaadb924c40b2a12115a5b7d0dfd3228df803a2de84500148304502210099c98c2edeeee6ec0fb5f3bea8b79bb016a2717afa9b5072370f34382de281d302206f5e2980a995e045cf90a547f0752a7ee99d48547bc135258fe7bc07e015430101008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868f6010000 - # signature for output #1 (htlc-timeout for htlc #3) - remote_htlc_signature = 3044022021bb883bf324553d085ba2e821cad80c28ef8b303dbead8f98e548783c02d1600220638f9ef2a9bba25869afc923f4b5dc38be3bb459f9efa5d869392d5f7779a4a0 - # local_htlc_signature = 3045022100fd85bd7697b89c08ec12acc8ba89b23090637d83abd26ca37e01ae93e67c367302202b551fe69386116c47f984aab9c8dfd25d864dcde5d3389cfbef2447a85c4b77 - htlc_timeout_tx (htlc #3): 02000000000101153cd825fdb3aa624bfe513e8031d5d08c5e582fb3d1d1fe8faf27d3eed410cd010000000000000000010a060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022021bb883bf324553d085ba2e821cad80c28ef8b303dbead8f98e548783c02d1600220638f9ef2a9bba25869afc923f4b5dc38be3bb459f9efa5d869392d5f7779a4a001483045022100fd85bd7697b89c08ec12acc8ba89b23090637d83abd26ca37e01ae93e67c367302202b551fe69386116c47f984aab9c8dfd25d864dcde5d3389cfbef2447a85c4b7701008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #2 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100c9e6f0454aa598b905a35e641a70cc9f67b5f38cc4b00843a041238c4a9f1c4a0220260a2822a62da97e44583e837245995ca2e36781769c52f19e498efbdcca262b - # local_htlc_signature = 30450221008a9f2ea24cd455c2b64c1472a5fa83865b0a5f49a62b661801e884cf2849af8302204d44180e50bf6adfcf1c1e581d75af91aba4e28681ce4a5ee5f3cbf65eca10f3 - htlc_success_tx (htlc #4): 02000000000101153cd825fdb3aa624bfe513e8031d5d08c5e582fb3d1d1fe8faf27d3eed410cd020000000000000000019a090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100c9e6f0454aa598b905a35e641a70cc9f67b5f38cc4b00843a041238c4a9f1c4a0220260a2822a62da97e44583e837245995ca2e36781769c52f19e498efbdcca262b014830450221008a9f2ea24cd455c2b64c1472a5fa83865b0a5f49a62b661801e884cf2849af8302204d44180e50bf6adfcf1c1e581d75af91aba4e28681ce4a5ee5f3cbf65eca10f3012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with four outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 2195 - # base commitment transaction fee = 2344 - # actual commitment transaction fee = 7344 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6985656 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402201a8c1b1f9671cd9e46c7323a104d7047cc48d3ee80d40d4512e0c72b8dc65666022066d7f9a2ce18c9eb22d2739ffcce05721c767f9b607622a31b6ea5793ddce403 - # local_signature = 3044022044d592025b610c0d678f65032e87035cdfe89d1598c522cc32524ae8172417c30220749fef9d5b2ae8cdd91ece442ba8809bc891efedae2291e578475f97715d1767 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484b8976a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022044d592025b610c0d678f65032e87035cdfe89d1598c522cc32524ae8172417c30220749fef9d5b2ae8cdd91ece442ba8809bc891efedae2291e578475f97715d17670147304402201a8c1b1f9671cd9e46c7323a104d7047cc48d3ee80d40d4512e0c72b8dc65666022066d7f9a2ce18c9eb22d2739ffcce05721c767f9b607622a31b6ea5793ddce40301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 2 - # signature for output #0 (htlc-timeout for htlc #3) - remote_htlc_signature = 3045022100e57b845066a06ee7c2cbfc29eabffe52daa9bf6f6de760066d04df9f9b250e0002202ffb197f0e6e0a77a75a9aff27014bd3de83b7f748d7efef986abe655e1dd50e - # local_htlc_signature = 3045022100ecc8c6529d0b2316d046f0f0757c1e1c25a636db168ec4f3aa1b9278df685dc0022067ae6b65e936f1337091f7b18a15935b608c5f2cdddb2f892ed0babfdd376d76 - htlc_timeout_tx (htlc #3): 020000000001018130a10f09b13677ba2885a8bca32860f3a952e5912b829a473639b5a2c07b900000000000000000000109060000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100e57b845066a06ee7c2cbfc29eabffe52daa9bf6f6de760066d04df9f9b250e0002202ffb197f0e6e0a77a75a9aff27014bd3de83b7f748d7efef986abe655e1dd50e01483045022100ecc8c6529d0b2316d046f0f0757c1e1c25a636db168ec4f3aa1b9278df685dc0022067ae6b65e936f1337091f7b18a15935b608c5f2cdddb2f892ed0babfdd376d7601008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #1 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100d193b7ecccad8057571620a0b1ffa6c48e9483311723b59cf536043b20bc51550220546d4bd37b3b101ecda14f6c907af46ec391abce1cd9c7ce22b1a62b534f2f2a - # local_htlc_signature = 3044022014d66f11f9cacf923807eba49542076c5fe5cccf252fb08fe98c78ef3ca6ab5402201b290dbe043cc512d9d78de074a5a129b8759bc6a6c546b190d120b690bd6e82 - htlc_success_tx (htlc #4): 020000000001018130a10f09b13677ba2885a8bca32860f3a952e5912b829a473639b5a2c07b900100000000000000000199090000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100d193b7ecccad8057571620a0b1ffa6c48e9483311723b59cf536043b20bc51550220546d4bd37b3b101ecda14f6c907af46ec391abce1cd9c7ce22b1a62b534f2f2a01473044022014d66f11f9cacf923807eba49542076c5fe5cccf252fb08fe98c78ef3ca6ab5402201b290dbe043cc512d9d78de074a5a129b8759bc6a6c546b190d120b690bd6e82012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with four outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 3702 - # base commitment transaction fee = 3953 - # actual commitment transaction fee = 8953 - # HTLC #3 offered amount 3000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6984047 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304502210092a587aeb777f869e7ff0d7898ea619ee26a3dacd1f3672b945eea600be431100220077ee9eae3528d15251f2a52b607b189820e57a6ccfac8d1af502b132ee40169 - # local_signature = 3045022100e5efb73c32d32da2d79702299b6317de6fb24a60476e3855926d78484dd1b3c802203557cb66a42c944ef06e00bcc4da35a5bcb2f185aab0f8e403e519e1d66aaf75 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8004b80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e4846f916a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100e5efb73c32d32da2d79702299b6317de6fb24a60476e3855926d78484dd1b3c802203557cb66a42c944ef06e00bcc4da35a5bcb2f185aab0f8e403e519e1d66aaf750148304502210092a587aeb777f869e7ff0d7898ea619ee26a3dacd1f3672b945eea600be431100220077ee9eae3528d15251f2a52b607b189820e57a6ccfac8d1af502b132ee4016901475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 2 - # signature for output #0 (htlc-timeout for htlc #3) - remote_htlc_signature = 304402206fa54c11f98c3bae1e93df43fc7affeb05b476bf8060c03e29c377c69bc08e8b0220672701cce50d5c379ff45a5d2cfe48ac44973adb066ac32608e21221d869bb89 - # local_htlc_signature = 304402206e36c683ebf2cb16bcef3d5439cf8b53cd97280a365ed8acd7abb85a8ba5f21c02206e8621edfc2a5766cbc96eb67fd501127ff163eb6b85518a39f7d4974aef126f - htlc_timeout_tx (htlc #3): 020000000001018db483bff65c70ee71d8282aeec5a880e2e2b39e45772bda5460403095c62e3f0000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206fa54c11f98c3bae1e93df43fc7affeb05b476bf8060c03e29c377c69bc08e8b0220672701cce50d5c379ff45a5d2cfe48ac44973adb066ac32608e21221d869bb890147304402206e36c683ebf2cb16bcef3d5439cf8b53cd97280a365ed8acd7abb85a8ba5f21c02206e8621edfc2a5766cbc96eb67fd501127ff163eb6b85518a39f7d4974aef126f01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6868f7010000 - # signature for output #1 (htlc-success for htlc #4) - remote_htlc_signature = 3044022057649739b0eb74d541ead0dfdb3d4b2c15aa192720031044c3434c67812e5ca902201e5ede42d960ae551707f4a6b34b09393cf4dee2418507daa022e3550dbb5817 - # local_htlc_signature = 304402207faad26678c8850e01b4a0696d60841f7305e1832b786110ee9075cb92ed14a30220516ef8ee5dfa80824ea28cbcec0dd95f8b847146257c16960db98507db15ffdc - htlc_success_tx (htlc #4): 020000000001018db483bff65c70ee71d8282aeec5a880e2e2b39e45772bda5460403095c62e3f0100000000000000000176050000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022057649739b0eb74d541ead0dfdb3d4b2c15aa192720031044c3434c67812e5ca902201e5ede42d960ae551707f4a6b34b09393cf4dee2418507daa022e3550dbb58170147304402207faad26678c8850e01b4a0696d60841f7305e1832b786110ee9075cb92ed14a30220516ef8ee5dfa80824ea28cbcec0dd95f8b847146257c16960db98507db15ffdc012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with three outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 3703 - # base commitment transaction fee = 3317 - # actual commitment transaction fee = 11317 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6984683 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 3045022100b495d239772a237ff2cf354b1b11be152fd852704cb184e7356d13f2fb1e5e430220723db5cdb9cbd6ead7bfd3deb419cf41053a932418cbb22a67b581f40bc1f13e - # local_signature = 304402201b736d1773a124c745586217a75bed5f66c05716fbe8c7db4fdb3c3069741cdd02205083f39c321c1bcadfc8d97e3c791a66273d936abac0c6a2fde2ed46019508e1 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484eb936a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402201b736d1773a124c745586217a75bed5f66c05716fbe8c7db4fdb3c3069741cdd02205083f39c321c1bcadfc8d97e3c791a66273d936abac0c6a2fde2ed46019508e101483045022100b495d239772a237ff2cf354b1b11be152fd852704cb184e7356d13f2fb1e5e430220723db5cdb9cbd6ead7bfd3deb419cf41053a932418cbb22a67b581f40bc1f13e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 1 - # signature for output #0 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100c34c61735f93f2e324cc873c3b248111ccf8f6db15d5969583757010d4ad2b4602207867bb919b2ddd6387873e425345c9b7fd18d1d66aba41f3607bc2896ef3c30a - # local_htlc_signature = 3045022100988c143e2110067117d2321bdd4bd16ca1734c98b29290d129384af0962b634e02206c1b02478878c5f547018b833986578f90c3e9be669fe5788ad0072a55acbb05 - htlc_success_tx (htlc #4): 0200000000010120060e4a29579d429f0f27c17ee5f1ee282f20d706d6f90b63d35946d8f3029a0000000000000000000175050000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100c34c61735f93f2e324cc873c3b248111ccf8f6db15d5969583757010d4ad2b4602207867bb919b2ddd6387873e425345c9b7fd18d1d66aba41f3607bc2896ef3c30a01483045022100988c143e2110067117d2321bdd4bd16ca1734c98b29290d129384af0962b634e02206c1b02478878c5f547018b833986578f90c3e9be669fe5788ad0072a55acbb05012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with three outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 4914 - # base commitment transaction fee = 4402 - # actual commitment transaction fee = 12402 - # HTLC #4 received amount 4000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6868 - # to_local amount 6983598 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 3045022100b4b16d5f8cc9fc4c1aff48831e832a0d8990e133978a66e302c133550954a44d022073573ce127e2200d316f6b612803a5c0c97b8d20e1e44dbe2ac0dd2fb8c95244 - # local_signature = 3045022100d72638bc6308b88bb6d45861aae83e5b9ff6e10986546e13bce769c70036e2620220320be7c6d66d22f30b9fcd52af66531505b1310ca3b848c19285b38d8a1a8c19 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8003a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484ae8f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100d72638bc6308b88bb6d45861aae83e5b9ff6e10986546e13bce769c70036e2620220320be7c6d66d22f30b9fcd52af66531505b1310ca3b848c19285b38d8a1a8c1901483045022100b4b16d5f8cc9fc4c1aff48831e832a0d8990e133978a66e302c133550954a44d022073573ce127e2200d316f6b612803a5c0c97b8d20e1e44dbe2ac0dd2fb8c9524401475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 1 - # signature for output #0 (htlc-success for htlc #4) - remote_htlc_signature = 3045022100f43591c156038ba217756006bb3c55f7d113a325cdd7d9303c82115372858d68022016355b5aadf222bc8d12e426c75f4a03423917b2443a103eb2a498a3a2234374 - # local_htlc_signature = 30440220585dee80fafa264beac535c3c0bb5838ac348b156fdc982f86adc08dfc9bfd250220130abb82f9f295cc9ef423dcfef772fde2acd85d9df48cc538981d26a10a9c10 - htlc_success_tx (htlc #4): 02000000000101a9172908eace869cc35128c31fc2ab502f72e4dff31aab23e0244c4b04b11ab00000000000000000000122020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100f43591c156038ba217756006bb3c55f7d113a325cdd7d9303c82115372858d68022016355b5aadf222bc8d12e426c75f4a03423917b2443a103eb2a498a3a2234374014730440220585dee80fafa264beac535c3c0bb5838ac348b156fdc982f86adc08dfc9bfd250220130abb82f9f295cc9ef423dcfef772fde2acd85d9df48cc538981d26a10a9c10012004040404040404040404040404040404040404040404040404040404040404048a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac686800000000 - - name: commitment tx with two outputs untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 4915 - # base commitment transaction fee = 3558 - # actual commitment transaction fee = 15558 - # to_local amount 6984442 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402203a286936e74870ca1459c700c71202af0381910a6bfab687ef494ef1bc3e02c902202506c362d0e3bee15e802aa729bf378e051644648253513f1c085b264cc2a720 - # local_signature = 30450221008a953551f4d67cb4df3037207fc082ddaf6be84d417b0bd14c80aab66f1b01a402207508796dc75034b2dee876fe01dc05a08b019f3e5d689ac8842ade2f1befccf5 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484fa926a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221008a953551f4d67cb4df3037207fc082ddaf6be84d417b0bd14c80aab66f1b01a402207508796dc75034b2dee876fe01dc05a08b019f3e5d689ac8842ade2f1befccf50147304402203a286936e74870ca1459c700c71202af0381910a6bfab687ef494ef1bc3e02c902202506c362d0e3bee15e802aa729bf378e051644648253513f1c085b264cc2a72001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with two outputs untrimmed (maximum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651180 - # base commitment transaction fee = 6987454 - # actual commitment transaction fee = 6999454 - # to_local amount 546 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd3 - # local_signature = 3045022100e11b638c05c650c2f63a421d36ef8756c5ce82f2184278643520311cdf50aa200220259565fb9c8e4a87ccaf17f27a3b9ca4f20625754a0920d9c6c239d8156a11de - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b800222020000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80ec0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e4840400483045022100e11b638c05c650c2f63a421d36ef8756c5ce82f2184278643520311cdf50aa200220259565fb9c8e4a87ccaf17f27a3b9ca4f20625754a0920d9c6c239d8156a11de0147304402200a8544eba1d216f5c5e530597665fa9bec56943c0f66d98fc3d028df52d84f7002201e45fa5c6bc3a506cc2553e7d1c0043a9811313fc39c954692c0d47cfce2bbd301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with one output untrimmed (minimum feerate) - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651181 - # base commitment transaction fee = 6987455 - # actual commitment transaction fee = 7000000 - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2 - # local_signature = 304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a379 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484040047304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a3790147304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with fee greater than funder amount - to_local_msat: 6988000000 - to_remote_msat: 3000000000 - local_feerate_per_kw: 9651936 - # base commitment transaction fee = 6988001 - # actual commitment transaction fee = 7000000 - # to_remote amount 3000000 P2WPKH(032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991) - remote_signature = 304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a2 - # local_signature = 304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a379 - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484040047304402207e8d51e0c570a5868a78414f4e0cbfaed1106b171b9581542c30718ee4eb95ba02203af84194c97adf98898c9afe2f2ed4a7f8dba05a2dfab28ac9d9c604aa49a3790147304402202ade0142008309eb376736575ad58d03e5b115499709c6db0b46e36ff394b492022037b63d78d66404d6504d4c4ac13be346f3d1802928a6d3ad95a6a944227161a201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 0 - - name: commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage - to_local_msat: 6987999999 - to_remote_msat: 3000000000 - local_feerate_per_kw: 253 - # HTLC #1 received amount 2000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868 - # HTLC #5 offered amount 5000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868 - # HTLC #6 offered amount 5000 wscript 76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868 - # HTLC #5 and 6 have CLTV 506 and 505, respectively, and preimage 0505050505050505050505050505050505050505050505050505050505050505 - remote_signature = 304402207d0870964530f97b62497b11153c551dca0a1e226815ef0a336651158da0f82402200f5378beee0e77759147b8a0a284decd11bfd2bc55c8fafa41c134fe996d43c8 - # local_signature = 304402200d10bf5bc5397fc59d7188ae438d80c77575595a2d488e41bd6363a810cc8d72022012b57e714fbbfdf7a28c47d5b370cb8ac37c8545f596216e5b21e9b236ef457c - output commit_tx: 02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2d8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121b8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121bc0c62d0000000000160014cc1b07838e387deacd0e5232e1e8b49f4c29e484a69f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e040047304402200d10bf5bc5397fc59d7188ae438d80c77575595a2d488e41bd6363a810cc8d72022012b57e714fbbfdf7a28c47d5b370cb8ac37c8545f596216e5b21e9b236ef457c0147304402207d0870964530f97b62497b11153c551dca0a1e226815ef0a336651158da0f82402200f5378beee0e77759147b8a0a284decd11bfd2bc55c8fafa41c134fe996d43c801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220 - num_htlcs: 3 - # signature for output #0 (htlc-success for htlc #1) - remote_htlc_signature = 3045022100b470fe12e5b7fea9eccb8cbff1972cea4f96758041898982a02bcc7f9d56d50b0220338a75b2afaab4ec00cdd2d9273c68c7581ff5a28bcbb40c4d138b81f1d45ce5 - # local_htlc_signature = 3044022017b90c65207522a907fb6a137f9dd528b3389465a8ae72308d9e1d564f512cf402204fc917b4f0e88604a3e994f85bfae7c7c1f9d9e9f78e8cd112e0889720d9405b - htlc_success_tx (htlc #1): 020000000001014bdccf28653066a2c554cafeffdfe1e678e64a69b056684deb0c4fba909423ec000000000000000000011f070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100b470fe12e5b7fea9eccb8cbff1972cea4f96758041898982a02bcc7f9d56d50b0220338a75b2afaab4ec00cdd2d9273c68c7581ff5a28bcbb40c4d138b81f1d45ce501473044022017b90c65207522a907fb6a137f9dd528b3389465a8ae72308d9e1d564f512cf402204fc917b4f0e88604a3e994f85bfae7c7c1f9d9e9f78e8cd112e0889720d9405b012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000 - # signature for output #1 (htlc-timeout for htlc #6) - remote_htlc_signature = 3045022100b575379f6d8743cb0087648f81cfd82d17a97fbf8f67e058c65ce8b9d25df9500220554a210d65b02d9f36c6adf0f639430ca8293196ba5089bf67cc3a9813b7b00a - # local_htlc_signature = 3045022100ee2e16b90930a479b13f8823a7f14b600198c838161160b9436ed086d3fc57e002202a66fa2324f342a17129949c640bfe934cbc73a869ba7c06aa25c5a3d0bfb53d - htlc_timeout_tx (htlc #6): 020000000001014bdccf28653066a2c554cafeffdfe1e678e64a69b056684deb0c4fba909423ec01000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100b575379f6d8743cb0087648f81cfd82d17a97fbf8f67e058c65ce8b9d25df9500220554a210d65b02d9f36c6adf0f639430ca8293196ba5089bf67cc3a9813b7b00a01483045022100ee2e16b90930a479b13f8823a7f14b600198c838161160b9436ed086d3fc57e002202a66fa2324f342a17129949c640bfe934cbc73a869ba7c06aa25c5a3d0bfb53d01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868f9010000 - # signature for output #2 (htlc-timeout for htlc #5) - remote_htlc_signature = 30440220471c9f3ad92e49b13b7b8059f43ecf8f7887b0dccbb9fdb54bfe23d62a8ae332022024bd22fae0740e86a44228c35330da9526fd7306dffb2b9dc362d5e78abef7cc - # local_htlc_signature = 304402207157f452f2506d73c315192311893800cfb3cc235cc1185b1cfcc136b55230db022014be242dbc6c5da141fec4034e7f387f74d6ff1899453d72ba957467540e1ecb - htlc_timeout_tx (htlc #5): 020000000001014bdccf28653066a2c554cafeffdfe1e678e64a69b056684deb0c4fba909423ec02000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220471c9f3ad92e49b13b7b8059f43ecf8f7887b0dccbb9fdb54bfe23d62a8ae332022024bd22fae0740e86a44228c35330da9526fd7306dffb2b9dc362d5e78abef7cc0147304402207157f452f2506d73c315192311893800cfb3cc235cc1185b1cfcc136b55230db022014be242dbc6c5da141fec4034e7f387f74d6ff1899453d72ba957467540e1ecb01008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868fa010000 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 6623043044..6ae9b7dc87 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -107,10 +107,10 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I assert(open.fundingTxFeerate_opt.contains(FeeratePerKw(1250 sat))) // check that minimum fee rate of 253 sat/bw is used - eclair.open(nodeId, fundingAmount = 10000000L sat, pushAmount_opt = None, channelType_opt = Some(ChannelTypes.StaticRemoteKey()), fundingFeerate_opt = Some(FeeratePerByte(1 sat)), fundingFeeBudget_opt = None, announceChannel_opt = None, openTimeout_opt = None) + eclair.open(nodeId, fundingAmount = 10000000L sat, pushAmount_opt = None, channelType_opt = Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), fundingFeerate_opt = Some(FeeratePerByte(1 sat)), fundingFeeBudget_opt = None, announceChannel_opt = None, openTimeout_opt = None) val open1 = switchboard.expectMsgType[OpenChannel] assert(open1.fundingTxFeerate_opt.contains(FeeratePerKw.MinimumFeeratePerKw)) - assert(open1.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) + assert(open1.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) } test("call send with passing correct arguments") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 433d97a3ff..b939873da8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -29,8 +29,8 @@ import fr.acinq.eclair.message.OnionMessages.OnionMessageConfig import fr.acinq.eclair.payment.offer.OffersConfig import fr.acinq.eclair.payment.relay.OnTheFlyFunding import fr.acinq.eclair.payment.relay.Relayer.{AsyncPaymentsParams, RelayFees, RelayParams} -import fr.acinq.eclair.router.Graph.{MessageWeightRatios, HeuristicsConstants} import fr.acinq.eclair.reputation.Reputation +import fr.acinq.eclair.router.Graph.{HeuristicsConstants, MessageWeightRatios} import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.router.{PathFindingExperimentConf, Router} import fr.acinq.eclair.wire.protocol._ @@ -110,7 +110,9 @@ object TestConstants { Features.Wumbo -> FeatureSupport.Optional, Features.PaymentMetadata -> FeatureSupport.Optional, Features.RouteBlinding -> FeatureSupport.Optional, + Features.ShutdownAnySegwit -> FeatureSupport.Optional, Features.StaticRemoteKey -> FeatureSupport.Mandatory, + Features.AnchorOutputsZeroFeeHtlcTx -> FeatureSupport.Optional, Features.Quiescence -> FeatureSupport.Optional, Features.SplicePrototype -> FeatureSupport.Optional, Features.ProvideStorage -> FeatureSupport.Optional, @@ -155,7 +157,6 @@ object TestConstants { quiescenceTimeout = 2 minutes, balanceThresholds = Nil, minTimeBetweenUpdates = 0 hours, - acceptIncomingStaticRemoteKeyChannels = false ), onChainFeeConf = OnChainFeeConf( feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium), @@ -265,7 +266,6 @@ object TestConstants { nodeParams, nodeParams.features.initFeatures(), None, - None, isChannelOpener = true, paysCommitTxFees = true, dualFunded = false, @@ -304,6 +304,7 @@ object TestConstants { Features.Wumbo -> FeatureSupport.Optional, Features.PaymentMetadata -> FeatureSupport.Optional, Features.RouteBlinding -> FeatureSupport.Optional, + Features.ShutdownAnySegwit -> FeatureSupport.Optional, Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputsZeroFeeHtlcTx -> FeatureSupport.Optional, Features.Quiescence -> FeatureSupport.Optional, @@ -347,7 +348,6 @@ object TestConstants { quiescenceTimeout = 2 minutes, balanceThresholds = Nil, minTimeBetweenUpdates = 0 hour, - acceptIncomingStaticRemoteKeyChannels = false ), onChainFeeConf = OnChainFeeConf( feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium), @@ -457,7 +457,6 @@ object TestConstants { nodeParams, nodeParams.features.initFeatures(), None, - None, isChannelOpener = false, paysCommitTxFees = false, dualFunded = false, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala index d208ad21f5..a7fa658f78 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/balance/CheckBalanceSpec.scala @@ -108,7 +108,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(CheckBalance.computeOffChainBalance(Seq(alice.stateData.asInstanceOf[DATA_CLOSING]), recentlySpentInputs = Set(closingTxInput)).closing == expected) } - test("channel closed with remote commit tx", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("channel closed with remote commit tx") { f => import f._ // We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice @@ -161,7 +161,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(balance4 == expected4) } - test("channel closed with next remote commit tx", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("channel closed with next remote commit tx") { f => import f._ // We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala index d6f10df0e0..df2afcd2b4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.TxIn.SEQUENCE_FINAL import fr.acinq.bitcoin.psbt.{KeyPathWithMaster, Psbt, TaprootBip32DerivationPath} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath -import fr.acinq.bitcoin.scalacompat.{Block, ByteVector64, Crypto, KotlinUtils, OutPoint, Satoshi, SatoshiLong, Script, ScriptElt, ScriptWitness, Transaction, TxId, TxIn, TxOut} +import fr.acinq.bitcoin.scalacompat.{Block, ByteVector64, KotlinUtils, OutPoint, Satoshi, SatoshiLong, Script, ScriptElt, ScriptWitness, Transaction, TxId, TxIn, TxOut} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair.blockchain.OnChainWallet.{FundTransactionResponse, MakeFundingTxResponse, OnChainBalance, ProcessPsbtResponse} import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType @@ -36,7 +36,7 @@ import scala.util.{Failure, Random, Success} /** * Created by PM on 06/07/2017. */ -class DummyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { +class DummyOnChainWallet extends OnChainWallet with OnChainAddressCache { import DummyOnChainWallet._ @@ -52,8 +52,6 @@ class DummyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { case _ => Script.pay2wpkh(dummyReceivePubkey) }) - override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(dummyReceivePubkey) - override def fundTransaction(tx: Transaction, feeRate: FeeratePerKw, replaceable: Boolean, changePosition: Option[Int], externalInputsWeight: Map[OutPoint, Long], minInputConfirmations_opt: Option[Int], feeBudget_opt: Option[Satoshi])(implicit ec: ExecutionContext): Future[FundTransactionResponse] = { funded += (tx.txid -> tx) Future.successful(FundTransactionResponse(tx, 0 sat, None)) @@ -92,12 +90,10 @@ class DummyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(false) - override def getP2wpkhPubkey(renew: Boolean): PublicKey = dummyReceivePubkey - override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = Script.pay2tr(dummyReceivePubkey.xOnly) } -class SingleKeyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { +class SingleKeyOnChainWallet extends OnChainWallet with OnChainAddressCache { import fr.acinq.bitcoin.scalacompat.KotlinUtils._ @@ -124,8 +120,6 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { case _ => p2trScript }) - override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = Future.successful(p2wpkhPublicKey) - override def fundTransaction(tx: Transaction, feeRate: FeeratePerKw, replaceable: Boolean, changePosition: Option[Int], externalInputsWeight: Map[OutPoint, Long], minInputConfirmations_opt: Option[Int], feeBudget_opt: Option[Satoshi])(implicit ec: ExecutionContext): Future[FundTransactionResponse] = synchronized { val currentAmountIn = tx.txIn.flatMap(txIn => inputs.find(_.txid == txIn.outPoint.txid).flatMap(_.txOut.lift(txIn.outPoint.index.toInt))).map(_.amount).sum val amountOut = tx.txOut.map(_.amount).sum @@ -231,8 +225,6 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnChainPubkeyCache { override def doubleSpent(tx: Transaction)(implicit ec: ExecutionContext): Future[Boolean] = Future.successful(doubleSpent.contains(tx.txid)) - override def getP2wpkhPubkey(renew: Boolean): PublicKey = p2wpkhPublicKey - override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = p2trScript } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresherSpec.scala index 59d999aca3..818eba4456 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/OnChainAddressRefresherSpec.scala @@ -1,8 +1,7 @@ package fr.acinq.eclair.blockchain.bitcoind import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps -import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{Crypto, Script, ScriptElt} +import fr.acinq.bitcoin.scalacompat.{Script, ScriptElt} import fr.acinq.eclair.blockchain.OnChainAddressGenerator import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.AddressType import fr.acinq.eclair.{TestKitBaseClass, randomKey} @@ -15,48 +14,31 @@ import scala.concurrent.{ExecutionContext, Future} class OnChainAddressRefresherSpec extends TestKitBaseClass with AnyFunSuiteLike { test("renew on-chain addresses") { - val finalPubkey = new AtomicReference[PublicKey](randomKey().publicKey) val finalPubkeyScript = new AtomicReference[Seq[ScriptElt]](Script.pay2tr(randomKey().xOnlyPublicKey())) - val renewedPublicKeyCount = new AtomicInteger(0) - val renewedPublicKeyScriptCount = new AtomicInteger(0) + val renewedCount = new AtomicInteger(0) val generator = new OnChainAddressGenerator { override def getReceivePublicKeyScript(addressType: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[Seq[ScriptElt]] = { - renewedPublicKeyScriptCount.incrementAndGet() + renewedCount.incrementAndGet() Future.successful(addressType match { case Some(AddressType.P2tr) => Script.pay2tr(randomKey().xOnlyPublicKey()) case _ => Script.pay2wpkh(randomKey().publicKey) }) } - - override def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = { - renewedPublicKeyCount.incrementAndGet() - Future.successful(randomKey().publicKey) - } } - val manager = system.spawnAnonymous(OnChainAddressRefresher(generator, finalPubkey, finalPubkeyScript, 1 second)) - - // We send a batch of requests to renew our public key. - val publicKey1 = finalPubkey.get() - (1 to 7).foreach(_ => manager ! OnChainAddressRefresher.RenewPubkey) - awaitCond(finalPubkey.get() != publicKey1) - assert(renewedPublicKeyCount.get() == 1) + val manager = system.spawnAnonymous(OnChainAddressRefresher(generator, finalPubkeyScript, 1 second)) // We send a batch of requests to renew our public key script. val script1 = finalPubkeyScript.get() (1 to 5).foreach(_ => manager ! OnChainAddressRefresher.RenewPubkeyScript) awaitCond(finalPubkeyScript.get() != script1) - assert(renewedPublicKeyScriptCount.get() == 1) + assert(renewedCount.get() == 1) - // If we mix the two types of renew requests, only the first one will be renewed. - val publicKey2 = finalPubkey.get() + // We send another request to renew our public key script. val script2 = finalPubkeyScript.get() manager ! OnChainAddressRefresher.RenewPubkeyScript - manager ! OnChainAddressRefresher.RenewPubkey awaitCond(finalPubkeyScript.get() != script2) - assert(renewedPublicKeyCount.get() == 1) - assert(renewedPublicKeyScriptCount.get() == 2) - assert(finalPubkey.get() == publicKey2) + assert(renewedCount.get() == 2) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConfSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConfSpec.scala index 113c66aea7..67b3c43e0d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConfSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConfSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.blockchain.fee import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.randomKey -import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} import org.scalatest.funsuite.AnyFunSuite class OnChainFeeConfSpec extends AnyFunSuite { @@ -37,14 +37,11 @@ class OnChainFeeConfSpec extends AnyFunSuite { } test("get commitment feerate") { - val commitmentFormat = DefaultCommitmentFormat val feeConf = OnChainFeeConf(defaultFeeTargets, defaultMaxClosingFeerate, safeUtxosThreshold = 0, spendAnchorWithoutHtlcs = true, anchorWithoutHtlcsMaxFee = 10_000.sat, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty) - val feerates1 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = FeeratePerKw(5000 sat)) - assert(feeConf.getCommitmentFeerate(feerates1, randomKey().publicKey, commitmentFormat) == FeeratePerKw(5000 sat)) - - val feerates2 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = FeeratePerKw(4000 sat)) - assert(feeConf.getCommitmentFeerate(feerates2, randomKey().publicKey, commitmentFormat) == FeeratePerKw(4000 sat)) + assert(feeConf.getCommitmentFeerate(feerates1, randomKey().publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) == FeeratePerKw(2500 sat)) + val feerates2 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = FeeratePerKw(2000 sat)) + assert(feeConf.getCommitmentFeerate(feerates2, randomKey().publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) == FeeratePerKw(2000 sat)) } test("get commitment feerate (anchor outputs)") { @@ -100,24 +97,6 @@ class OnChainFeeConfSpec extends AnyFunSuite { } test("fee difference too high") { - val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat), DustTolerance(25000 sat, closeOnUpdateFeeOverflow = false)) - val testCases = Seq( - (FeeratePerKw(500 sat), FeeratePerKw(500 sat), false), - (FeeratePerKw(500 sat), FeeratePerKw(250 sat), false), - (FeeratePerKw(500 sat), FeeratePerKw(249 sat), true), - (FeeratePerKw(500 sat), FeeratePerKw(200 sat), true), - (FeeratePerKw(249 sat), FeeratePerKw(500 sat), false), - (FeeratePerKw(250 sat), FeeratePerKw(500 sat), false), - (FeeratePerKw(250 sat), FeeratePerKw(1000 sat), false), - (FeeratePerKw(250 sat), FeeratePerKw(1001 sat), true), - (FeeratePerKw(250 sat), FeeratePerKw(1500 sat), true), - ) - testCases.foreach { case (networkFeerate, proposedFeerate, expected) => - assert(tolerance.isFeeDiffTooHigh(DefaultCommitmentFormat, networkFeerate, proposedFeerate) == expected) - } - } - - test("fee difference too high (anchor outputs)") { val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat), DustTolerance(25000 sat, closeOnUpdateFeeOverflow = false)) val testCases = Seq( (FeeratePerKw(500 sat), FeeratePerKw(500 sat), false), @@ -131,8 +110,7 @@ class OnChainFeeConfSpec extends AnyFunSuite { (FeeratePerKw(1000 sat), FeeratePerKw(500 sat), false), ) testCases.foreach { case (networkFeerate, proposedFeerate, expected) => - assert(tolerance.isFeeDiffTooHigh(UnsafeLegacyAnchorOutputsCommitmentFormat, networkFeerate, proposedFeerate) == expected) - assert(tolerance.isFeeDiffTooHigh(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, networkFeerate, proposedFeerate) == expected) + assert(tolerance.isProposedCommitFeerateTooHigh(networkFeerate, proposedFeerate) == expected) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala index f74a051fda..405223d820 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala @@ -24,47 +24,10 @@ import org.scalatest.funsuite.AnyFunSuiteLike class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStateTestsBase { - test("pick channel type based on local and remote features") { - case class TestCase(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expectedChannelType: ChannelType) - val testCases = Seq( - TestCase(Features.empty, Features.empty, announceChannel = true, ChannelTypes.Standard()), - TestCase(Features(ScidAlias -> Optional), Features(ScidAlias -> Optional), announceChannel = false, ChannelTypes.Standard(scidAlias = true)), - TestCase(Features(StaticRemoteKey -> Optional), Features.empty, announceChannel = true, ChannelTypes.Standard()), - TestCase(Features.empty, Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.Standard()), - TestCase(Features.empty, Features(StaticRemoteKey -> Mandatory), announceChannel = true, ChannelTypes.Standard()), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey()), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), announceChannel = true, ChannelTypes.StaticRemoteKey()), - TestCase(Features(StaticRemoteKey -> Optional, ScidAlias -> Mandatory), Features(StaticRemoteKey -> Mandatory, ScidAlias -> Mandatory), announceChannel = false, ChannelTypes.StaticRemoteKey(scidAlias = true)), - TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), announceChannel = true, ChannelTypes.AnchorOutputs()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, ScidAlias -> Optional, ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, ScidAlias -> Mandatory, ZeroConf -> Optional), announceChannel = false, ChannelTypes.AnchorOutputs(scidAlias = true, zeroConf = true)), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputs()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), announceChannel = false, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true)), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(zeroConf = true)), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Mandatory, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(zeroConf = true)), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Mandatory, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional, Features.ZeroConf -> Optional), announceChannel = false, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), - ) - - for (testCase <- testCases) { - assert(ChannelTypes.defaultFromFeatures(testCase.localFeatures, testCase.remoteFeatures, announceChannel = testCase.announceChannel) == testCase.expectedChannelType, s"localFeatures=${testCase.localFeatures} remoteFeatures=${testCase.remoteFeatures}") - } - } - test("create channel type from features") { case class TestCase(features: Features[InitFeature], expectedChannelType: ChannelType) val validChannelTypes = Seq( - TestCase(Features.empty, ChannelTypes.Standard()), - TestCase(Features(ScidAlias -> Mandatory), ChannelTypes.Standard(scidAlias = true)), - TestCase(Features(StaticRemoteKey -> Mandatory), ChannelTypes.StaticRemoteKey()), - TestCase(Features(StaticRemoteKey -> Mandatory, ScidAlias -> Mandatory), ChannelTypes.StaticRemoteKey(scidAlias = true)), TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory), ChannelTypes.AnchorOutputs()), TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory, ScidAlias -> Mandatory, ZeroConf -> Mandatory), ChannelTypes.AnchorOutputs(scidAlias = true, zeroConf = true)), TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), @@ -95,11 +58,6 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha test("enrich channel type with optional permanent channel features") { case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[PermanentChannelFeature]) val testCases = Seq( - TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), - TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), - TestCase(ChannelTypes.Standard(), Features(UpfrontShutdownScript -> Mandatory), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features.empty, announceChannel = true, Set.empty), - TestCase(ChannelTypes.StaticRemoteKey(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set(UpfrontShutdownScript)), TestCase(ChannelTypes.AnchorOutputs(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), TestCase(ChannelTypes.AnchorOutputs(), Features(UpfrontShutdownScript -> Optional), Features(UpfrontShutdownScript -> Mandatory), announceChannel = true, Set(UpfrontShutdownScript)), TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Features.empty, Features(UpfrontShutdownScript -> Optional), announceChannel = true, Set.empty), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala index b315f667f7..3aaa7f2cf1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.reputation.Reputation -import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat +import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions} import fr.acinq.eclair.wire.protocol._ import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -40,7 +40,6 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging - private val feerates = FeeratesPerKw.single(TestConstants.feeratePerKw) private val feeConfNoMismatch = OnChainFeeConf( feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium), maxClosingFeerate = FeeratePerKw(10_000 sat), @@ -67,10 +66,10 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("correct values for availableForSend/availableForReceive (success case)") { f => import f._ - val a = 758640000 msat // initial balance alice + val a = 772000000 msat // initial balance alice val b = 190000000 msat // initial balance bob val p = 42000000 msat // a->b payment - val htlcOutputFee = 2 * 1720000 msat // fee due to the additional htlc output; we count it twice because we keep a reserve for a x2 feerate increase + val htlcOutputFee = 860000 msat val maxDustExposure = 500000 sat val ac0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments @@ -83,11 +82,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (payment_preimage, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) + val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.onChainFeeConf) assert(ac1.availableBalanceForSend == a - p - htlcOutputFee) // as soon as htlc is sent, alice sees its balance decrease (more than the payment amount because of the commitment fees) assert(ac1.availableBalanceForReceive == b) - val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) + val Right(bc1) = bc0.receiveAdd(add) assert(bc1.availableBalanceForSend == b) assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee) @@ -152,10 +151,10 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("correct values for availableForSend/availableForReceive (failure case)") { f => import f._ - val a = 758640000 msat // initial balance alice + val a = 772000000 msat // initial balance alice val b = 190000000 msat // initial balance bob val p = 42000000 msat // a->b payment - val htlcOutputFee = 2 * 1720000 msat // fee due to the additional htlc output; we count it twice because we keep a reserve for a x2 feerate increase + val htlcOutputFee = 860000 msat val maxDustExposure = 500000 sat val ac0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments @@ -168,11 +167,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (_, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) + val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.onChainFeeConf) assert(ac1.availableBalanceForSend == a - p - htlcOutputFee) // as soon as htlc is sent, alice sees its balance decrease (more than the payment amount because of the commitment fees) assert(ac1.availableBalanceForReceive == b) - val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) + val Right(bc1) = bc0.receiveAdd(add) assert(bc1.availableBalanceForSend == b) assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee) @@ -237,12 +236,12 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("correct values for availableForSend/availableForReceive (multiple htlcs)") { f => import f._ - val a = 758640000 msat // initial balance alice + val a = 772000000 msat // initial balance alice val b = 190000000 msat // initial balance bob val p1 = 18000000 msat // a->b payment val p2 = 20000000 msat // a->b payment val p3 = 40000000 msat // b->a payment - val htlcOutputFee = 2 * 1720000 msat // fee due to the additional htlc output; we count it twice because we keep a reserve for a x2 feerate increase + val htlcOutputFee = 860000 msat val maxDustExposure = 500000 sat val ac0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments @@ -256,29 +255,29 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (payment_preimage1, cmdAdd1) = makeCmdAdd(p1, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add1)) = ac0.sendAdd(cmdAdd1, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) + val Right((ac1, add1)) = ac0.sendAdd(cmdAdd1, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.onChainFeeConf) assert(ac1.availableBalanceForSend == a - p1 - htlcOutputFee) // as soon as htlc is sent, alice sees its balance decrease (more than the payment amount because of the commitment fees) assert(ac1.availableBalanceForReceive == b) val (_, cmdAdd2) = makeCmdAdd(p2, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) + val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.onChainFeeConf) assert(ac2.availableBalanceForSend == a - p1 - htlcOutputFee - p2 - htlcOutputFee) // as soon as htlc is sent, alice sees its balance decrease (more than the payment amount because of the commitment fees) assert(ac2.availableBalanceForReceive == b) val (payment_preimage3, cmdAdd3) = makeCmdAdd(p3, alice.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((bc1, add3)) = bc0.sendAdd(cmdAdd3, currentBlockHeight, bob.underlyingActor.nodeParams.channelConf, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) + val Right((bc1, add3)) = bc0.sendAdd(cmdAdd3, currentBlockHeight, bob.underlyingActor.nodeParams.channelConf, bob.underlyingActor.nodeParams.onChainFeeConf) assert(bc1.availableBalanceForSend == b - p3) // bob doesn't pay the fee assert(bc1.availableBalanceForReceive == a) - val Right(bc2) = bc1.receiveAdd(add1, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) + val Right(bc2) = bc1.receiveAdd(add1) assert(bc2.availableBalanceForSend == b - p3) assert(bc2.availableBalanceForReceive == a - p1 - htlcOutputFee) - val Right(bc3) = bc2.receiveAdd(add2, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) + val Right(bc3) = bc2.receiveAdd(add2) assert(bc3.availableBalanceForSend == b - p3) assert(bc3.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee) - val Right(ac3) = ac2.receiveAdd(add3, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) + val Right(ac3) = ac2.receiveAdd(add3) assert(ac3.availableBalanceForSend == a - p1 - htlcOutputFee - p2 - htlcOutputFee) assert(ac3.availableBalanceForReceive == b - p3) @@ -387,7 +386,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val isInitiator = true val c = CommitmentsSpec.makeCommitments(100000000 msat, 50000000 msat, FeeratePerKw(2500 sat), 546 sat, isInitiator) val (_, cmdAdd) = makeCmdAdd(c.availableBalanceForSend, randomKey().publicKey, f.currentBlockHeight) - val Right((c1, _)) = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch) + val Right((c1, _)) = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feeConfNoMismatch) assert(c1.availableBalanceForSend == 0.msat) // We should be able to handle a fee increase. @@ -395,14 +394,14 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Now we shouldn't be able to send until we receive enough to handle the updated commit tx fee (even trimmed HTLCs shouldn't be sent). val (_, cmdAdd1) = makeCmdAdd(100 msat, randomKey().publicKey, f.currentBlockHeight) - val Left(_: InsufficientFunds) = c2.sendAdd(cmdAdd1, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch) + val Left(_: InsufficientFunds) = c2.sendAdd(cmdAdd1, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feeConfNoMismatch) } test("can send availableForSend") { f => for (isInitiator <- Seq(true, false)) { val c = CommitmentsSpec.makeCommitments(702000000 msat, 52000000 msat, FeeratePerKw(2679 sat), 546 sat, isInitiator) val (_, cmdAdd) = makeCmdAdd(c.availableBalanceForSend, randomKey().publicKey, f.currentBlockHeight) - val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch) + val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feeConfNoMismatch) assert(result.isRight, result) } } @@ -411,7 +410,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with for (isInitiator <- Seq(true, false)) { val c = CommitmentsSpec.makeCommitments(31000000 msat, 702000000 msat, FeeratePerKw(2679 sat), 546 sat, isInitiator) val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - c.receiveAdd(add, feerates, feeConfNoMismatch) + c.receiveAdd(add) } } @@ -432,14 +431,14 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with for (_ <- 1 to t.pendingHtlcs) { val amount = Random.nextInt(maxPendingHtlcAmount.toLong.toInt).msat.max(1 msat) val (_, cmdAdd) = makeCmdAdd(amount, randomKey().publicKey, f.currentBlockHeight) - c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch) match { + c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feeConfNoMismatch) match { case Right((cc, _)) => c = cc case Left(e) => ignore(s"$t -> could not setup initial htlcs: $e") } } if (c.availableBalanceForSend > 0.msat) { val (_, cmdAdd) = makeCmdAdd(c.availableBalanceForSend, randomKey().publicKey, f.currentBlockHeight) - val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch) + val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feeConfNoMismatch) assert(result.isRight, s"$t -> $result") } } @@ -462,14 +461,14 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with for (_ <- 1 to t.pendingHtlcs) { val amount = Random.nextInt(maxPendingHtlcAmount.toLong.toInt).msat.max(1 msat) val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, amount, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - c.receiveAdd(add, feerates, feeConfNoMismatch) match { + c.receiveAdd(add) match { case Right(cc) => c = cc case Left(e) => ignore(s"$t -> could not setup initial htlcs: $e") } } if (c.availableBalanceForReceive > 0.msat) { val add = UpdateAddHtlc(randomBytes32(), c.changes.remoteNextHtlcId, c.availableBalanceForReceive, randomBytes32(), CltvExpiry(f.currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - c.receiveAdd(add, feerates, feeConfNoMismatch) match { + c.receiveAdd(add) match { case Right(_) => () case Left(e) => fail(s"$t -> $e") } @@ -490,12 +489,12 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isOpener: Boolean = true, announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isOpener, isOpener, None, None, Features.empty) + val localChannelParams = LocalChannelParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isOpener, isOpener, None, Features.empty) val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) val commitParams = CommitParams(dustLimit, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat).pubkeyScript) val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { @@ -505,7 +504,7 @@ object CommitmentsSpec { Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, @@ -515,12 +514,12 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announcement_opt: Option[ChannelAnnouncement]): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 - val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val localChannelParams = LocalChannelParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, Features.empty) val remoteChannelParams = RemoteChannelParams(remoteNodeId, Some(channelReserve), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) val commitParams = CommitParams(0 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, DefaultCommitmentFormat).pubkeyScript) + val fundingTxOut = TxOut((toLocal + toRemote).truncateToSatoshi, Transactions.makeFundingScript(localFundingPubKey, remoteFundingPubKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat).pubkeyScript) val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), randomTxId(), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { @@ -530,7 +529,7 @@ object CommitmentsSpec { Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = announcement_opt.nonEmpty)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, DefaultCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), + List(Commitment(0, 0, OutPoint(randomTxId(), 0), fundingTxOut.amount, remoteFundingPubKey, localFundingStatus, RemoteFundingStatus.Locked, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, commitParams, localCommit, commitParams, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala index 657ca58f69..6c94ae473e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/DustExposureSpec.scala @@ -47,37 +47,6 @@ class DustExposureSpec extends AnyFunSuiteLike { assert(DustExposure.computeExposure(spec, 500 sat, Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) == 2796.sat.toMilliSatoshi) assert(DustExposure.computeExposure(spec, 500 sat, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) == 3796.sat.toMilliSatoshi) } - { - // Low feerate: buffer adds 10 sat/byte - val dustLimit = 500.sat - val feerate = FeeratePerByte(10 sat).perKw - assert(Transactions.receivedHtlcTrimThreshold(dustLimit, feerate, Transactions.DefaultCommitmentFormat) == 2257.sat) - assert(Transactions.offeredHtlcTrimThreshold(dustLimit, feerate, Transactions.DefaultCommitmentFormat) == 2157.sat) - assert(Transactions.receivedHtlcTrimThreshold(dustLimit, feerate * 2, Transactions.DefaultCommitmentFormat) == 4015.sat) - assert(Transactions.offeredHtlcTrimThreshold(dustLimit, feerate * 2, Transactions.DefaultCommitmentFormat) == 3815.sat) - val htlcs = Set[DirectedHtlc]( - // Below the dust limit. - IncomingHtlc(createHtlc(0, 450.sat.toMilliSatoshi)), - OutgoingHtlc(createHtlc(0, 450.sat.toMilliSatoshi)), - // Above the dust limit, trimmed at 10 sat/byte - IncomingHtlc(createHtlc(1, 2250.sat.toMilliSatoshi)), - OutgoingHtlc(createHtlc(1, 2150.sat.toMilliSatoshi)), - // Above the dust limit, trimmed at 20 sat/byte - IncomingHtlc(createHtlc(2, 4010.sat.toMilliSatoshi)), - OutgoingHtlc(createHtlc(2, 3810.sat.toMilliSatoshi)), - // Above the dust limit, untrimmed at 20 sat/byte - IncomingHtlc(createHtlc(3, 4020.sat.toMilliSatoshi)), - OutgoingHtlc(createHtlc(3, 3820.sat.toMilliSatoshi)), - ) - val spec = CommitmentSpec(htlcs, feerate, 50000 msat, 75000 msat) - val expected = 450.sat + 450.sat + 2250.sat + 2150.sat + 4010.sat + 3810.sat - assert(DustExposure.computeExposure(spec, dustLimit, Transactions.DefaultCommitmentFormat) == expected.toMilliSatoshi) - assert(DustExposure.computeExposure(spec, feerate * 2, dustLimit, Transactions.DefaultCommitmentFormat) == DustExposure.computeExposure(spec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(4, 4010.sat.toMilliSatoshi)), spec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(4, 3810.sat.toMilliSatoshi)), spec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(!DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(5, 4020.sat.toMilliSatoshi)), spec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(5, 3820.sat.toMilliSatoshi)), spec, dustLimit, Transactions.DefaultCommitmentFormat)) - } { // High feerate: buffer adds 25% val dustLimit = 1000.sat @@ -103,7 +72,6 @@ class DustExposureSpec extends AnyFunSuiteLike { val spec = CommitmentSpec(htlcs, feerate, 50000 msat, 75000 msat) val expected = 900.sat + 900.sat + 15000.sat + 14000.sat + 18000.sat + 17000.sat assert(DustExposure.computeExposure(spec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) == expected.toMilliSatoshi) - assert(DustExposure.computeExposure(spec, feerate * 1.25, dustLimit, Transactions.DefaultCommitmentFormat) == DustExposure.computeExposure(spec, dustLimit, Transactions.DefaultCommitmentFormat)) assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(4, 18000.sat.toMilliSatoshi)), spec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) assert(DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(4, 17000.sat.toMilliSatoshi)), spec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) assert(!DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(5, 19000.sat.toMilliSatoshi)), spec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) @@ -114,22 +82,22 @@ class DustExposureSpec extends AnyFunSuiteLike { test("filter incoming htlcs before forwarding") { val dustLimit = 1000.sat val initialSpec = CommitmentSpec(Set.empty, FeeratePerKw(10000 sat), 0 msat, 0 msat) - assert(DustExposure.computeExposure(initialSpec, dustLimit, Transactions.DefaultCommitmentFormat) == 0.msat) - assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(0, 9000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 9000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) + assert(DustExposure.computeExposure(initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) == 0.msat) + assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(0, 9000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 9000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) // NB: HTLC-success transactions are bigger than HTLC-timeout transactions: that means incoming htlcs have a higher // dust threshold than outgoing htlcs in our commitment. - assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(0, 9500.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 9500.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(!DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(0, 10000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) - assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 10000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.DefaultCommitmentFormat)) + assert(DustExposure.contributesToDustExposure(IncomingHtlc(createHtlc(0, 9500.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 9500.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 10000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(!DustExposure.contributesToDustExposure(OutgoingHtlc(createHtlc(0, 10000.sat.toMilliSatoshi)), initialSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat)) val updatedSpec = initialSpec.copy(htlcs = Set( OutgoingHtlc(createHtlc(2, 9000.sat.toMilliSatoshi)), OutgoingHtlc(createHtlc(3, 9500.sat.toMilliSatoshi)), IncomingHtlc(createHtlc(4, 9500.sat.toMilliSatoshi)), )) - assert(DustExposure.computeExposure(updatedSpec, dustLimit, Transactions.DefaultCommitmentFormat) == 18500.sat.toMilliSatoshi) + assert(DustExposure.computeExposure(updatedSpec, dustLimit, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) == 18500.sat.toMilliSatoshi) val receivedHtlcs = Seq( createHtlc(5, 9500.sat.toMilliSatoshi), @@ -139,7 +107,7 @@ class DustExposureSpec extends AnyFunSuiteLike { createHtlc(9, 400.sat.toMilliSatoshi), createHtlc(10, 50000.sat.toMilliSatoshi), ) - val (accepted, rejected) = DustExposure.filterBeforeForward(25000 sat, updatedSpec, dustLimit, 10000.sat.toMilliSatoshi, initialSpec, dustLimit, 15000.sat.toMilliSatoshi, receivedHtlcs, Transactions.DefaultCommitmentFormat) + val (accepted, rejected) = DustExposure.filterBeforeForward(25000 sat, updatedSpec, dustLimit, 10000.sat.toMilliSatoshi, initialSpec, dustLimit, 15000.sat.toMilliSatoshi, receivedHtlcs, Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) assert(accepted.map(_.id).toSet == Set(5, 6, 8, 10)) assert(rejected.map(_.id).toSet == Set(7, 9)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index f9eeeb2e8f..0c48fa8555 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -81,9 +81,9 @@ class FuzzySpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Channe aliceRegister ! alice bobRegister ! bob // no announcements - alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, commitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.Standard(), replyTo = system.deadLetters) + alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.anchorOutputsFeeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(TestConstants.initiatorPushAmount), requireConfirmedInputs = false, requestFunding_opt = None, aliceChannelParams, commitParams, pipe, bobInit, channelFlags, ChannelConfig.standard, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), replyTo = system.deadLetters) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] - bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, commitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.Standard()) + bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, requireConfirmedInputs = false, bobChannelParams, commitParams, pipe, aliceInit, ChannelConfig.standard, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] pipe ! (alice, bob) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 4d802ebfe4..d0234a8ff4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong, randomKey} import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuiteLike -import scodec.bits.{ByteVector, HexStringSyntax} +import scodec.bits.HexStringSyntax import java.util.UUID import scala.concurrent.duration._ @@ -174,12 +174,12 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat bobClaimHtlcTimeoutTxs.foreach(claimHtlcTimeout => assert(Closing.trimmedOrTimedOutHtlcs(localKeys, localCommitment, localCommit, claimHtlcTimeout.sign()).isEmpty)) } - test("find timed out htlcs (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - findTimedOutHtlcs(setupHtlcs(Set(ChannelStateTestsTags.AnchorOutputs))) + test("find timed out htlcs") { + findTimedOutHtlcs(setupHtlcs()) } - test("find timed out htlcs (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - findTimedOutHtlcs(setupHtlcs(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs))) + test("find timed out htlcs (anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { + findTimedOutHtlcs(setupHtlcs(Set(ChannelStateTestsTags.AnchorOutputsPhoenix))) } test("check closing tx amounts above dust") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index d71ae5c3a6..bcddf81d43 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -219,9 +219,9 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit private def createFixtureParams(channelType: SupportedChannelType, fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false), nonInitiatorPaysCommitTxFees: Boolean = false): FixtureParams = { val Seq(nodeParamsA, nodeParamsB) = Seq(TestConstants.Alice.nodeParams, TestConstants.Bob.nodeParams).map(_.copy(features = Features(channelType.features.map(f => f -> FeatureSupport.Optional).toMap[Feature, FeatureSupport]))) - val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA) + val localChannelParamsA = makeChannelParams(nodeParamsA, nodeParamsA.features.initFeatures(), None, isChannelOpener = true, paysCommitTxFees = !nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountA) val commitParamsA = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) - val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB) + val localChannelParamsB = makeChannelParams(nodeParamsB, nodeParamsB.features.initFeatures(), None, isChannelOpener = false, paysCommitTxFees = nonInitiatorPaysCommitTxFees, dualFunded = true, fundingAmountB) val commitParamsB = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(fundingAmountA + fundingAmountB, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) val channelKeysA = nodeParamsA.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsA.fundingKeyPath) val channelKeysB = nodeParamsB.channelKeyManager.channelKeys(ChannelConfig.standard, localChannelParamsB.fundingKeyPath) @@ -232,7 +232,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit nodeParams.nodeId, None, channelKeys.revocationBasePoint, - localParams.walletStaticPaymentBasepoint.getOrElse(channelKeys.paymentBasePoint), + channelKeys.paymentBasePoint, channelKeys.delayedPaymentBasePoint, channelKeys.htlcBasePoint, localParams.initFeatures, @@ -384,7 +384,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val utxosA = Seq(50_000 sat) val fundingB = 50_000 sat val utxosB = Seq(80_000 sat) - withFixture(ChannelTypes.AnchorOutputs(), fundingA, utxosA, fundingB, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f => + withFixture(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), fundingA, utxosA, fundingB, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = true, forRemote = true)) { f => import f._ alice ! Start(alice2bob.ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala index 493817c743..db69a0d1c6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala @@ -4,18 +4,15 @@ import akka.actor.ActorRef import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.testkit.{TestActor, TestFSMRef, TestProbe} import com.softwaremill.quicklens.ModifyPimp -import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat._ import fr.acinq.eclair.TestConstants.{Alice, Bob} -import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingSpentTriggered import fr.acinq.eclair.channel.fsm.Channel -import fr.acinq.eclair.channel.states.ChannelStateTestsBase.{FakeTxPublisherFactory, PimpTestFSM} -import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} +import fr.acinq.eclair.channel.states.ChannelStateTestsBase +import fr.acinq.eclair.channel.states.ChannelStateTestsBase.FakeTxPublisherFactory import fr.acinq.eclair.router.Announcements -import fr.acinq.eclair.wire.protocol.{ChannelReestablish, ChannelUpdate, CommitSig, Error, Init, RevokeAndAck} +import fr.acinq.eclair.wire.protocol.{ChannelReestablish, ChannelUpdate, Init} import fr.acinq.eclair.{TestKitBaseClass, _} +import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike -import org.scalatest.{Outcome, Tag} import scala.concurrent.duration._ @@ -31,72 +28,8 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan } } - private def aliceInit = Init(Alice.nodeParams.features.initFeatures()) - - private def bobInit = Init(Bob.nodeParams.features.initFeatures()) - - test("use funding pubkeys from publish commitment to spend our output", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - import f._ - val sender = TestProbe() - - // we start by storing the current state - val oldStateData = alice.stateData.asInstanceOf[PersistentChannelData] - // then we add an htlc and sign it - addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN()) - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - // alice will receive neither the revocation nor the commit sig - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.expectMsgType[CommitSig] - - // we simulate a disconnection - sender.send(alice, INPUT_DISCONNECTED) - sender.send(bob, INPUT_DISCONNECTED) - awaitCond(alice.stateName == OFFLINE) - awaitCond(bob.stateName == OFFLINE) - - // and we terminate Alice - alice.stop() - - // we restart Alice - val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(Alice.nodeParams, Alice.channelKeys(), aliceWallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref) - newAlice ! INPUT_RESTORED(oldStateData) - - // then we reconnect them - sender.send(newAlice, INPUT_RECONNECTED(alice2bob.ref, aliceInit, bobInit)) - sender.send(bob, INPUT_RECONNECTED(bob2alice.ref, bobInit, aliceInit)) - - // peers exchange channel_reestablish messages - alice2bob.expectMsgType[ChannelReestablish] - bob2alice.expectMsgType[ChannelReestablish] - - // alice then realizes it has an old state... - bob2alice.forward(newAlice) - // ... and ask bob to publish its current commitment - val error = alice2bob.expectMsgType[Error] - assert(new String(error.data.toArray) == PleasePublishYourCommitment(channelId(newAlice)).getMessage) - - // alice now waits for bob to publish its commitment - awaitCond(newAlice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) - - // bob is nice and publishes its commitment - val bobCommitTx = bob.signCommitTx() - - // actual tests starts here: let's see what we can do with Bob's commit tx - sender.send(newAlice, WatchFundingSpentTriggered(bobCommitTx)) - - // from Bob's commit tx we can extract both funding public keys - val OP_2 :: OP_PUSHDATA(pub1, _) :: OP_PUSHDATA(pub2, _) :: OP_2 :: OP_CHECKMULTISIG :: Nil = Script.parse(bobCommitTx.txIn(0).witness.stack.last) - assert(Set(pub1, pub2).map(PublicKey(_)) == Set(Alice.channelKeys().fundingKey(0).publicKey, Bob.channelKeys().fundingKey(0).publicKey)) - // from Bob's commit tx we can also extract our p2wpkh output - val ourOutput = bobCommitTx.txOut.find(_.publicKeyScript.length == 22).get - val OP_0 :: OP_PUSHDATA(pubKeyHash, _) :: Nil = Script.parse(ourOutput.publicKeyScript) - - // check that our output in Bob's commit tx sends to our static payment point - val Some(ourStaticPaymentPoint) = oldStateData.asInstanceOf[DATA_NORMAL].commitments.localChannelParams.walletStaticPaymentBasepoint - assert(pubKeyHash == ourStaticPaymentPoint.hash160) - } + private val aliceInit = Init(Alice.nodeParams.features.initFeatures()) + private val bobInit = Init(Bob.nodeParams.features.initFeatures()) /** We are only interested in channel updates from Alice, we use the channel flag to discriminate */ def aliceChannelUpdateListener(channelUpdateListener: TestProbe): TestProbe = { 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 60c1d74275..e3f8222782 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 @@ -21,7 +21,6 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, actorRefAdapter import akka.pattern.pipe import akka.testkit.{TestFSMRef, TestProbe} import com.softwaremill.quicklens.ModifyPimp -import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, BtcAmount, MilliBtcDouble, MnemonicCode, OutPoint, SatoshiLong, ScriptElt, Transaction, TxId} import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator import fr.acinq.eclair.blockchain.bitcoind.BitcoindService @@ -29,7 +28,7 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.MempoolTx import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BitcoinCoreClient, BitcoinJsonRPCClient} import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw} -import fr.acinq.eclair.blockchain.{CurrentBlockHeight, OnChainPubkeyCache} +import fr.acinq.eclair.blockchain.{CurrentBlockHeight, OnChainAddressCache} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.publish.ReplaceableTxPublisher.{Publish, Stop, UpdateConfirmationTarget} @@ -126,18 +125,12 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w def createTestWallet(walletName: String) = { val walletRpcClient = createWallet(walletName) val probe = TestProbe() - val walletClient = new BitcoinCoreClient(walletRpcClient) with OnChainPubkeyCache { - val pubkey = { - getP2wpkhPubkey().pipeTo(probe.ref) - probe.expectMsgType[PublicKey] - } - val pubkeyScript = { + val walletClient = new BitcoinCoreClient(walletRpcClient) with OnChainAddressCache { + private val pubkeyScript: Seq[ScriptElt] = { getReceivePublicKeyScript(None).pipeTo(probe.ref) probe.expectMsgType[Seq[ScriptElt]] } - override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey - override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript } @@ -165,9 +158,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val aliceNodeParams = TestConstants.Alice.nodeParams.copy(blockHeight = blockHeight) val setup = init(aliceNodeParams, TestConstants.Bob.nodeParams.copy(blockHeight = blockHeight), walletA_opt = Some(walletClient)) val testTags = channelType match { - case _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs) - case _: ChannelTypes.AnchorOutputs => Set(ChannelStateTestsTags.AnchorOutputs) - case _: ChannelTypes.StaticRemoteKey => Set(ChannelStateTestsTags.StaticRemoteKey) + case _: ChannelTypes.AnchorOutputs => Set(ChannelStateTestsTags.AnchorOutputsPhoenix) case _ => Set.empty[String] } reachNormal(setup, testTags) @@ -1603,10 +1594,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w // Force-close channel and verify txs sent to watcher. val remoteCommitTx = bob.signCommitTx() - bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitmentFormat match { - case Transactions.DefaultCommitmentFormat => assert(remoteCommitTx.txOut.size == 4) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(remoteCommitTx.txOut.size == 6) - } + assert(remoteCommitTx.txOut.size == 6) probe.send(alice, WatchFundingSpentTriggered(remoteCommitTx)) // We make the commit tx confirm because claim-htlc txs have a relative delay when using anchor outputs. @@ -1614,17 +1602,14 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w probe.expectMsg(remoteCommitTx.txid) generateBlocks(1) - val anchorTx_opt = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.commitmentFormat match { - case Transactions.DefaultCommitmentFormat => None - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx]) - } - val mainTx_opt = if (bob.stateData.asInstanceOf[DATA_NORMAL].commitments.channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx]) val claimHtlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget)) assert(claimHtlcTimeout.txInfo.isInstanceOf[ClaimHtlcTimeoutTx]) alice2blockchain.expectWatchTxConfirmed(remoteCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(mainTx_opt.map(_.input).toSeq ++ anchorTx_opt.map(_.input.outPoint).toSeq ++ Seq(claimHtlcSuccess.input, claimHtlcTimeout.input)) + alice2blockchain.expectWatchOutputsSpent(Seq(mainTx.input, anchorTx.input.outPoint) ++ Seq(claimHtlcSuccess.input, claimHtlcTimeout.input)) alice2blockchain.expectNoMessage(100 millis) (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) @@ -1690,7 +1675,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } } - def testClaimHtlcTxFeerateTooLowAnchors(nextCommit: Boolean): Unit = { + def testClaimHtlcTxFeerateTooLow(nextCommit: Boolean): Unit = { withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputs()) { f => import f._ @@ -1710,59 +1695,11 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("claim htlc tx feerate too low, lowering output amount") { - testClaimHtlcTxFeerateTooLowAnchors(nextCommit = false) + testClaimHtlcTxFeerateTooLow(nextCommit = false) } test("claim htlc tx feerate too low, lowering output amount (next remote commit)") { - testClaimHtlcTxFeerateTooLowAnchors(nextCommit = true) - } - - def testClaimHtlcTxFeerateTooLowStandard(nextCommit: Boolean): Unit = { - withFixture(Seq(11 millibtc), ChannelTypes.Standard()) { f => - import f._ - - val targetFeerate = FeeratePerKw(15_000 sat) - val (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) = remoteCloseChannelWithHtlcs(f, aliceBlockHeight() + 300, nextCommit) - - // The Claim-HTLC-success tx will be immediately published. - setFeerate(targetFeerate) - val claimHtlcSuccessPublisher = createPublisher() - claimHtlcSuccessPublisher ! Publish(probe.ref, claimHtlcSuccess) - val claimHtlcSuccessTx = getMempoolTxs(1).head - val claimHtlcSuccessTargetFee = Transactions.weight2fee(targetFeerate, claimHtlcSuccessTx.weight.toInt) - assert(claimHtlcSuccessTargetFee * 0.9 <= claimHtlcSuccessTx.fees && claimHtlcSuccessTx.fees <= claimHtlcSuccessTargetFee * 1.1, s"actualFee=${claimHtlcSuccessTx.fees} targetFee=$claimHtlcSuccessTargetFee") - generateBlocks(6) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) - val claimHtlcSuccessResult = probe.expectMsgType[TxConfirmed] - assert(claimHtlcSuccessResult.cmd == claimHtlcSuccess) - assert(claimHtlcSuccessResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) - claimHtlcSuccessPublisher ! Stop - - // The Claim-HTLC-timeout will be published after the timeout. - val claimHtlcTimeoutPublisher = createPublisher() - claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) - alice2blockchain.expectNoMessage(100 millis) - generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) - val claimHtlcTimeoutTx = getMempoolTxs(1).head - val claimHtlcTimeoutTargetFee = Transactions.weight2fee(targetFeerate, claimHtlcTimeoutTx.weight.toInt) - assert(claimHtlcTimeoutTargetFee * 0.9 <= claimHtlcTimeoutTx.fees && claimHtlcTimeoutTx.fees <= claimHtlcTimeoutTargetFee * 1.1, s"actualFee=${claimHtlcTimeoutTx.fees} targetFee=$claimHtlcTimeoutTargetFee") - - generateBlocks(6) - system.eventStream.publish(CurrentBlockHeight(currentBlockHeight(probe))) - val claimHtlcTimeoutResult = probe.expectMsgType[TxConfirmed] - assert(claimHtlcTimeoutResult.cmd == claimHtlcTimeout) - assert(claimHtlcTimeoutResult.tx.txIn.map(_.outPoint.txid).contains(remoteCommitTx.txid)) - claimHtlcTimeoutPublisher ! Stop - } - } - - test("claim htlc tx feerate too low, lowering output amount (standard commitment format)") { - testClaimHtlcTxFeerateTooLowStandard(nextCommit = false) - } - - test("claim htlc tx feerate too low, lowering output amount (next remote commit, standard commitment format)") { - testClaimHtlcTxFeerateTooLowStandard(nextCommit = true) + testClaimHtlcTxFeerateTooLow(nextCommit = true) } test("claim htlc tx feerate way too low, skipping output") { @@ -1790,65 +1727,6 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } } - test("claim htlc tx not confirming, lowering output amount again (standard commitment format)") { - withFixture(Seq(11 millibtc), ChannelTypes.Standard()) { f => - import f._ - - val initialFeerate = FeeratePerKw(15_000 sat) - val targetFeerate = FeeratePerKw(20_000 sat) - - val (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) = remoteCloseChannelWithHtlcs(f, aliceBlockHeight() + 144, nextCommit = false) - - val listener = TestProbe() - system.eventStream.subscribe(listener.ref, classOf[TransactionPublished]) - - // The Claim-HTLC-success tx will be immediately published. - setFeerate(initialFeerate, fastest = targetFeerate) - val claimHtlcSuccessPublisher = createPublisher() - claimHtlcSuccessPublisher ! Publish(probe.ref, claimHtlcSuccess) - val claimHtlcSuccessTx1 = getMempoolTxs(1).head - assert(listener.expectMsgType[TransactionPublished].tx.txid == claimHtlcSuccessTx1.txid) - - setFeerate(targetFeerate, fastest = targetFeerate) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 5)) - val claimHtlcSuccessTxId2 = listener.expectMsgType[TransactionPublished].tx.txid - assert(!isInMempool(claimHtlcSuccessTx1.txid)) - val claimHtlcSuccessTx2 = getMempoolTxs(1).head - assert(claimHtlcSuccessTx2.txid == claimHtlcSuccessTxId2) - assert(claimHtlcSuccessTx1.fees < claimHtlcSuccessTx2.fees) - val targetHtlcSuccessFee = Transactions.weight2fee(targetFeerate, claimHtlcSuccessTx2.weight.toInt) - assert(targetHtlcSuccessFee * 0.9 <= claimHtlcSuccessTx2.fees && claimHtlcSuccessTx2.fees <= targetHtlcSuccessFee * 1.1, s"actualFee=${claimHtlcSuccessTx2.fees} targetFee=$targetHtlcSuccessFee") - val finalHtlcSuccessTx = getMempool().head - assert(finalHtlcSuccessTx.txIn.length == 1) - assert(finalHtlcSuccessTx.txOut.length == 1) - assert(finalHtlcSuccessTx.txIn.head.outPoint.txid == remoteCommitTx.txid) - - // The Claim-HTLC-timeout will be published after the timeout. - setFeerate(initialFeerate, fastest = targetFeerate) - val claimHtlcTimeoutPublisher = createPublisher() - claimHtlcTimeoutPublisher ! Publish(probe.ref, claimHtlcTimeout) - generateBlocks(144) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 144)) - assert(probe.expectMsgType[TxConfirmed].tx.txid == finalHtlcSuccessTx.txid) // the claim-htlc-success is now confirmed - val claimHtlcTimeoutTx1 = getMempoolTxs(1).head - assert(listener.expectMsgType[TransactionPublished].tx.txid == claimHtlcTimeoutTx1.txid) - - setFeerate(targetFeerate, fastest = targetFeerate) - system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight() + 145)) - val claimHtlcTimeoutTxId2 = listener.expectMsgType[TransactionPublished].tx.txid - assert(!isInMempool(claimHtlcTimeoutTx1.txid)) - val claimHtlcTimeoutTx2 = getMempoolTxs(1).head - assert(claimHtlcTimeoutTx2.txid == claimHtlcTimeoutTxId2) - assert(claimHtlcTimeoutTx1.fees < claimHtlcTimeoutTx2.fees) - val targetHtlcTimeoutFee = Transactions.weight2fee(targetFeerate, claimHtlcTimeoutTx2.weight.toInt) - assert(targetHtlcTimeoutFee * 0.9 <= claimHtlcTimeoutTx2.fees && claimHtlcTimeoutTx2.fees <= targetHtlcTimeoutFee * 1.1, s"actualFee=${claimHtlcTimeoutTx2.fees} targetFee=$targetHtlcTimeoutFee") - val finalHtlcTimeoutTx = getMempool().head - assert(finalHtlcTimeoutTx.txIn.length == 1) - assert(finalHtlcTimeoutTx.txOut.length == 1) - assert(finalHtlcTimeoutTx.txIn.head.outPoint.txid == remoteCommitTx.txid) - } - } - test("claim htlc tx not confirming, but cannot lower output amount again") { withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputs()) { f => import f._ @@ -1883,18 +1761,12 @@ class ReplaceableTxPublisherWithEclairSignerSpec extends ReplaceableTxPublisherS val seed = MnemonicCode.toSeed(MnemonicCode.toMnemonics(entropy), walletName) val keyManager = new LocalOnChainKeyManager(walletName, seed, TimestampSecond.now(), Block.RegtestGenesisBlock.hash) val walletRpcClient = new BasicBitcoinJsonRPCClient(Block.RegtestGenesisBlock.hash, rpcAuthMethod = bitcoinrpcauthmethod, host = "localhost", port = bitcoindRpcPort, wallet = Some(walletName)) - val walletClient = new BitcoinCoreClient(walletRpcClient, onChainKeyManager_opt = Some(keyManager)) with OnChainPubkeyCache { - lazy val pubkey = { - getP2wpkhPubkey().pipeTo(probe.ref) - probe.expectMsgType[PublicKey] - } - lazy val pubkeyScript = { + val walletClient = new BitcoinCoreClient(walletRpcClient, onChainKeyManager_opt = Some(keyManager)) with OnChainAddressCache { + lazy val pubkeyScript: Seq[ScriptElt] = { getReceivePublicKeyScript(None).pipeTo(probe.ref) probe.expectMsgType[Seq[ScriptElt]] } - override def getP2wpkhPubkey(renew: Boolean): PublicKey = pubkey - override def getReceivePublicKeyScript(renew: Boolean): Seq[ScriptElt] = pubkeyScript } createEclairBackedWallet(walletRpcClient, keyManager) 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 d00b3f429e..3226996a10 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 @@ -42,7 +42,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { private val fundingKey: PrivateKey = randomKey() private val localCommitKeys: LocalCommitmentKeys = LocalCommitmentKeys(randomKey(), randomKey().publicKey, randomKey().publicKey, randomKey(), randomKey().publicKey, randomKey().publicKey) - private val remoteCommitKeys: RemoteCommitmentKeys = RemoteCommitmentKeys(Right(randomKey()), randomKey().publicKey, randomKey().publicKey, randomKey(), randomKey().publicKey, randomKey().publicKey) + private val remoteCommitKeys: RemoteCommitmentKeys = RemoteCommitmentKeys(randomKey(), randomKey().publicKey, randomKey().publicKey, randomKey(), randomKey().publicKey, randomKey().publicKey) case class FixtureParam(nodeParams: NodeParams, txPublisher: ActorRef[TxPublisher.Command], factory: TestProbe, probe: TestProbe) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index a566f64167..acf6b994c4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw} -import fr.acinq.eclair.blockchain.{OnChainPubkeyCache, OnChainWallet, SingleKeyOnChainWallet} +import fr.acinq.eclair.blockchain.{OnChainAddressCache, OnChainWallet, SingleKeyOnChainWallet} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx} @@ -38,7 +38,6 @@ import fr.acinq.eclair.payment.{Invoice, OutgoingPaymentPacket} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.Router.{ChannelHop, HopRelayParams, Route} import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ import org.scalatest.Assertions @@ -55,14 +54,6 @@ object ChannelStateTestsTags { val DualFunding = "dual_funding" /** If set, a liquidity ads will be used when opening a channel. */ val LiquidityAds = "liquidity_ads" - /** If set, channels will use option_static_remotekey. */ - val StaticRemoteKey = "static_remotekey" - /** If set, channels will use option_anchor_outputs. */ - val AnchorOutputs = "anchor_outputs" - /** If set, channels will use option_anchors_zero_fee_htlc_tx. */ - val AnchorOutputsZeroFeeHtlcTxs = "anchor_outputs_zero_fee_htlc_tx" - /** If set, channels will use option_shutdown_anysegwit. */ - val ShutdownAnySegwit = "shutdown_anysegwit" /** If set, channels will be public (otherwise we don't announce them by default). */ val ChannelsPublic = "channels_public" /** If set, initial announcement_signatures and channel_updates will not be intercepted and ignored. */ @@ -101,8 +92,11 @@ object ChannelStateTestsTags { val SimpleClose = "option_simple_close" /** If set, disable option_splice for one node. */ val DisableSplice = "disable_splice" - /** If set, channels will use taproot. */ + /** If set, channels will use the anchor outputs format where HTLC txs have a non-0 feerate, which is used for pre-taproot Phoenix channels. */ + val AnchorOutputsPhoenix = "anchor_outputs_phoenix" + /** If set, channels will use taproot like Phoenix does. */ val OptionSimpleTaprootPhoenix = "option_simple_taproot_phoenix" + /** If set, channels will use taproot. */ val OptionSimpleTaproot = "option_simple_taproot" } @@ -119,8 +113,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { alice2relayer: TestProbe, bob2relayer: TestProbe, channelUpdateListener: TestProbe, - aliceWallet: OnChainWallet with OnChainPubkeyCache, - bobWallet: OnChainWallet with OnChainPubkeyCache, + aliceWallet: OnChainWallet with OnChainAddressCache, + bobWallet: OnChainWallet with OnChainAddressCache, alicePeer: TestProbe, bobPeer: TestProbe) { def currentBlockHeight: BlockHeight = alice.underlyingActor.nodeParams.currentBlockHeight @@ -146,10 +140,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { temporaryChannelId = ByteVector32.Zeroes, fundingAmount = fundingAmount, dualFunded = dualFunded, - commitTxFeerate = channelType.commitmentFormat match { - case DefaultCommitmentFormat => TestConstants.feeratePerKw - case _ => TestConstants.anchorOutputsFeeratePerKw - }, + commitTxFeerate = TestConstants.anchorOutputsFeeratePerKw, fundingTxFeerate = TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, pushAmount_opt = pushAmount_opt, @@ -204,7 +195,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { system.registerOnTermination(TestKit.shutdownActorSystem(systemA)) system.registerOnTermination(TestKit.shutdownActorSystem(systemB)) - def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, walletA_opt: Option[OnChainWallet with OnChainPubkeyCache] = None, walletB_opt: Option[OnChainWallet with OnChainPubkeyCache] = None, tags: Set[String] = Set.empty): SetupFixture = { + def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, walletA_opt: Option[OnChainWallet with OnChainAddressCache] = None, walletB_opt: Option[OnChainWallet with OnChainAddressCache] = None, tags: Set[String] = Set.empty): SetupFixture = { val aliceOpenReplyTo = TestProbe() val alice2bob = TestProbe() val bob2alice = TestProbe() @@ -263,54 +254,62 @@ trait ChannelStateTestsBase extends Assertions with Eventually { def updateInitFeatures(nodeParamsA: NodeParams, nodeParamsB: NodeParams, tags: Set[String]): (NodeParams, NodeParams) = { val nodeParamsA1 = nodeParamsA.copy(features = nodeParamsA.features .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableWumbo))(_.removed(Features.Wumbo)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.StaticRemoteKey))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputs))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional).updated(Features.AnchorOutputs, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional).updated(Features.AnchorOutputs, FeatureSupport.Optional).updated(Features.AnchorOutputsZeroFeeHtlcTx, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ScidAlias))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DualFunding))(_.updated(Features.DualFunding, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional)) ) val nodeParamsB1 = nodeParamsB.copy(features = nodeParamsB.features .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableWumbo))(_.removed(Features.Wumbo)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.StaticRemoteKey))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputs))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional).updated(Features.AnchorOutputs, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional).updated(Features.AnchorOutputs, FeatureSupport.Optional).updated(Features.AnchorOutputsZeroFeeHtlcTx, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ScidAlias))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DualFunding))(_.updated(Features.DualFunding, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableSplice))(_.removed(Features.SplicePrototype)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional)) ) (nodeParamsA1, nodeParamsB1) } + /** Pick the channel type based on local and remote feature bits. */ + def channelTypeFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): SupportedChannelType = { + def canUse(feature: InitFeature): Boolean = Features.canUseFeature(localFeatures, remoteFeatures, feature) + + val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel + val zeroConf = canUse(Features.ZeroConf) + if (canUse(Features.SimpleTaprootChannelsStaging)) { + ChannelTypes.SimpleTaprootChannelsStaging(scidAlias, zeroConf) + } else if (canUse(Features.SimpleTaprootChannelsPhoenix)) { + ChannelTypes.SimpleTaprootChannelsPhoenix + } else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) { + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf) + } else if (canUse(Features.AnchorOutputs)) { + ChannelTypes.AnchorOutputs(scidAlias, zeroConf) + } else { + fail("cannot figure out channel_type from features") + } + } + def computeChannelParams(setup: SetupFixture, tags: Set[String], channelFlags: ChannelFlags = ChannelFlags(announceChannel = false)): ChannelParamsFixture = { import setup._ val (nodeParamsA, nodeParamsB) = updateInitFeatures(alice.underlyingActor.nodeParams, bob.underlyingActor.nodeParams, tags) val aliceInitFeatures = nodeParamsA.features.initFeatures() val bobInitFeatures = nodeParamsB.features.initFeatures() - val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, announceChannel = channelFlags.announceChannel) - - // those features can only be enabled with AnchorOutputsZeroFeeHtlcTxs, this is to prevent incompatible test configurations - if (tags.contains(ChannelStateTestsTags.ZeroConf)) assert(tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs) || tags.contains(ChannelStateTestsTags.AnchorOutputs) || tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix) || tags.contains(ChannelStateTestsTags.OptionSimpleTaproot), "invalid test configuration") - if (tags.contains(ChannelStateTestsTags.ScidAlias)) assert(tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs) || tags.contains(ChannelStateTestsTags.AnchorOutputs) || tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix) || tags.contains(ChannelStateTestsTags.OptionSimpleTaproot), "invalid test configuration") + val channelType = channelTypeFromFeatures(aliceInitFeatures, bobInitFeatures, announceChannel = channelFlags.announceChannel) implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global val aliceChannelParams = Alice.channelParams .modify(_.initFeatures).setTo(aliceInitFeatures) - .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(aliceWallet.getP2wpkhPubkey(), 10 seconds))) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) - .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(aliceWallet.getP2wpkhPubkey(), 10 seconds))))) + .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Await.result(aliceWallet.getReceivePublicKeyScript(), 10 seconds)))) val aliceCommitParams = CommitParams(nodeParamsA.channelConf.dustLimit, nodeParamsA.channelConf.htlcMinimum, nodeParamsA.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsA.channelConf.maxAcceptedHtlcs, nodeParamsB.channelConf.toRemoteDelay) .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight))(UInt64(150_000_000)) @@ -318,9 +317,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice))(1000 sat) val bobChannelParams = Bob.channelParams .modify(_.initFeatures).setTo(bobInitFeatures) - .modify(_.walletStaticPaymentBasepoint).setToIf(channelType.paysDirectlyToWallet)(Some(Await.result(bobWallet.getP2wpkhPubkey(), 10 seconds))) .modify(_.initialRequestedChannelReserve_opt).setToIf(tags.contains(ChannelStateTestsTags.DualFunding))(None) - .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Script.pay2wpkh(Await.result(bobWallet.getP2wpkhPubkey(), 10 seconds))))) + .modify(_.upfrontShutdownScript_opt).setToIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(Some(Script.write(Await.result(bobWallet.getReceivePublicKeyScript(), 10 seconds)))) val bobCommitParams = CommitParams(nodeParamsB.channelConf.dustLimit, nodeParamsB.channelConf.htlcMinimum, nodeParamsB.channelConf.maxHtlcValueInFlight(TestConstants.fundingSatoshis, unlimited = false), nodeParamsB.channelConf.maxAcceptedHtlcs, nodeParamsA.channelConf.toRemoteDelay) .modify(_.maxHtlcValueInFlight).setToIf(tags.contains(ChannelStateTestsTags.NoMaxHtlcValueInFlight))(UInt64.MaxValue) .modify(_.dustLimit).setToIf(tags.contains(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob))(1000 sat) @@ -635,7 +633,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { } } - case class PublishedForceCloseTxs(mainTx_opt: Option[Transaction], anchorTx_opt: Option[Transaction], htlcSuccessTxs: Seq[Transaction], htlcTimeoutTxs: Seq[Transaction]) { + case class PublishedForceCloseTxs(mainTx_opt: Option[Transaction], anchorTx: Transaction, htlcSuccessTxs: Seq[Transaction], htlcTimeoutTxs: Seq[Transaction]) { val htlcTxs: Seq[Transaction] = htlcSuccessTxs ++ htlcTimeoutTxs } @@ -654,43 +652,24 @@ trait ChannelStateTestsBase extends Assertions with Eventually { assert(commitTx.txid == closingState.commitments.latest.localCommit.txId) val commitInput = closingState.commitments.latest.commitInput(s.underlyingActor.channelKeys) Transaction.correctlySpends(commitTx, Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - val publishedAnchorTx_opt = closingState.commitments.latest.commitmentFormat match { - case DefaultCommitmentFormat => None - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(s2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx].tx) - } + val publishedAnchorTx = s2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx].tx // if s has a main output in the commit tx (when it has a non-dust balance), it should be claimed val publishedMainTx_opt = localCommitPublished.localOutput_opt.map(_ => s2blockchain.expectFinalTxPublished("local-main-delayed").tx) - val (publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) = closingState.commitments.latest.commitmentFormat match { - case Transactions.DefaultCommitmentFormat => - // all htlcs success/timeout should be published as-is, we cannot RBF - val publishedHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => - val htlcTx = s2blockchain.expectMsgType[PublishFinalTx] - assert(htlcTx.parentTx_opt.contains(commitTx.txid)) - assert(localCommitPublished.htlcOutputs.contains(htlcTx.input)) - assert(htlcTx.desc == "htlc-success" || htlcTx.desc == "htlc-timeout") - htlcTx - } - val successTxs = publishedHtlcTxs.filter(_.desc == "htlc-success").map(_.tx) - val timeoutTxs = publishedHtlcTxs.filter(_.desc == "htlc-timeout").map(_.tx) - (successTxs, timeoutTxs) - case _: Transactions.AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => - // all htlcs success/timeout should be published as replaceable txs - val publishedHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => - val htlcTx = s2blockchain.expectMsgType[PublishReplaceableTx] - assert(htlcTx.commitTx == commitTx) - assert(localCommitPublished.htlcOutputs.contains(htlcTx.txInfo.input.outPoint)) - htlcTx.txInfo - } - // the publisher actors will sign the HTLC transactions, so we sign them here to test witness validity - val successTxs = publishedHtlcTxs.collect { case tx: HtlcSuccessTx => - assert(localCommitPublished.incomingHtlcs.get(tx.input.outPoint).contains(tx.htlcId)) - tx.sign() - } - val timeoutTxs = publishedHtlcTxs.collect { case tx: HtlcTimeoutTx => - assert(localCommitPublished.outgoingHtlcs.get(tx.input.outPoint).contains(tx.htlcId)) - tx.sign() - } - (successTxs, timeoutTxs) + // all htlcs success/timeout should be published as replaceable txs + val publishedHtlcTxs = (0 until htlcSuccessCount + htlcTimeoutCount).map { _ => + val htlcTx = s2blockchain.expectMsgType[PublishReplaceableTx] + assert(htlcTx.commitTx == commitTx) + assert(localCommitPublished.htlcOutputs.contains(htlcTx.txInfo.input.outPoint)) + htlcTx.txInfo + } + // the publisher actors will sign the HTLC transactions, so we sign them here to test witness validity + val publishedHtlcSuccessTxs = publishedHtlcTxs.collect { case tx: HtlcSuccessTx => + assert(localCommitPublished.incomingHtlcs.get(tx.input.outPoint).contains(tx.htlcId)) + tx.sign() + } + val publishedHtlcTimeoutTxs = publishedHtlcTxs.collect { case tx: HtlcTimeoutTx => + assert(localCommitPublished.outgoingHtlcs.get(tx.input.outPoint).contains(tx.htlcId)) + tx.sign() } assert(publishedHtlcSuccessTxs.size == htlcSuccessCount) assert(publishedHtlcTimeoutTxs.size == htlcTimeoutCount) @@ -708,13 +687,13 @@ trait ChannelStateTestsBase extends Assertions with Eventually { s2blockchain.expectNoMessage(100 millis) // once our closing transactions are published, we watch for their confirmation - (publishedMainTx_opt ++ publishedAnchorTx_opt ++ publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(tx => { + (publishedMainTx_opt ++ Seq(publishedAnchorTx) ++ publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(tx => { s ! WatchOutputSpentTriggered(tx.txOut.headOption.map(_.amount).getOrElse(330 sat), tx) s2blockchain.expectWatchTxConfirmed(tx.txid) }) // s is now in CLOSING state with txs pending for confirmation before going in CLOSED state - val publishedTxs = PublishedForceCloseTxs(publishedMainTx_opt, publishedAnchorTx_opt, publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) + val publishedTxs = PublishedForceCloseTxs(publishedMainTx_opt, publishedAnchorTx, publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) (localCommitPublished, publishedTxs) } @@ -733,10 +712,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { assert(remoteCommitPublished.outgoingHtlcs.size == htlcTimeoutCount) // If anchor outputs is used, we use the anchor output to bump the fees if necessary. - val publishedAnchorTx_opt = closingData.commitments.latest.commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => Some(s2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx].tx) - case Transactions.DefaultCommitmentFormat => None - } + val publishedAnchorTx = s2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx].tx // if s has a main output in the commit tx (when it has a non-dust balance), it should be claimed val publishedMainTx_opt = remoteCommitPublished.localOutput_opt.map(_ => s2blockchain.expectFinalTxPublished("remote-main-delayed").tx) publishedMainTx_opt.foreach(tx => Transaction.correctlySpends(tx, rCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) @@ -770,13 +746,13 @@ trait ChannelStateTestsBase extends Assertions with Eventually { s2blockchain.expectNoMessage(100 millis) // once our closing transactions are published, we watch for their confirmation - (publishedMainTx_opt ++ publishedAnchorTx_opt ++ publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(tx => { + (publishedMainTx_opt ++ Seq(publishedAnchorTx) ++ publishedHtlcSuccessTxs ++ publishedHtlcTimeoutTxs).foreach(tx => { s ! WatchOutputSpentTriggered(tx.txOut.headOption.map(_.amount).getOrElse(330 sat), tx) s2blockchain.expectWatchTxConfirmed(tx.txid) }) // s is now in CLOSING state with txs pending for confirmation before going in CLOSED state - val publishedTxs = PublishedForceCloseTxs(publishedMainTx_opt, publishedAnchorTx_opt, publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) + val publishedTxs = PublishedForceCloseTxs(publishedMainTx_opt, publishedAnchorTx, publishedHtlcSuccessTxs, publishedHtlcTimeoutTxs) (remoteCommitPublished, publishedTxs) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index d82fe3ffea..07b371dcca 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.io.Peer.OpenChannelResponse -import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, PhoenixSimpleTaprootChannelCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{PhoenixSimpleTaprootChannelCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, OpenChannel, TlvStream} import fr.acinq.eclair.{CltvExpiryDelta, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -41,7 +41,6 @@ import scala.concurrent.duration._ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { private val HighRemoteDustLimit = "high_remote_dust_limit" - private val StandardChannelType = "standard_channel_type" case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], aliceOpenReplyTo: TestProbe, alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, listener: TestProbe) @@ -53,7 +52,6 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS import setup._ val channelParams = computeChannelParams(setup, test.tags) - .modify(_.channelType).setToIf(test.tags.contains(StandardChannelType))(ChannelTypes.Standard()) val listener = TestProbe() within(30 seconds) { alice.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelAborted]) @@ -66,30 +64,11 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS } } - test("recv AcceptChannel") { f => + test("recv AcceptChannel (anchor outputs zero fee htlc txs)") { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] // Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script. assert(accept.upfrontShutdownScript_opt.contains(ByteVector.empty)) - assert(accept.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) - bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - aliceOpenReplyTo.expectNoMessage() - } - - test("recv AcceptChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - import f._ - val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs())) - bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) - aliceOpenReplyTo.expectNoMessage() - } - - test("recv AcceptChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - import f._ - val accept = bob2alice.expectMsgType[AcceptChannel] assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) @@ -97,7 +76,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS aliceOpenReplyTo.expectNoMessage() } - test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => + test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) @@ -130,7 +109,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptChannel (channel type not set but feature bit set)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptChannel (channel type not set)") { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) @@ -141,24 +120,13 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(StandardChannelType)) { f => - import f._ - val accept = bob2alice.expectMsgType[AcceptChannel] - // Alice asked for a standard channel whereas they both support anchor outputs. - assert(accept.channelType_opt.contains(ChannelTypes.Standard())) - bob2alice.forward(alice, accept) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].commitmentFormat == DefaultCommitmentFormat) - aliceOpenReplyTo.expectNoMessage() - } - test("recv AcceptChannel (invalid channel type)") { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) + assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) val invalidAccept = accept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs()))) bob2alice.forward(alice, invalidAccept) - alice2bob.expectMsg(Error(accept.temporaryChannelId, "invalid channel_type=anchor_outputs, expected channel_type=static_remotekey")) + alice2bob.expectMsg(Error(accept.temporaryChannelId, "invalid channel_type=anchor_outputs")) listener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSED) aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptDualFundedChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptDualFundedChannelStateSpec.scala index 38e52ad4e7..0ab2f06274 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptDualFundedChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptDualFundedChannelStateSpec.scala @@ -68,7 +68,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt } } - test("recv AcceptDualFundedChannel", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -87,7 +87,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectNoMessage() } - test("recv AcceptDualFundedChannel (with liquidity ads)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (with liquidity ads)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -100,7 +100,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv AcceptDualFundedChannel (with invalid liquidity ads sig)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (with invalid liquidity ads sig)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -113,7 +113,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == CLOSED) } - test("recv AcceptDualFundedChannel (with invalid liquidity ads amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (with invalid liquidity ads amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel].copy(fundingAmount = TestConstants.nonInitiatorFundingSatoshis / 2) @@ -122,7 +122,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == CLOSED) } - test("recv AcceptDualFundedChannel (without liquidity ads response)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (without liquidity ads response)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -132,7 +132,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == CLOSED) } - test("recv AcceptDualFundedChannel (with push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.NonInitiatorPushAmount), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (with push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.NonInitiatorPushAmount)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -144,7 +144,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv AcceptDualFundedChannel (require confirmed inputs)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(bobRequiresConfirmedInputs), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (require confirmed inputs)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(bobRequiresConfirmedInputs)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -155,7 +155,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv AcceptDualFundedChannel (negative funding amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (negative funding amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -166,7 +166,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptDualFundedChannel (invalid push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.NonInitiatorPushAmount), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (invalid push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.NonInitiatorPushAmount)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] @@ -178,7 +178,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptDualFundedChannel (invalid max accepted htlcs)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (invalid max accepted htlcs)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1 @@ -190,7 +190,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptDualFundedChannel (dust limit too low)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (dust limit too low)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] val lowDustLimit = Channel.MIN_DUST_LIMIT - 1.sat @@ -202,7 +202,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptDualFundedChannel (dust limit too high)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (dust limit too high)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] val highDustLimit = Alice.nodeParams.channelConf.maxRemoteDustLimit + 1.sat @@ -214,7 +214,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv AcceptDualFundedChannel (to_self_delay too high)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv AcceptDualFundedChannel (to_self_delay too high)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptDualFundedChannel] val delayTooHigh = Alice.nodeParams.channelConf.maxToLocalDelay + 1 @@ -226,7 +226,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected] } - test("recv Error", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice ! Error(ByteVector32.Zeroes, "dual funding not supported") listener.expectMsgType[ChannelAborted] @@ -234,7 +234,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsgType[OpenChannelResponse.RemoteError] } - test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val sender = TestProbe() val c = CMD_CLOSE(sender.ref, None, None) @@ -245,7 +245,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsg(OpenChannelResponse.Cancelled) } - test("recv INPUT_DISCONNECTED", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice ! INPUT_DISCONNECTED listener.expectMsgType[ChannelAborted] @@ -253,7 +253,7 @@ class WaitForAcceptDualFundedChannelStateSpec extends TestKitBaseClass with Fixt aliceOpenReplyTo.expectMsg(OpenChannelResponse.Disconnected) } - test("recv TickChannelOpenTimeout", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv TickChannelOpenTimeout", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice ! TickChannelOpenTimeout listener.expectMsgType[ChannelAborted] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 47369f4464..3248890918 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -17,14 +17,13 @@ package fr.acinq.eclair.channel.states.a import akka.testkit.{TestFSMRef, TestProbe} -import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.{Block, Btc, ByteVector32, SatoshiLong, TxId} import fr.acinq.eclair.TestConstants.Bob import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} -import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, OpenChannel, TlvStream} import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -39,8 +38,6 @@ import scala.concurrent.duration._ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { - private val StandardChannelType = "standard_channel_type" - case class FixtureParam(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe, listener: TestProbe) override def withFixture(test: OneArgTest): Outcome = { @@ -48,7 +45,6 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui import setup._ val channelParams = computeChannelParams(setup, test.tags) - .modify(_.channelType).setToIf(test.tags.contains(StandardChannelType))(ChannelTypes.Standard()) val listener = TestProbe() within(30 seconds) { bob.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelAborted]) @@ -59,37 +55,18 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui } } - test("recv OpenChannel") { f => + test("recv OpenChannel (anchor outputs zero fee htlc txs)") { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] // Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script. assert(open.upfrontShutdownScript_opt.contains(ByteVector.empty)) - // We always send a channel type, even for standard channels. - assert(open.channelType_opt.contains(ChannelTypes.StaticRemoteKey())) - alice2bob.forward(bob) - awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) - } - - test("recv OpenChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - import f._ - val open = alice2bob.expectMsgType[OpenChannel] - assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputs())) - alice2bob.forward(bob) - awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == UnsafeLegacyAnchorOutputsCommitmentFormat) - } - - test("recv OpenChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - import f._ - val open = alice2bob.expectMsgType[OpenChannel] assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => + test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true))) @@ -98,15 +75,6 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(StandardChannelType)) { f => - import f._ - val open = alice2bob.expectMsgType[OpenChannel] - assert(open.channelType_opt.contains(ChannelTypes.Standard())) - alice2bob.forward(bob) - awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == DefaultCommitmentFormat) - } - test("recv OpenChannel (simple taproot channels)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] @@ -221,19 +189,6 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui awaitCond(bob.stateName == CLOSED) } - test("recv OpenChannel (fee too low, but still valid)") { f => - import f._ - val open = alice2bob.expectMsgType[OpenChannel] - // set a very small fee - val tinyFee = FeeratePerKw(253 sat) - bob ! open.copy(feeratePerKw = tinyFee) - val error = bob2alice.expectMsgType[Error] - // we check that the error uses the temporary channel id - assert(error == Error(open.temporaryChannelId, "local/remote feerates are too different: remoteFeeratePerKw=253 localFeeratePerKw=10000")) - listener.expectMsgType[ChannelAborted] - awaitCond(bob.stateName == CLOSED) - } - test("recv OpenChannel (fee below absolute valid minimum)") { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] @@ -304,7 +259,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui test("recv OpenChannel (empty upfront shutdown script)", Tag(ChannelStateTestsTags.UpfrontShutdownScript)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] - val open1 = open.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))) + val open1 = open.copy(tlvStream = TlvStream(open.tlvStream.records.filterNot(_.isInstanceOf[ChannelTlv.UpfrontShutdownScriptTlv]) + ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))) alice2bob.forward(bob, open1) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelParams.remoteParams.upfrontShutdownScript_opt.isEmpty) @@ -319,7 +274,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui awaitCond(bob.stateName == CLOSED) } - test("recv OpenChannel (zeroconf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv OpenChannel (zeroconf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob, open) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenDualFundedChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenDualFundedChannelStateSpec.scala index e0436d5203..07b7ae96fb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenDualFundedChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenDualFundedChannelStateSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.channel.states.a import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, SatoshiLong} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.Bob import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} @@ -60,7 +60,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur } } - test("recv OpenDualFundedChannel", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] @@ -91,7 +91,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv OpenDualFundedChannel (with liquidity ads)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (with liquidity ads)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] @@ -103,7 +103,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur assert(accept.willFund_opt.nonEmpty) } - test("recv OpenDualFundedChannel (with liquidity ads and fee credit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (with liquidity ads and fee credit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.LiquidityAds)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] @@ -116,7 +116,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur assert(accept.tlvStream.get[ChannelTlv.FeeCreditUsedTlv].map(_.amount).contains(2_500_000 msat)) } - test("recv OpenDualFundedChannel (with push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (with push amount)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] @@ -127,7 +127,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv OpenDualFundedChannel (require confirmed inputs)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(aliceRequiresConfirmedInputs)) { f => + test("recv OpenDualFundedChannel (require confirmed inputs)", Tag(ChannelStateTestsTags.DualFunding), Tag(aliceRequiresConfirmedInputs)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] @@ -138,7 +138,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CREATED) } - test("recv OpenDualFundedChannel (invalid chain)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (invalid chain)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] val chain = BlockHash(randomBytes32()) @@ -149,7 +149,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (funding too low)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (funding too low)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] bob ! open.copy(fundingAmount = 100 sat) @@ -159,7 +159,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (invalid push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (invalid push amount)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.NoPushAmount)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] bob ! open.copy(fundingAmount = 50_000 sat, tlvStream = open.tlvStream.copy(records = open.tlvStream.records + ChannelTlv.PushAmountTlv(50_000_001 msat))) @@ -169,7 +169,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (invalid max accepted htlcs)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (invalid max accepted htlcs)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1 @@ -180,7 +180,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (to_self_delay too high)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (to_self_delay too high)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] val delayTooHigh = Bob.nodeParams.channelConf.maxToLocalDelay + 1 @@ -191,7 +191,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (dust limit too high)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (dust limit too high)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] val dustLimitTooHigh = Bob.nodeParams.channelConf.maxRemoteDustLimit + 1.sat @@ -202,7 +202,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv OpenDualFundedChannel (dust limit too small)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv OpenDualFundedChannel (dust limit too small)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val open = alice2bob.expectMsgType[OpenDualFundedChannel] val dustLimitTooSmall = Channel.MIN_DUST_LIMIT - 1.sat @@ -213,14 +213,14 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv Error", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ bob ! Error(ByteVector32.Zeroes, "dual funding not supported") bobListener.expectMsgType[ChannelAborted] awaitCond(bob.stateName == CLOSED) } - test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val sender = TestProbe() val cmd = CMD_CLOSE(sender.ref, None, None) @@ -230,7 +230,7 @@ class WaitForOpenDualFundedChannelStateSpec extends TestKitBaseClass with Fixtur awaitCond(bob.stateName == CLOSED) } - test("recv INPUT_DISCONNECTED", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ bob ! INPUT_DISCONNECTED bobListener.expectMsgType[ChannelAborted] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala index 4d5f307fbc..04c6a37709 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala @@ -126,7 +126,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny assert(aliceData.latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx.txid == fundingTxId) } - test("complete interactive-tx protocol (zero-conf)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("complete interactive-tx protocol (zero-conf)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val listener = TestProbe() @@ -425,7 +425,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny reconnect(f, fundingTxId, commitmentFormat, aliceExpectsCommitSig = true, bobExpectsCommitSig = true) } - test("recv INPUT_DISCONNECTED (commit_sig not received)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (commit_sig not received)", Tag(ChannelStateTestsTags.DualFunding)) { f => testReconnectCommitSigNotReceived(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -493,7 +493,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny reconnect(f, fundingTxId, commitmentFormat, aliceExpectsCommitSig = false, bobExpectsCommitSig = true) } - test("recv INPUT_DISCONNECTED (commit_sig received by Alice)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (commit_sig received by Alice)", Tag(ChannelStateTestsTags.DualFunding)) { f => testReconnectCommitSigReceivedByAlice(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -501,7 +501,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny testReconnectCommitSigReceivedByAlice(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } - test("recv INPUT_DISCONNECTED (commit_sig received by Bob)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (commit_sig received by Bob)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId @@ -579,7 +579,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny assert(listener.expectMsgType[TransactionPublished].tx.txid == fundingTx.txid) } - test("recv INPUT_DISCONNECTED (commit_sig received)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (commit_sig received)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 2a13707216..c4a77c07d7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -25,7 +25,6 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.ChannelStateTestsBase -import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -82,12 +81,10 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun test("recv FundingCreated (funder can't pay fees)", Tag(FunderBelowCommitFees)) { f => import f._ - val fees = Transactions.weight2fee(TestConstants.feeratePerKw, Transactions.DefaultCommitmentFormat.commitWeight) - val missing = fees - 100.sat val fundingCreated = alice2bob.expectMsgType[FundingCreated] alice2bob.forward(bob) val error = bob2alice.expectMsgType[Error] - assert(error == Error(fundingCreated.temporaryChannelId, CannotAffordFirstCommitFees(fundingCreated.temporaryChannelId, missing, fees).getMessage)) + assert(error == Error(fundingCreated.temporaryChannelId, CannotAffordFirstCommitFees(fundingCreated.temporaryChannelId, 3370 sat, 3470 sat).getMessage)) awaitCond(bob.stateName == CLOSED) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 20f7ff0505..08fc290bf5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -92,7 +92,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Created] } - test("recv FundingSigned with valid signature (zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv FundingSigned with valid signature (zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ bob2alice.expectMsgType[FundingSigned] bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 1b813e610e..51fbdd8840 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -27,6 +27,7 @@ import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Announcements +import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -147,7 +148,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } - test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid val aliceIds = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].aliases @@ -172,7 +173,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } - test("recv ChannelReady (zero-conf, no alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv ChannelReady (zero-conf, no alias)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid val aliceIds = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].aliases @@ -218,7 +219,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } - test("recv ChannelReady (public, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv ChannelReady (public, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid val aliceIds = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].aliases @@ -240,7 +241,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } - test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (remote commit)") { f => import f._ // bob publishes his commitment tx val tx = bob.signCommitTx() @@ -265,9 +266,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu alice ! Error(ByteVector32.Zeroes, "oops") aliceListener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[TxPublisher.PublishTx] - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushAmount)) { f => @@ -295,8 +294,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu alice ! CMD_FORCECLOSE(sender.ref) aliceListener.expectMsgType[ChannelAborted] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[TxPublisher.PublishTx] - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala index f307e2dfe7..7566087c06 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingConfirmedStateSpec.scala @@ -182,7 +182,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture assert(bob.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED) } - test("recv WatchPublishedTriggered (initiator)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchPublishedTriggered (initiator)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx alice ! WatchPublishedTriggered(fundingTx) @@ -193,7 +193,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_READY) } - test("recv WatchPublishedTriggered (non-initiator)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchPublishedTriggered (non-initiator)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx bob ! WatchPublishedTriggered(fundingTx) @@ -204,7 +204,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_READY) } - test("recv WatchPublishedTriggered (offline)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchPublishedTriggered (offline)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.ScidAlias)) { f => import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx alice ! INPUT_DISCONNECTED @@ -755,7 +755,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_READY) } - test("recv WatchFundingSpentTriggered while offline (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered while offline (remote commit)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx alice ! INPUT_DISCONNECTED @@ -782,7 +782,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateName == CLOSING) } - test("recv WatchFundingSpentTriggered while offline (previous tx)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered while offline (previous tx)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx @@ -818,7 +818,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateName == CLOSING) } - test("recv WatchFundingSpentTriggered after restart (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered after restart (remote commit)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val aliceData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED] @@ -851,7 +851,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob2.stateName == CLOSING) } - test("recv WatchFundingSpentTriggered after restart (previous tx)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered after restart (previous tx)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx @@ -983,7 +983,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture bob2alice.expectNoMessage(100 millis) } - test("recv INPUT_DISCONNECTED (unsigned rbf attempt)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (unsigned rbf attempt)", Tag(ChannelStateTestsTags.DualFunding)) { f => testDisconnectUnsignedRbfAttempt(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1043,7 +1043,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations) } - test("recv INPUT_DISCONNECTED (rbf commit_sig received by Alice)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (rbf commit_sig received by Alice)", Tag(ChannelStateTestsTags.DualFunding)) { f => testDisconnectRbfCommitSigReceivedAlice(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1104,7 +1104,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations) } - test("recv INPUT_DISCONNECTED (rbf commit_sig received by Bob)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (rbf commit_sig received by Bob)", Tag(ChannelStateTestsTags.DualFunding)) { f => testDisconnectRbfCommitSigReceivedBob(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1196,7 +1196,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations) } - test("recv INPUT_DISCONNECTED (rbf commit_sig received)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (rbf commit_sig received)", Tag(ChannelStateTestsTags.DualFunding)) { f => testDisconnectRbfCommitSigReceived(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1298,7 +1298,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations) } - test("recv INPUT_DISCONNECTED (rbf tx_signatures partially received)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_DISCONNECTED (rbf tx_signatures partially received)", Tag(ChannelStateTestsTags.DualFunding)) { f => testDisconnectTxSigsPartiallyReceived(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1311,12 +1311,10 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "dual funding d34d") awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[TxPublisher.PublishTx] // claim-main-delayed - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } - test("recv Error (remote commit published)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error (remote commit published)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "force-closing channel, bye-bye") @@ -1341,7 +1339,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture alice2blockchain.expectNoMessage(100 millis) } - test("recv Error (previous tx confirms)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error (previous tx confirms)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx @@ -1419,10 +1417,10 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture awaitCond(alice.stateName == CLOSING) aliceListener.expectMsgType[ChannelAborted] alice2blockchain.expectFinalTxPublished(commitTx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") Transaction.correctlySpends(claimMain.tx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain.input) } def restartNodes(f: FixtureParam, aliceData: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED, bobData: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED): (TestFSMRef[ChannelState, ChannelData, Channel], TestFSMRef[ChannelState, ChannelData, Channel]) = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala index 752b769125..25a7022584 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForDualFundingReadyStateSpec.scala @@ -107,7 +107,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF } } - test("recv ChannelReady", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv ChannelReady", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice.underlyingActor.nodeParams.db.peers.addOrUpdateRelayFees(bob.underlyingActor.nodeParams.nodeId, RelayFees(20 msat, 125)) @@ -168,7 +168,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF bob2alice.expectNoMessage(100 millis) } - test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val listenerA = TestProbe() @@ -204,7 +204,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF bob2alice.expectNoMessage(100 millis) } - test("recv ChannelReady (public channel)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + test("recv ChannelReady (public channel)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val aliceChannelReady = alice2bob.expectMsgType[ChannelReady] @@ -255,7 +255,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].lastAnnouncement_opt.nonEmpty) } - test("recv TxInitRbf", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv TxInitRbf", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice2bob.expectMsgType[ChannelReady] alice ! TxInitRbf(channelId(alice), 0, TestConstants.feeratePerKw * 1.1) @@ -263,7 +263,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_READY) } - test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ // bob publishes his commitment tx val bobCommitTx = bob.signCommitTx() @@ -275,7 +275,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF awaitCond(alice.stateName == CLOSING) } - test("recv WatchFundingSpentTriggered (unrecognized commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (unrecognized commit)", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ alice2bob.expectMsgType[ChannelReady] alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0)) @@ -283,7 +283,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_READY) } - test("recv Error", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val commitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "dual funding failure") @@ -295,7 +295,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == commitTx.txid) } - test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_CLOSE", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val sender = TestProbe() val c = CMD_CLOSE(sender.ref, None, None) @@ -303,7 +303,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF sender.expectMsg(RES_FAILURE(c, CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_DUAL_FUNDING_READY))) } - test("recv CMD_FORCECLOSE", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_FORCECLOSE", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ val sender = TestProbe() val commitTx = alice.signCommitTx() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 5b9ae47675..81f33b5b6f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -27,6 +27,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.publish.TxPublisher.PublishFinalTx import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} +import fr.acinq.eclair.testutils.PimpTestProbe.convert import fr.acinq.eclair.transactions.Scripts.multiSig2of2 import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream} import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomKey} @@ -224,7 +225,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF awaitCond(bob.stateName == CLOSED) } - test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (remote commit)") { f => import f._ // bob publishes his commitment tx val tx = bob.signCommitTx() @@ -250,9 +251,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF alice ! Error(ByteVector32.Zeroes, "oops") awaitCond(alice.stateName == CLOSING) listener.expectMsgType[ChannelAborted] - assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[TxPublisher.PublishTx] // claim-main-delayed - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushAmount)) { f => @@ -284,9 +283,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) listener.expectMsgType[ChannelAborted] - assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[TxPublisher.PublishTx] // claim-main-delayed - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala index 6b70e16caf..668f445eef 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.relay.Relayer.RelayForward import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions.{UnsignedHtlcSuccessTx, UnsignedHtlcTimeoutTx} +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{channel, _} import org.scalatest.Outcome @@ -455,11 +455,11 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL // the HTLC times out, alice needs to close the channel alice ! CurrentBlockHeight(add.cltvExpiry.blockHeight) alice2blockchain.expectFinalTxPublished(commitTx.txid) + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val mainDelayedTx = alice2blockchain.expectFinalTxPublished("local-main-delayed") - assert(alice2blockchain.expectFinalTxPublished("htlc-timeout").input == htlcTimeoutTx.input.outPoint) + assert(alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].input == htlcTimeoutTx.input) alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(mainDelayedTx.input) - alice2blockchain.expectWatchOutputSpent(htlcTimeoutTx.input.outPoint) + alice2blockchain.expectWatchOutputsSpent(Seq(mainDelayedTx.input, anchorTx.input.outPoint, htlcTimeoutTx.input.outPoint)) alice2blockchain.expectNoMessage(100 millis) channelUpdateListener.expectMsgType[LocalChannelDown] @@ -489,11 +489,11 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt) // bob publishes a set of force-close transactions, including the HTLC-success using the received preimage bob2blockchain.expectFinalTxPublished(commitTx.txid) + val anchorTx = bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val mainDelayedTx = bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - bob2blockchain.expectWatchOutputSpent(mainDelayedTx.input) - bob2blockchain.expectWatchOutputSpent(htlcSuccessTx.input.outPoint) - assert(bob2blockchain.expectFinalTxPublished("htlc-success").input == htlcSuccessTx.input.outPoint) + bob2blockchain.expectWatchOutputsSpent(Seq(mainDelayedTx.input, anchorTx.input.outPoint, htlcSuccessTx.input.outPoint)) + assert(bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input == htlcSuccessTx.input) bob2blockchain.expectNoMessage(100 millis) channelUpdateListener.expectMsgType[LocalChannelDown] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala index af540aa1fa..9f997cb960 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala @@ -639,10 +639,10 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(commitment.localCommit.spec.toLocal == 650_000_000.msat) assert(commitment.localChannelReserve == 15_000.sat) val commitFees = Transactions.commitTxTotalCost(commitment.remoteCommitParams.dustLimit, commitment.remoteCommit.spec, commitment.commitmentFormat) - assert(commitFees > 20_000.sat) + assert(commitFees > 7_000.sat) val sender = TestProbe() - val cmd = CMD_SPLICE(sender.ref, spliceIn_opt = None, Some(SpliceOut(630_000 sat, defaultSpliceOutScriptPubKey)), requestFunding_opt = None, channelType_opt = None) + val cmd = CMD_SPLICE(sender.ref, spliceIn_opt = None, Some(SpliceOut(643_000 sat, defaultSpliceOutScriptPubKey)), requestFunding_opt = None, channelType_opt = None) alice ! cmd exchangeStfu(f) sender.expectMsgType[RES_FAILURE[_, _]] @@ -762,7 +762,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("recv CMD_SPLICE (accepting upgrade channel to taproot)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv CMD_SPLICE (accepting upgrade channel to taproot)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val htlcs = setupHtlcs(f) @@ -772,7 +772,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("recv CMD_SPLICE (rejecting upgrade channel to taproot)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_SPLICE (rejecting upgrade channel to taproot)") { f => import f._ val htlcs = setupHtlcs(f) @@ -1007,7 +1007,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice2bob.expectMsgType[TxAbort].toAscii.contains("transaction is already confirmed")) } - test("recv CMD_BUMP_FUNDING_FEE (transaction is using 0-conf)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_BUMP_FUNDING_FEE (transaction is using 0-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat))) @@ -1222,7 +1222,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.map(_.fundingTxIndex) == Seq.empty) } - test("splice local/remote locking (zero-conf)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight), Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("splice local/remote locking (zero-conf)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(250_000 sat))) @@ -1391,7 +1391,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik } } - test("recv announcement_signatures", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip)) { f => + test("recv announcement_signatures", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip)) { f => import f._ val aliceListener = TestProbe() @@ -1484,7 +1484,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_NORMAL].lastAnnouncement_opt.contains(spliceAnn2)) } - test("recv announcement_signatures (after restart)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip)) { f => + test("recv announcement_signatures (after restart)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip)) { f => import f._ val aliceListener = TestProbe() @@ -1682,7 +1682,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(!alice.stateData.asInstanceOf[DATA_NORMAL].commitments.hasPendingOrProposedHtlcs) } - test("recv UpdateAddHtlc before splice confirms (zero-conf)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv UpdateAddHtlc before splice confirms (zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val spliceTx = initiateSplice(f, spliceOut_opt = Some(SpliceOut(50_000 sat, defaultSpliceOutScriptPubKey))) @@ -1706,7 +1706,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.head.localCommit.spec.htlcs.size == 1) } - test("recv UpdateAddHtlc while splice is being locked", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv UpdateAddHtlc while splice is being locked", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val spliceTx1 = initiateSplice(f, spliceOut_opt = Some(SpliceOut(50_000 sat, defaultSpliceOutScriptPubKey))) @@ -2070,7 +2070,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("disconnect (commit_sig received by alice)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("disconnect (commit_sig received by alice)") { f => disconnectCommitSigReceivedAlice(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2139,7 +2139,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("disconnect (commit_sig received by bob)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("disconnect (commit_sig received by bob)") { f => disconnectCommitSigReceivedBob(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2203,7 +2203,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("disconnect (commit_sig received)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("disconnect (commit_sig received)") { f => disconnectCommitSigReceived(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2282,7 +2282,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("disconnect (tx_signatures received by alice)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("disconnect (tx_signatures received by alice)") { f => disconnectTxSigsReceivedAlice(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2290,7 +2290,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik disconnectTxSigsReceivedAlice(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } - test("disconnect (tx_signatures received by alice, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("disconnect (tx_signatures received by alice, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val htlcs = setupHtlcs(f) @@ -2611,7 +2611,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("don't resend splice_locked when zero-conf channel confirms", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("don't resend splice_locked when zero-conf channel confirms", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val fundingTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat, pushAmount = 0 msat))) @@ -3307,19 +3307,22 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2bob.expectMsgType[Error] val commitTx2 = alice2blockchain.expectFinalTxPublished("commit-tx").tx Transaction.correctlySpends(commitTx2, Seq(fundingTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + val anchorAlice = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMainAlice = alice2blockchain.expectFinalTxPublished("local-main-delayed") // Alice publishes her htlc timeout transactions. - val aliceHtlcTimeout = htlcs.aliceToBob.map(_ => alice2blockchain.expectFinalTxPublished("htlc-timeout")) - aliceHtlcTimeout.foreach(htlcTx => Transaction.correctlySpends(htlcTx.tx, Seq(commitTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + val aliceHtlcTimeout = htlcs.aliceToBob.map(_ => alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].sign()) + aliceHtlcTimeout.foreach(htlcTx => Transaction.correctlySpends(htlcTx, Seq(commitTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) // Bob detects Alice's commit tx. bob ! WatchFundingSpentTriggered(commitTx2) + val anchorBob = bob2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] + val claimMainBob = bob2blockchain.expectFinalTxPublished("remote-main-delayed") val bobHtlcTimeout = htlcs.bobToAlice.map(_ => bob2blockchain.expectReplaceableTxPublished[ClaimHtlcTimeoutTx]) bobHtlcTimeout.foreach(htlcTx => Transaction.correctlySpends(htlcTx.sign(), Seq(commitTx2), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) bob2blockchain.expectWatchTxConfirmed(commitTx2.txid) - bob2blockchain.expectWatchOutputsSpent(aliceHtlcTimeout.map(_.input) ++ bobHtlcTimeout.map(_.input.outPoint)) + bob2blockchain.expectWatchOutputsSpent(Seq(claimMainBob.input, anchorBob.input.outPoint) ++ aliceHtlcTimeout.map(_.txIn.head.outPoint) ++ bobHtlcTimeout.map(_.input.outPoint)) alice2blockchain.expectWatchTxConfirmed(commitTx2.txid) - alice2blockchain.expectWatchOutputsSpent(Seq(claimMainAlice.input) ++ aliceHtlcTimeout.map(_.input) ++ bobHtlcTimeout.map(_.input.outPoint)) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMainAlice.input, anchorAlice.input.outPoint) ++ aliceHtlcTimeout.map(_.txIn.head.outPoint) ++ bobHtlcTimeout.map(_.input.outPoint)) // The first splice transaction confirms. alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1) @@ -3333,9 +3336,9 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, commitTx2) alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, claimMainAlice.tx) aliceHtlcTimeout.foreach(htlcTx => { - alice ! WatchOutputSpentTriggered(0 sat, htlcTx.tx) - alice2blockchain.expectWatchTxConfirmed(htlcTx.tx.txid) - alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx) + alice ! WatchOutputSpentTriggered(0 sat, htlcTx) + alice2blockchain.expectWatchTxConfirmed(htlcTx.txid) + alice ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx) val htlcDelayed = alice2blockchain.expectFinalTxPublished("htlc-delayed") alice2blockchain.expectWatchOutputSpent(htlcDelayed.input) alice ! WatchOutputSpentTriggered(0 sat, htlcDelayed.tx) @@ -3360,11 +3363,12 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx) }) aliceHtlcTimeout.foreach(htlcTx => { - bob ! WatchOutputSpentTriggered(0 sat, htlcTx.tx) - bob2blockchain.expectWatchTxConfirmed(htlcTx.tx.txid) - bob ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx.tx) + bob ! WatchOutputSpentTriggered(0 sat, htlcTx) + bob2blockchain.expectWatchTxConfirmed(htlcTx.txid) + bob ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, htlcTx) }) bob2blockchain.expectNoMessage(100 millis) + bob ! WatchTxConfirmedTriggered(BlockHeight(400000), 42, claimMainBob.tx) awaitCond(bob.stateName == CLOSED) assert(bob.stateData.asInstanceOf[DATA_CLOSED].closingTxId == commitTx2.txid) assert(bob.stateData.asInstanceOf[DATA_CLOSED].fundingTxId == fundingTx2.txid) @@ -3449,7 +3453,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_CLOSED].fundingTxId == fundingTx1.txid) } - test("force-close with multiple splices (previous active revoked)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("force-close with multiple splices (previous active revoked)") { f => import f._ val htlcs = setupHtlcs(f) @@ -3527,7 +3531,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_CLOSED].closingAmount == (Seq(remoteMain.tx, mainPenalty.tx) ++ htlcPenalty.map(_.tx)).map(_.txOut.head.amount).sum) } - test("force-close with multiple splices (inactive remote)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("force-close with multiple splices (inactive remote)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val htlcs = setupHtlcs(f) @@ -3727,7 +3731,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(alice.stateData.asInstanceOf[DATA_CLOSED].closingTxId == bobRevokedCommitTx.txid) } - test("force-close after channel type upgrade (latest active)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("force-close after channel type upgrade (latest active)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val htlcs = setupHtlcs(f) @@ -3824,7 +3828,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(bob.stateData.asInstanceOf[DATA_CLOSED].fundingTxId == fundingTx2.txid) } - test("force-close after channel type upgrade (previous active)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("force-close after channel type upgrade (previous active)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val htlcs = setupHtlcs(f) @@ -3865,7 +3869,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectNoMessage(100 millis) } - test("force-close after channel type upgrade (revoked previous active)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("force-close after channel type upgrade (revoked previous active)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val htlcs = setupHtlcs(f) @@ -3917,7 +3921,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(bob.stateData.asInstanceOf[DATA_CLOSED].closingTxId == aliceCommitTx.txid) } - test("force-close after channel type upgrade (revoked latest active)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("force-close after channel type upgrade (revoked latest active)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val htlcs = setupHtlcs(f) @@ -3955,7 +3959,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik assert(bob.stateData.asInstanceOf[DATA_CLOSED].fundingTxId == spliceTx.txid) } - test("force-close after channel type upgrade (revoked previous inactive)", Tag(ChannelStateTestsTags.AnchorOutputs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("force-close after channel type upgrade (revoked previous inactive)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val htlcs = setupHtlcs(f) @@ -4071,7 +4075,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik bob2blockchain.expectNoMessage(100 millis) } - test("put back watches after restart (inactive)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("put back watches after restart (inactive)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val fundingTx0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get @@ -4171,7 +4175,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik resolveHtlcs(f, htlcs) } - test("recv CMD_SPLICE (splice-in + splice-out) with pre and post splice htlcs", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_SPLICE (splice-in + splice-out) with pre and post splice htlcs") { f => spliceWithPreAndPostHtlcs(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -4179,7 +4183,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik spliceWithPreAndPostHtlcs(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } - test("recv CMD_SPLICE (splice-in + splice-out) with pending htlcs, resolved after splice locked", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_SPLICE (splice-in + splice-out) with pending htlcs, resolved after splice locked") { f => import f._ val htlcs = setupHtlcs(f) 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 fee6d8a0f3..afe94bf6c5 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 @@ -22,7 +22,6 @@ import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, SatoshiLong, Script, Transaction, TxOut} -import fr.acinq.eclair.Features.StaticRemoteKey import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ @@ -159,7 +158,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = ExpiryTooSmall(channelId(alice), CltvExpiry(currentBlockHeight + 3), expiryTooSmall, currentBlockHeight) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (expiry too big)") { f => @@ -172,7 +171,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = ExpiryTooBig(channelId(alice), maximum = maxAllowedExpiryDelta.toCltvExpiry(currentBlockHeight), actual = expiryTooBig, blockHeight = currentBlockHeight) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (value too small)") { f => @@ -183,7 +182,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = HtlcValueTooSmall(channelId(alice), 1000 msat, 50 msat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (0 msat)") { f => @@ -196,7 +195,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! add val error = HtlcValueTooSmall(channelId(bob), 1 msat, 0 msat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(200 millis) + bob2alice.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (increasing balance but still below reserve)", Tag(ChannelStateTestsTags.NoPushAmount)) { f => @@ -213,25 +212,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(sender.ref, MilliSatoshi(Int.MaxValue), randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) - alice ! add - val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(Int.MaxValue), missing = 1388843 sat, reserve = 20000 sat, fees = 8960 sat) - sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) - } - - test("recv CMD_ADD_HTLC (insufficient funds) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - import f._ - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - // The anchor outputs commitment format costs more fees for the funder (bigger commit tx + cost of anchor outputs) - assert(initialState.commitments.availableBalanceForSend < initialState.commitments.modify(_.active).apply(_.map(_.modify(_.commitmentFormat).setTo(DefaultCommitmentFormat))).availableBalanceForSend) val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add - val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 20000 sat, fees = 3900 sat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (insufficient funds, missing 1 msat)") { f => @@ -240,22 +225,21 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val add = CMD_ADD_HTLC(sender.ref, initialState.commitments.availableBalanceForSend + 1.msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) bob ! add - val error = InsufficientFunds(channelId(alice), amount = add.amount, missing = 0 sat, reserve = 10000 sat, fees = 0 sat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(200 millis) + bob2alice.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (HTLC dips into remote funder fee reserve)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f => import f._ val sender = TestProbe() - addHtlc(758_640_000 msat, alice, bob, alice2bob, bob2alice) + addHtlc(772_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat) // At this point alice has the minimal amount to sustain a channel. // Alice maintains an extra reserve to accommodate for a few more HTLCs, so the first few HTLCs should be allowed. - val htlcs = (1 to 7).map { _ => + val htlcs = (1 to 9).map { _ => bob ! CMD_ADD_HTLC(sender.ref, 12_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] val add = bob2alice.expectMsgType[UpdateAddHtlc] @@ -266,7 +250,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // But this one will dip alice below her reserve: we must wait for the previous HTLCs to settle before sending any more. val failedAdd = CMD_ADD_HTLC(sender.ref, 11_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) bob ! failedAdd - val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 1360 sat, 20_000 sat, 22_720 sat) + val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 200 sat, 20_000 sat, 8_200 sat) sender.expectMsg(RES_ADD_FAILED(failedAdd, error, Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate))) // If Bob had sent this HTLC, Alice would have accepted dipping into her reserve. @@ -279,29 +263,34 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CMD_ADD_HTLC (HTLC dips into remote funder channel reserve)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f => import f._ val sender = TestProbe() - addHtlc(758_640_000 msat, alice, bob, alice2bob, bob2alice) + addHtlc(772_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat) // We increase the feerate to get Alice's balance closer to her channel reserve. - bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(17_500 sat))) - updateFee(FeeratePerKw(17_500 sat), alice, bob, alice2bob, bob2alice) + alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(3_500 sat))) + bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(3_500 sat))) + updateFee(FeeratePerKw(3_500 sat), alice, bob, alice2bob, bob2alice) // At this point alice has the minimal amount to sustain a channel. - // Alice maintains an extra reserve to accommodate for a one more HTLCs, so the first few HTLCs should be allowed. - bob ! CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) - sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] - val add = bob2alice.expectMsgType[UpdateAddHtlc] - bob2alice.forward(alice, add) + // Alice maintains an extra reserve to accommodate for a few more HTLCs, so the first few HTLCs should be allowed. + val htlcs = (1 to 4).map { _ => + bob ! CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] + val add = bob2alice.expectMsgType[UpdateAddHtlc] + bob2alice.forward(alice, add) + add + } // But this one will dip alice below her reserve: we must wait for the previous HTLCs to settle before sending any more. val failedAdd = CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) bob ! failedAdd - val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 340 sat, 20_000 sat, 21_700 sat) + val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 206 sat, 20_000 sat, 8_206 sat) sender.expectMsg(RES_ADD_FAILED(failedAdd, error, Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate))) // If Bob had sent this HTLC, Alice would have accepted dipping into her reserve. + val add = htlcs.last.copy(id = htlcs.last.id + 1) val proposedChanges = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.proposed.size - alice ! add.copy(id = add.id + 1) + alice ! add awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.proposed.size == proposedChanges + 1) } @@ -309,37 +298,37 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - alice ! CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] - alice ! CMD_ADD_HTLC(sender.ref, 200000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 200_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] - alice ! CMD_ADD_HTLC(sender.ref, 51760000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 70_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(sender.ref, 1000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + val add = CMD_ADD_HTLC(sender.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add - val error = InsufficientFunds(channelId(alice), amount = 1000000 msat, missing = 1000 sat, reserve = 20000 sat, fees = 12400 sat) + val error = InsufficientFunds(channelId(alice), amount = 1_000_000 msat, missing = 1580 sat, reserve = 20_000 sat, fees = 5_190 sat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f => import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - alice ! CMD_ADD_HTLC(sender.ref, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] - alice ! CMD_ADD_HTLC(sender.ref, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 300_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(sender.ref, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + val add = CMD_ADD_HTLC(sender.ref, 500_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add - val error = InsufficientFunds(channelId(alice), amount = 500000000 msat, missing = 348240 sat, reserve = 20000 sat, fees = 12400 sat) + val error = InsufficientFunds(channelId(alice), amount = 500_000_000 msat, missing = 329_720 sat, reserve = 20_000 sat, fees = 4_760 sat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over remote max inflight htlc value)", Tag(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight)) { f => @@ -352,7 +341,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! add val error = HtlcValueTooHighInFlight(channelId(bob), maximum = UInt64(150_000_000), actual = 151_000_000 msat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(200 millis) + bob2alice.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over remote max inflight htlc value with duplicate amounts)", Tag(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight)) { f => @@ -369,7 +358,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! add1 val error = HtlcValueTooHighInFlight(channelId(bob), maximum = UInt64(150_000_000), actual = 151_000_000 msat) sender.expectMsg(RES_ADD_FAILED(add1, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(200 millis) + bob2alice.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over local max inflight htlc value)", Tag(ChannelStateTestsTags.AliceLowMaxHtlcValueInFlight)) { f => @@ -382,7 +371,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = HtlcValueTooHighInFlight(channelId(alice), maximum = UInt64(150_000_000), actual = 151_000_000 msat) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over remote max accepted htlcs)") { f => @@ -400,7 +389,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over local max accepted htlcs)") { f => @@ -418,7 +407,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! add val error = TooManyAcceptedHtlcs(channelId(bob), maximum = 30) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(200 millis) + bob2alice.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (over max dust htlc exposure)") { f => @@ -427,38 +416,28 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val aliceCommitments = initialState.commitments assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 7730.sat) - assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 8130.sat) - assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 7630.sat) - assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 8030.sat) + assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1100.sat) + assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1100.sat) + assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1000.sat) + assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1000.sat) // Alice sends HTLCs to Bob that add 10 000 sat to the dust exposure: - addHtlc(500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // dust htlc - addHtlc(1250.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // trimmed htlc - addHtlc(8250.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // slightly above the trimmed threshold -> included in the dust exposure - addHtlc(15000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // way above the trimmed threshold -> not included in the dust exposure + (1 to 20).foreach { _ => addHtlc(500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) } + addHtlc(15_000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // way above the trimmed threshold -> not included in the dust exposure crossSign(alice, bob, alice2bob, bob2alice) // Bob sends HTLCs to Alice that add 14 500 sat to the dust exposure: - addHtlc(300.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // dust htlc - addHtlc(6000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // trimmed htlc - addHtlc(8200.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // slightly above the trimmed threshold -> included in the dust exposure - addHtlc(18000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // way above the trimmed threshold -> not included in the dust exposure + (1 to 29).foreach { _ => addHtlc(500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) } + addHtlc(18_000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // way above the trimmed threshold -> not included in the dust exposure crossSign(bob, alice, bob2alice, alice2bob) // HTLCs that take Alice's dust exposure above her threshold are rejected. val dustAdd = CMD_ADD_HTLC(sender.ref, 501.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! dustAdd - sender.expectMsg(RES_ADD_FAILED(dustAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25001.sat.toMilliSatoshi), Some(initialState.channelUpdate))) - val trimmedAdd = CMD_ADD_HTLC(sender.ref, 5000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) - alice ! trimmedAdd - sender.expectMsg(RES_ADD_FAILED(trimmedAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 29500.sat.toMilliSatoshi), Some(initialState.channelUpdate))) - val justAboveTrimmedAdd = CMD_ADD_HTLC(sender.ref, 8500.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) - alice ! justAboveTrimmedAdd - sender.expectMsg(RES_ADD_FAILED(justAboveTrimmedAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 33000.sat.toMilliSatoshi), Some(initialState.channelUpdate))) + sender.expectMsg(RES_ADD_FAILED(dustAdd, LocalDustHtlcExposureTooHigh(channelId(alice), 25_000.sat, 25_001.sat.toMilliSatoshi), Some(initialState.channelUpdate))) // HTLCs that don't contribute to dust exposure are accepted. - alice ! CMD_ADD_HTLC(sender.ref, 25000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + alice ! CMD_ADD_HTLC(sender.ref, 25_000.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] } @@ -469,27 +448,24 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - // Alice sends HTLCs to Bob that add 20 000 sat to the dust exposure. + // Alice sends HTLCs to Bob that add 22 500 sat to the dust exposure. // She signs them but Bob doesn't answer yet. - addHtlc(4000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(3000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(7000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(6000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) + (1 to 25).foreach { _ => addHtlc(900.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) } alice ! CMD_SIGN(Some(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] alice2bob.expectMsgType[CommitSig] - // Alice sends HTLCs to Bob that add 4 000 sat to the dust exposure. - addHtlc(2500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(1500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) + // Alice sends HTLCs to Bob that add 1 800 sat to the dust exposure. + addHtlc(900.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) + addHtlc(900.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // HTLCs that take Alice's dust exposure above her threshold are rejected. - val add = CMD_ADD_HTLC(sender.ref, 1001.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) + val add = CMD_ADD_HTLC(sender.ref, 701.sat.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add sender.expectMsg(RES_ADD_FAILED(add, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25001.sat.toMilliSatoshi), Some(initialState.channelUpdate))) } - test("recv CMD_ADD_HTLC (over max dust htlc exposure in local commit only with pending local changes)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_ADD_HTLC (over max dust htlc exposure in local commit only with pending local changes)") { f => import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] @@ -513,7 +489,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with sender.expectMsg(RES_ADD_FAILED(add, LocalDustHtlcExposureTooHigh(channelId(alice), 25000.sat, 25200.sat.toMilliSatoshi), Some(initialState.channelUpdate))) } - test("recv CMD_ADD_HTLC (over max dust htlc exposure in remote commit only with pending local changes)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_ADD_HTLC (over max dust htlc exposure in remote commit only with pending local changes)") { f => import f._ val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] @@ -550,34 +526,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // this is over channel-capacity val add2 = CMD_ADD_HTLC(sender.ref, TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) alice ! add2 - val error = InsufficientFunds(channelId(alice), add2.amount, 578133 sat, 20000 sat, 10680 sat) + val error = InsufficientFunds(channelId(alice), add2.amount, 562193 sat, 20000 sat, 4330 sat) sender.expectMsg(RES_ADD_FAILED(add2, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) - } - - test("recv CMD_ADD_HTLC (channel feerate mismatch)") { f => - import f._ - - val sender = TestProbe() - bob.setBitcoinCoreFeerate(FeeratePerKw(20000 sat)) - bob ! CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(20000 sat))) - bob2alice.expectNoMessage(100 millis) // we don't close because the commitment doesn't contain any HTLC - - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val upstream = localOrigin(sender.ref) - val add = CMD_ADD_HTLC(sender.ref, 500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, upstream) - bob ! add - val error = FeerateTooDifferent(channelId(bob), FeeratePerKw(20000 sat), FeeratePerKw(10000 sat)) - sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - bob2alice.expectNoMessage(100 millis) // we don't close the channel, we can simply avoid using it while we disagree on feerate - - // we now agree on feerate so we can send HTLCs - bob.setBitcoinCoreFeerate(FeeratePerKw(11000 sat)) - bob ! CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat))) - bob2alice.expectNoMessage(100 millis) - bob ! add - sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] - bob2alice.expectMsgType[UpdateAddHtlc] + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (after having sent Shutdown)") { f => @@ -594,7 +545,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! add val error = NoMoreHtlcsClosingInProgress(channelId(alice)) sender.expectMsg(RES_ADD_FAILED(add, error, Some(initialState.channelUpdate))) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) } test("recv CMD_ADD_HTLC (after having received Shutdown)") { f => @@ -645,6 +596,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(new String(error.data.toArray) == UnexpectedHtlcId(channelId(bob), expected = 4, actual = 42).getMessage) awaitCond(bob.stateName == CLOSING) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -660,6 +612,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -670,16 +623,17 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) alice2bob.forward(bob, htlc) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = MilliSatoshi(Long.MaxValue), missing = 9223372036083735L sat, reserve = 20000 sat, fees = 8960 sat).getMessage) + assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = MilliSatoshi(Long.MaxValue), missing = 9223372036078675L sat, reserve = 20000 sat, fees = 3900 sat).getMessage) awaitCond(bob.stateName == CLOSING) // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } - test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs)") { f => import f._ val tx = bob.signCommitTx() alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 400000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) @@ -701,11 +655,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 167600000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 3, 10000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 10000000 msat, missing = 11720 sat, reserve = 20000 sat, fees = 14120 sat).getMessage) + assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 10000000 msat, missing = 2790 sat, reserve = 20000 sat, fees = 5190 sat).getMessage) awaitCond(bob.stateName == CLOSING) // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -717,11 +672,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, 300000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, 500000000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 500000000 msat, missing = 332400 sat, reserve = 20000 sat, fees = 12400 sat).getMessage) + assert(new String(error.data.toArray) == InsufficientFunds(channelId(bob), amount = 500000000 msat, missing = 324760 sat, reserve = 20000 sat, fees = 4760 sat).getMessage) awaitCond(bob.stateName == CLOSING) // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -736,6 +692,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -754,6 +711,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -801,7 +759,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val sender = TestProbe() // for the test to be really useful we have constraint on parameters - assert(Alice.nodeParams.channelConf.dustLimit > Bob.nodeParams.channelConf.dustLimit) + assert(Alice.nodeParams.channelConf.dustLimit > Bob.nodeParams.channelConf.dustLimit + 10.sat) // and a low feerate to avoid messing with dust exposure limits val currentFeerate = FeeratePerKw(2500 sat) alice.setBitcoinCoreFeerate(currentFeerate) @@ -809,18 +767,10 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with updateFee(currentFeerate, alice, bob, alice2bob, bob2alice) // we're gonna exchange two htlcs in each direction, the goal is to have bob's commitment have 4 htlcs, and alice's // commitment only have 3. We will then check that alice indeed persisted 4 htlcs, and bob only 3. - val aliceMinReceive = Alice.nodeParams.channelConf.dustLimit + weight2fee(currentFeerate, DefaultCommitmentFormat.htlcSuccessWeight) - val aliceMinOffer = Alice.nodeParams.channelConf.dustLimit + weight2fee(currentFeerate, DefaultCommitmentFormat.htlcTimeoutWeight) - val bobMinReceive = Bob.nodeParams.channelConf.dustLimit + weight2fee(currentFeerate, DefaultCommitmentFormat.htlcSuccessWeight) - val bobMinOffer = Bob.nodeParams.channelConf.dustLimit + weight2fee(currentFeerate, DefaultCommitmentFormat.htlcTimeoutWeight) - val a2b_1 = bobMinReceive + 10.sat // will be in alice and bob tx - val a2b_2 = bobMinReceive + 20.sat // will be in alice and bob tx - val b2a_1 = aliceMinReceive + 10.sat // will be in alice and bob tx - val b2a_2 = bobMinOffer + 10.sat // will be only be in bob tx - assert(a2b_1 > aliceMinOffer && a2b_1 > bobMinReceive) - assert(a2b_2 > aliceMinOffer && a2b_2 > bobMinReceive) - assert(b2a_1 > aliceMinReceive && b2a_1 > bobMinOffer) - assert(b2a_2 < aliceMinReceive && b2a_2 > bobMinOffer) + val a2b_1 = Alice.nodeParams.channelConf.dustLimit + 10.sat // will be in alice and bob tx + val a2b_2 = Alice.nodeParams.channelConf.dustLimit + 20.sat // will be in alice and bob tx + val b2a_1 = Alice.nodeParams.channelConf.dustLimit + 10.sat // will be in alice and bob tx + val b2a_2 = Bob.nodeParams.channelConf.dustLimit + 10.sat // will be only be in bob tx alice ! CMD_ADD_HTLC(sender.ref, a2b_1.toMilliSatoshi, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.Score.max, None, localOrigin(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] alice2bob.expectMsgType[UpdateAddHtlc] @@ -1008,14 +958,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.htlcMaximumMsat == 500_000_000.msat) // Alice sends another large amount and goes below her balance threshold. - val (p5, htlc5) = addHtlc(439_500_000 msat, alice, bob, alice2bob, bob2alice) + val (p5, htlc5) = addHtlc(452_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) fulfillHtlc(htlc5.id, p5, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == 50_000_000.msat) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == 37_500_000.msat) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend > 5_000_000.msat) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend < 10_000_000.msat) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == 950_000_000.msat) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == 962_500_000.msat) bobListener.expectNoMessage(100 millis) assert(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.htlcMaximumMsat == 500_000_000.msat) assert(aliceListener.expectMsgType[LocalChannelUpdate].channelUpdate.htlcMaximumMsat == 5_000_000.msat) @@ -1076,33 +1026,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == initialState.commitments.latest.localCommit.spec.toLocal) } - test("recv CommitSig (multiple htlcs in both directions)") { f => - import f._ - - addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) // a->b (regular) - addHtlc(80000000 msat, alice, bob, alice2bob, bob2alice) // a->b (regular) - addHtlc(1200000 msat, bob, alice, bob2alice, alice2bob) // b->a (trimmed to dust) - addHtlc(10000000 msat, alice, bob, alice2bob, bob2alice) // a->b (regular) - addHtlc(50000000 msat, bob, alice, bob2alice, alice2bob) // b->a (regular) - addHtlc(1200000 msat, alice, bob, alice2bob, bob2alice) // a->b (trimmed to dust) - addHtlc(40000000 msat, bob, alice, bob2alice, alice2bob) // b->a (regular) - - alice ! CMD_SIGN() - val aliceCommitSig = alice2bob.expectMsgType[CommitSig] - assert(aliceCommitSig.htlcSignatures.length == 3) - alice2bob.forward(bob, aliceCommitSig) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - - // actual test begins - val bobCommitSig = bob2alice.expectMsgType[CommitSig] - assert(bobCommitSig.htlcSignatures.length == 5) - bob2alice.forward(alice, bobCommitSig) - - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.index == 1) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 5) - } - def testRecvCommitSigMultipleHtlcs(f: FixtureParam): Unit = { import f._ @@ -1130,11 +1053,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 3) } - test("recv CommitSig (multiple htlcs in both directions) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv CommitSig (multiple htlcs in both directions, anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testRecvCommitSigMultipleHtlcs(f) } - test("recv CommitSig (multiple htlcs in both directions) (phoenix taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f => + test("recv CommitSig (multiple htlcs in both directions, phoenix taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f => testRecvCommitSigMultipleHtlcs(f) } @@ -1165,11 +1088,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.htlcRemoteSigs.size == 5) } - test("recv CommitSig (multiple htlcs in both directions) (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CommitSig (multiple htlcs in both directions, anchor outputs zero fee htlc txs)") { f => testRecvCommitSigMultipleHtlcZeroFees(f) } - test("recv CommitSig (multiple htlcs in both directions) (taproot zero fee htlc txs)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + test("recv CommitSig (multiple htlcs in both directions, taproot zero fee htlc txs)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => testRecvCommitSigMultipleHtlcZeroFees(f) } @@ -1196,12 +1119,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CommitSig (only fee update)") { f => import f._ - alice ! CMD_UPDATE_FEE(TestConstants.feeratePerKw + FeeratePerKw(1000 sat), commit = false) + alice ! CMD_UPDATE_FEE(TestConstants.anchorOutputsFeeratePerKw + FeeratePerKw(1000 sat), commit = false) alice ! CMD_SIGN() // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) val updateFee = alice2bob.expectMsgType[UpdateFee] - assert(updateFee.feeratePerKw == TestConstants.feeratePerKw + FeeratePerKw(1000 sat)) + assert(updateFee.feeratePerKw == TestConstants.anchorOutputsFeeratePerKw + FeeratePerKw(1000 sat)) alice2bob.forward(bob) alice2bob.expectMsgType[CommitSig] alice2bob.forward(bob) @@ -1235,21 +1158,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob.signCommitTx().txOut.count(_.amount == 50000.sat) == 2) } - ignore("recv CommitSig (no changes)") { f => - import f._ - val tx = bob.signCommitTx() - // signature is invalid but it doesn't matter - bob ! CommitSig(ByteVector32.Zeroes, IndividualSignature(ByteVector64.Zeroes), Nil) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray).startsWith("cannot sign when there are no changes")) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectFinalTxPublished(tx.txid) - bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectWatchTxConfirmed(tx.txid) - } - test("recv CommitSig (invalid signature)") { f => import f._ addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) @@ -1261,6 +1169,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(new String(error.data.toArray).startsWith("invalid commitment signature")) awaitCond(bob.stateName == CLOSING) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1320,6 +1229,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) == HtlcSigCountMismatch(channelId(bob), expected = 1, actual = 2).getMessage) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1339,26 +1249,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray).startsWith("invalid htlc signature")) bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(tx.txid) } - test("recv RevokeAndAck (one htlc sent)") { f => - import f._ - addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) - - alice ! CMD_SIGN() - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - - // actual test begins - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.localChanges.acked.size == 1) - } - test("recv RevokeAndAck (one htlc received)") { f => import f._ val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) @@ -1450,6 +1345,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1513,38 +1409,27 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 7730.sat) - assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 8030.sat) + assert(Transactions.offeredHtlcTrimThreshold(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1100.sat) + assert(Transactions.receivedHtlcTrimThreshold(aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.commitmentFormat) == 1000.sat) - // Alice sends HTLCs to Bob that add 10 000 sat to the dust exposure: - addHtlc(500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // dust htlc - addHtlc(1250.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // trimmed htlc - addHtlc(8250.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) // slightly above the trimmed threshold -> included in the dust exposure + // Alice sends HTLCs to Bob that add 24 000 sat to the dust exposure: + (1 to 24).foreach(_ => addHtlc(1000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice)) crossSign(alice, bob, alice2bob, bob2alice) // Bob sends HTLCs to Alice that overflow the dust exposure: - val (_, dust1) = addHtlc(500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // dust htlc - val (_, dust2) = addHtlc(500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // dust htlc - val (_, trimmed1) = addHtlc(4000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // trimmed htlc - val (_, trimmed2) = addHtlc(6400.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // trimmed htlc - val (_, almostTrimmed) = addHtlc(8500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // slightly above the trimmed threshold -> included in the dust exposure - val (_, nonDust) = addHtlc(20000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // way above the trimmed threshold -> not included in the dust exposure + val (_, dust1) = addHtlc(750.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) + val (_, dust2) = addHtlc(500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) + val (_, nonDust) = addHtlc(20_000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) // way above the trimmed threshold -> not included in the dust exposure crossSign(bob, alice, bob2alice, alice2bob) // Alice forwards HTLCs that fit in the dust exposure. alice2relayer.expectMsgAllOf( - RelayForward(nonDust, TestConstants.Bob.nodeParams.nodeId, 6.0 / 30), - RelayForward(almostTrimmed, TestConstants.Bob.nodeParams.nodeId, 6.0 / 30), - RelayForward(trimmed2, TestConstants.Bob.nodeParams.nodeId, 6.0 / 30), + RelayForward(dust1, TestConstants.Bob.nodeParams.nodeId, 3.0 / 30), + RelayForward(nonDust, TestConstants.Bob.nodeParams.nodeId, 3.0 / 30), ) alice2relayer.expectNoMessage(100 millis) // And instantly fails the others. - val failedHtlcs = Seq( - alice2bob.expectMsgType[UpdateFailHtlc], - alice2bob.expectMsgType[UpdateFailHtlc], - alice2bob.expectMsgType[UpdateFailHtlc] - ) - assert(failedHtlcs.map(_.id).toSet == Set(dust1.id, dust2.id, trimmed1.id)) + assert(alice2bob.expectMsgType[UpdateFailHtlc].id == dust2.id) alice2bob.expectMsgType[CommitSig] alice2bob.expectNoMessage(100 millis) } @@ -1555,19 +1440,15 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) // Bob sends HTLCs to Alice that add 10 000 sat to the dust exposure. - addHtlc(4000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) - addHtlc(6000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) + (1 to 10).foreach(_ => addHtlc(1000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob)) crossSign(bob, alice, bob2alice, alice2bob) - alice2relayer.expectMsgType[RelayForward] - alice2relayer.expectMsgType[RelayForward] + (1 to 10).foreach(_ => alice2relayer.expectMsgType[RelayForward]) // Alice sends HTLCs to Bob that add 10 000 sat to the dust exposure but doesn't sign them yet. - addHtlc(6500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(3500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) + (1 to 10).foreach(_ => addHtlc(1000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice)) // Bob sends HTLCs to Alice that add 10 000 sat to the dust exposure. - val (_, rejectedHtlc) = addHtlc(7000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) - val (_, acceptedHtlc) = addHtlc(3000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) + (1 to 10).foreach(_ => addHtlc(1000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob)) bob ! CMD_SIGN(Some(sender.ref)) sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] bob2alice.expectMsgType[CommitSig] @@ -1580,9 +1461,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice) // Alice forwards HTLCs that fit in the dust exposure and instantly fails the others. - alice2relayer.expectMsg(RelayForward(acceptedHtlc, TestConstants.Bob.nodeParams.nodeId, 4.0 / 30)) + (1 to 5).foreach(_ => alice2relayer.expectMsgType[RelayForward]) alice2relayer.expectNoMessage(100 millis) - assert(alice2bob.expectMsgType[UpdateFailHtlc].id == rejectedHtlc.id) + (1 to 5).foreach(_ => alice2bob.expectMsgType[UpdateFailHtlc]) alice2bob.expectMsgType[CommitSig] alice2bob.expectNoMessage(100 millis) } @@ -1621,14 +1502,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectNoMessage(100 millis) } - test("recv RevokeAndAck (over max dust htlc exposure in local commit only with pending local changes)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob)) { f => + test("recv RevokeAndAck (over max dust htlc exposure in local commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob)) { f => import f._ assert(alice.underlyingActor.nodeParams.channelConf.dustLimit == 5000.sat) assert(bob.underlyingActor.nodeParams.channelConf.dustLimit == 1000.sat) testRevokeAndAckDustOverflowSingleCommit(f) } - test("recv RevokeAndAck (over max dust htlc exposure in remote commit only with pending local changes)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice)) { f => + test("recv RevokeAndAck (over max dust htlc exposure in remote commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice)) { f => import f._ assert(alice.underlyingActor.nodeParams.channelConf.dustLimit == 1000.sat) assert(bob.underlyingActor.nodeParams.channelConf.dustLimit == 5000.sat) @@ -1645,6 +1526,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1707,14 +1589,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(forward.htlc == htlc) } - def testRevokeAndAckHtlcStaticRemoteKey(f: FixtureParam): Unit = { + def testRevokeAndAckHtlc(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - assert(alice.commitments.localChannelParams.initFeatures.hasFeature(StaticRemoteKey)) - assert(bob.commitments.localChannelParams.initFeatures.hasFeature(StaticRemoteKey)) + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) def aliceToRemoteScript(): ByteVector = { - val toRemoteAmount = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toRemote + val toRemoteAmount = alice.commitments.latest.localCommit.spec.toRemote val Some(toRemoteOut) = alice.signCommitTx().txOut.find(_.amount == toRemoteAmount.truncateToSatoshi) toRemoteOut.publicKeyScript } @@ -1728,34 +1610,30 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob) bob2alice.expectMsgType[RevokeAndAck] bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + awaitCond(alice.commitments.remoteNextCommitInfo.isRight) bob2alice.expectMsgType[CommitSig] bob2alice.forward(alice) alice2bob.expectMsgType[RevokeAndAck] alice2bob.forward(bob) - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + awaitCond(bob.commitments.remoteNextCommitInfo.isRight) awaitCond(alice.stateName == NORMAL) // using option_static_remotekey alice's view of bob toRemote script stays the same across commitments assert(initialToRemoteScript == aliceToRemoteScript()) } - test("recv RevokeAndAck (one htlc sent, static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { - testRevokeAndAckHtlcStaticRemoteKey _ - } - - test("recv RevokeAndAck (one htlc sent, anchor_outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testRevokeAndAckHtlcStaticRemoteKey _ + test("recv RevokeAndAck (one htlc sent)") { f => + testRevokeAndAckHtlc(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv RevokeAndAck (one htlc sent, anchors_zero_fee_htlc_tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testRevokeAndAckHtlcStaticRemoteKey _ + test("recv RevokeAndAck (one htlc sent, anchor_outputs_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testRevokeAndAckHtlc(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv RevokeAndAck (one htlc sent, option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testRevokeAndAckHtlcStaticRemoteKey _ + test("recv RevokeAndAck (one htlc sent, option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testRevokeAndAckHtlc(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv RevocationTimeout") { f => @@ -1773,9 +1651,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with peer.expectMsg(Peer.Disconnect(alice.commitments.remoteNodeId)) } - private def testReceiveCmdFulfillHtlc(f: FixtureParam): Unit = { + private def testReceiveCmdFulfillHtlc(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) + val (r, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) @@ -1786,24 +1667,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateData == initialState.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fulfill)) } - test("recv CMD_FULFILL_HTLC") { - testReceiveCmdFulfillHtlc _ - } - - test("recv CMD_FULFILL_HTLC (static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { - testReceiveCmdFulfillHtlc _ - } - - test("recv CMD_FULFILL_HTLC (anchor_outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testReceiveCmdFulfillHtlc _ + test("recv CMD_FULFILL_HTLC") { f => + testReceiveCmdFulfillHtlc(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv CMD_FULFILL_HTLC (anchors_zero_fee_htlc_tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testReceiveCmdFulfillHtlc _ + test("recv CMD_FULFILL_HTLC (anchor_outputs_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testReceiveCmdFulfillHtlc(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv CMD_FULFILL_HTLC (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testReceiveCmdFulfillHtlc _ + test("recv CMD_FULFILL_HTLC (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testReceiveCmdFulfillHtlc(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => @@ -1861,8 +1734,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty) } - private def testUpdateFulfillHtlc(f: FixtureParam): Unit = { + private def testUpdateFulfillHtlc(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ + + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) + val (r, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) bob ! CMD_FULFILL_HTLC(htlc.id, r, None) @@ -1878,24 +1755,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(forward.htlc == htlc) } - test("recv UpdateFulfillHtlc") { - testUpdateFulfillHtlc _ + test("recv UpdateFulfillHtlc") { f => + testUpdateFulfillHtlc(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv UpdateFulfillHtlc (static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { - testUpdateFulfillHtlc _ + test("recv UpdateFulfillHtlc (anchor_outputs_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testUpdateFulfillHtlc(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv UpdateFulfillHtlc (anchor_outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testUpdateFulfillHtlc _ - } - - test("recv UpdateFulfillHtlc (anchors_zero_fee_htlc_tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testUpdateFulfillHtlc _ - } - - test("recv UpdateFulfillHtlc (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testUpdateFulfillHtlc _ + test("recv UpdateFulfillHtlc (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testUpdateFulfillHtlc(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv UpdateFulfillHtlc (sender has not signed htlc)") { f => @@ -1912,6 +1781,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1925,6 +1795,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -1943,13 +1814,18 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") - alice2blockchain.expectFinalTxPublished("htlc-timeout") + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] alice2blockchain.expectWatchTxConfirmed(tx.txid) } - private def testCmdFailHtlc(f: FixtureParam): Unit = { + private def testCmdFailHtlc(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ + + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) + val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) @@ -1963,24 +1839,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateData == initialState.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fail)) } - test("recv CMD_FAIL_HTLC") { - testCmdFailHtlc _ + test("recv CMD_FAIL_HTLC") { f => + testCmdFailHtlc(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv CMD_FAIL_HTLC (static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { - testCmdFailHtlc _ + test("recv CMD_FAIL_HTLC (anchor_outputs_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testCmdFailHtlc(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv CMD_FAIL_HTLC (anchor_outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testCmdFailHtlc _ - } - - test("recv CMD_FAIL_HTLC (anchors_zero_fee_htlc_tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testCmdFailHtlc _ - } - - test("recv CMD_FAIL_HTLC (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testCmdFailHtlc _ + test("recv CMD_FAIL_HTLC (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testCmdFailHtlc(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv CMD_FAIL_HTLC (with delay)") { f => @@ -2082,8 +1950,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty) } - private def testUpdateFailHtlc(f: FixtureParam): Unit = { + private def testUpdateFailHtlc(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ + + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) + val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) bob ! CMD_FAIL_HTLC(htlc.id, FailureReason.LocalFailure(PermanentChannelFailure()), None) @@ -2097,24 +1969,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2relayer.expectNoMessage() } - test("recv UpdateFailHtlc") { - testUpdateFailHtlc _ - } - - test("recv UpdateFailHtlc (static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { - testUpdateFailHtlc _ + test("recv UpdateFailHtlc") { f => + testUpdateFailHtlc(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv UpdateFailHtlc (anchor_outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testUpdateFailHtlc _ + test("recv UpdateFailHtlc (anchor_outputs_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testUpdateFailHtlc(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv UpdateFailHtlc (anchors_zero_fee_htlc_tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testUpdateFailHtlc _ - } - - test("recv UpdateFailHtlc (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testUpdateFailHtlc _ + test("recv UpdateFailHtlc (option_simple_taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testUpdateFailHtlc(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv UpdateFailMalformedHtlc") { f => @@ -2156,8 +2020,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(new String(error.data.toArray) == InvalidFailureCode(ByteVector32.Zeroes).getMessage) awaitCond(alice.stateName == CLOSING) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") - alice2blockchain.expectFinalTxPublished("htlc-timeout") + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -2175,6 +2040,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -2188,6 +2054,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } @@ -2204,121 +2071,28 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(fail.reason.length == 561) } - private def testCmdUpdateFee(f: FixtureParam): Unit = { + private def testCmdUpdateFee(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ + + assert(alice.commitments.latest.commitmentFormat == commitmentFormat) + assert(bob.commitments.latest.commitmentFormat == commitmentFormat) + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] alice ! CMD_UPDATE_FEE(FeeratePerKw(20000 sat)) val fee = alice2bob.expectMsgType[UpdateFee] awaitCond(alice.stateData == initialState.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fee)) } - test("recv CMD_UPDATE_FEE") { - testCmdUpdateFee _ + test("recv CMD_UPDATE_FEE") { f => + testCmdUpdateFee(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv CMD_UPDATE_FEE (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testCmdUpdateFee _ + test("recv CMD_UPDATE_FEE (anchor_output_phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testCmdUpdateFee(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv CMD_UPDATE_FEE (simple taproot channel)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { - testCmdUpdateFee _ - } - - test("recv CMD_UPDATE_FEE (over max dust htlc exposure)") { f => - import f._ - - // Alice sends HTLCs to Bob that are not included in the dust exposure at the current feerate: - addHtlc(13000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(14000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments - assert(DustExposure.computeExposure(aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.commitmentFormat) == 0.msat) - assert(DustExposure.computeExposure(aliceCommitments.latest.remoteCommit.spec, aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.commitmentFormat) == 0.msat) - - // A large feerate increase would make these HTLCs overflow alice's dust exposure, so she rejects it: - val sender = TestProbe() - val cmd = CMD_UPDATE_FEE(FeeratePerKw(20000 sat), replyTo_opt = Some(sender.ref)) - alice ! cmd - sender.expectMsg(RES_FAILURE(cmd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 27000000 msat))) - } - - test("recv CMD_UPDATE_FEE (over max dust htlc exposure with pending local changes)") { f => - import f._ - val sender = TestProbe() - assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - - // Alice sends an HTLC to Bob that is not included in the dust exposure at the current feerate. - // She signs them but Bob doesn't answer yet. - addHtlc(13000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - alice ! CMD_SIGN(Some(sender.ref)) - sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] - alice2bob.expectMsgType[CommitSig] - - // Alice sends another HTLC to Bob that is not included in the dust exposure at the current feerate. - addHtlc(14000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments - assert(DustExposure.computeExposure(aliceCommitments.latest.localCommit.spec, aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.commitmentFormat) == 0.msat) - assert(DustExposure.computeExposure(aliceCommitments.latest.remoteCommit.spec, aliceCommitments.latest.remoteCommitParams.dustLimit, aliceCommitments.latest.commitmentFormat) == 0.msat) - - // A large feerate increase would make these HTLCs overflow alice's dust exposure, so she rejects it: - val cmd = CMD_UPDATE_FEE(FeeratePerKw(20000 sat), replyTo_opt = Some(sender.ref)) - alice ! cmd - sender.expectMsg(RES_FAILURE(cmd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 27000000 msat))) - } - - def testCmdUpdateFeeDustOverflowSingleCommit(f: FixtureParam): Unit = { - import f._ - val sender = TestProbe() - // We start with a low feerate. - val initialFeerate = FeeratePerKw(500 sat) - alice.setBitcoinCoreFeerate(initialFeerate) - bob.setBitcoinCoreFeerate(initialFeerate) - updateFee(initialFeerate, alice, bob, alice2bob, bob2alice) - val aliceCommitments = alice.stateData.asInstanceOf[DATA_NORMAL].commitments - assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - val higherDustLimit = Seq(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.remoteCommitParams.dustLimit).max - val lowerDustLimit = Seq(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.remoteCommitParams.dustLimit).min - // We have the following dust thresholds at the current feerate - assert(Transactions.offeredHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 6989.sat) - assert(Transactions.receivedHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 7109.sat) - assert(Transactions.offeredHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 2989.sat) - assert(Transactions.receivedHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 3109.sat) - // And the following thresholds after the feerate update - // NB: we apply the real feerate when sending update_fee, not the one adjusted for dust - val updatedFeerate = FeeratePerKw(4000 sat) - assert(Transactions.offeredHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 7652.sat) - assert(Transactions.receivedHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 7812.sat) - assert(Transactions.offeredHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 3652.sat) - assert(Transactions.receivedHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 3812.sat) - - // Alice send HTLCs to Bob that are not included in the dust exposure at the current feerate. - // She signs them but Bob doesn't answer yet. - (1 to 2).foreach(_ => addHtlc(7400.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice)) - alice ! CMD_SIGN(Some(sender.ref)) - sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] - alice2bob.expectMsgType[CommitSig] - - // Alice sends other HTLCs to Bob that are not included in the dust exposure at the current feerate, without signing them. - (1 to 2).foreach(_ => addHtlc(7400.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice)) - - // A feerate increase makes these HTLCs become dust in one of the commitments but not the other. - val cmd = CMD_UPDATE_FEE(updatedFeerate, replyTo_opt = Some(sender.ref)) - alice.setBitcoinCoreFeerate(updatedFeerate) - bob.setBitcoinCoreFeerate(updatedFeerate) - alice ! cmd - if (higherDustLimit == aliceCommitments.latest.localCommitParams.dustLimit) { - sender.expectMsg(RES_FAILURE(cmd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 29600000 msat))) - } else { - sender.expectMsg(RES_FAILURE(cmd, RemoteDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 29600000 msat))) - } - } - - test("recv CMD_UPDATE_FEE (over max dust htlc exposure in local commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob)) { f => - testCmdUpdateFeeDustOverflowSingleCommit(f) - } - - test("recv CMD_UPDATE_FEE (over max dust htlc exposure in remote commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice)) { f => - testCmdUpdateFeeDustOverflowSingleCommit(f) + test("recv CMD_UPDATE_FEE (simple taproot channel)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => + testCmdUpdateFee(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } test("recv CMD_UPDATE_FEE (two in a row)") { f => @@ -2342,16 +2116,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv UpdateFee") { f => - import f._ - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) - bob ! fee - awaitCond(bob.stateData == initialState - .modify(_.commitments.changes.remoteChanges.proposed).using(_ :+ fee) - .modify(_.commitments.changes.remoteNextHtlcId).setTo(0)) - } - - test("recv UpdateFee (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) @@ -2365,9 +2129,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (two in a row)") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val fee1 = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) + val fee1 = UpdateFee(ByteVector32.Zeroes, TestConstants.anchorOutputsFeeratePerKw * 0.8) bob ! fee1 - val fee2 = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(14000 sat)) + val fee2 = UpdateFee(ByteVector32.Zeroes, TestConstants.anchorOutputsFeeratePerKw * 1.2) bob ! fee2 awaitCond(bob.stateData == initialState .modify(_.commitments.changes.remoteChanges.proposed).using(_ :+ fee2) @@ -2377,36 +2141,18 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv UpdateFee (when sender is not funder)") { f => import f._ val tx = alice.signCommitTx() - alice ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) + alice ! UpdateFee(ByteVector32.Zeroes, TestConstants.anchorOutputsFeeratePerKw * 1.2) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == alice.stateData.asInstanceOf[DATA_CLOSING].channelId) alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(tx.txid) } - test("recv UpdateFee (sender can't afford it)") { f => - import f._ - val tx = bob.signCommitTx() - val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100_000_000 sat)) - // we first update the feerates so that we don't trigger a 'fee too different' error - bob.setBitcoinCoreFeerate(fee.feeratePerKw) - bob ! fee - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == CannotAffordFees(channelId(bob), missing = 71620000L sat, reserve = 20000L sat, fees = 72400000L sat).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectFinalTxPublished(tx.txid) - // even though the feerate is extremely high, we publish our main transaction with a feerate capped by our max-closing-feerate - val mainTx = bob2blockchain.expectFinalTxPublished("local-main-delayed") - assert(Transactions.fee2rate(mainTx.fee, mainTx.tx.weight()) <= bob.nodeParams.onChainFeeConf.maxClosingFeerate * 1.1) - bob2blockchain.expectWatchTxConfirmed(tx.txid) - } - - test("recv UpdateFee (sender can't afford it, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.HighFeerateMismatchTolerance)) { f => + test("recv UpdateFee (sender can't afford it)", Tag(ChannelStateTestsTags.HighFeerateMismatchTolerance)) { f => import f._ val tx = bob.signCommitTx() // This feerate is just above the threshold: (800000 (alice balance) - 20000 (reserve) - 660 (anchors)) / 1124 (commit tx weight) = 693363 @@ -2419,27 +2165,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx } - test("recv UpdateFee (local/remote feerates are too different)") { f => - import f._ - - val commitTx = bob.signCommitTx() - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.feeratePerKw) - alice2bob.send(bob, UpdateFee(ByteVector32.Zeroes, TestConstants.feeratePerKw / 2)) - bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC - - // when we try to add an HTLC, we still disagree on the feerate so we close - alice2bob.send(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 2500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray).contains("local/remote feerates are too different")) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectFinalTxPublished(commitTx.txid) - bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - } - - test("recv UpdateFee (remote feerate is too high, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv UpdateFee (remote feerate is too high)") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] @@ -2451,7 +2177,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == CLOSING) } - test("recv UpdateFee (remote feerate is too small, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv UpdateFee (remote feerate is too small)") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] @@ -2466,129 +2192,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectNoMessage(250 millis) // we don't close because we're using anchor outputs } - test("recv UpdateFee (remote feerate is too small)") { f => - import f._ - val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments - val tx = bob.signCommitTx() - val expectedFeeratePerKw = bob.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.remoteNodeId, bobCommitments.latest.commitmentFormat) - assert(bobCommitments.latest.localCommit.spec.commitTxFeerate == expectedFeeratePerKw) - bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == "remote fee rate is too small: remoteFeeratePerKw=252") - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId == bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectFinalTxPublished(tx.txid) - bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectWatchTxConfirmed(tx.txid) - } - - test("recv UpdateFee (over max dust htlc exposure)") { f => - import f._ - - // Alice sends HTLCs to Bob that are not included in the dust exposure at the current feerate: - addHtlc(13000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(13500.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - addHtlc(14000.sat.toMilliSatoshi, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments - assert(DustExposure.computeExposure(bobCommitments.latest.localCommit.spec, bobCommitments.latest.localCommitParams.dustLimit, bobCommitments.latest.commitmentFormat) == 0.msat) - assert(DustExposure.computeExposure(bobCommitments.latest.remoteCommit.spec, bobCommitments.latest.remoteCommitParams.dustLimit, bobCommitments.latest.commitmentFormat) == 0.msat) - val tx = bob.signCommitTx() - - // A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-closes: - bob.setBitcoinCoreFeerate(FeeratePerKw(20000 sat)) - bob ! UpdateFee(channelId(bob), FeeratePerKw(20000 sat)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == LocalDustHtlcExposureTooHigh(channelId(bob), 30000 sat, 40500000 msat).getMessage) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) - awaitCond(bob.stateName == CLOSING) - } - - test("recv UpdateFee (over max dust htlc exposure with pending local changes)") { f => - import f._ - val sender = TestProbe() - assert(bob.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(alice.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 30_000.sat) - - // Bob sends HTLCs to Alice that are not included in the dust exposure at the current feerate. - // He signs them but Alice doesn't answer yet. - addHtlc(13000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) - addHtlc(13500.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) - bob ! CMD_SIGN(Some(sender.ref)) - sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] - bob2alice.expectMsgType[CommitSig] - - // Bob sends another HTLC to Alice that is not included in the dust exposure at the current feerate. - addHtlc(14000.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob) - val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments - assert(DustExposure.computeExposure(bobCommitments.latest.localCommit.spec, bobCommitments.latest.localCommitParams.dustLimit, bobCommitments.latest.commitmentFormat) == 0.msat) - assert(DustExposure.computeExposure(bobCommitments.latest.remoteCommit.spec, bobCommitments.latest.remoteCommitParams.dustLimit, bobCommitments.latest.commitmentFormat) == 0.msat) - - // A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-close: - val tx = bob.signCommitTx() - bob.setBitcoinCoreFeerate(FeeratePerKw(20000 sat)) - bob ! UpdateFee(channelId(bob), FeeratePerKw(20000 sat)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == LocalDustHtlcExposureTooHigh(channelId(bob), 30000 sat, 40500000 msat).getMessage) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) - awaitCond(bob.stateName == CLOSING) - } - - def testUpdateFeeDustOverflowSingleCommit(f: FixtureParam): Unit = { - import f._ - val sender = TestProbe() - // We start with a low feerate. - val initialFeerate = FeeratePerKw(500 sat) - alice.setBitcoinCoreFeerate(initialFeerate) - bob.setBitcoinCoreFeerate(initialFeerate) - updateFee(initialFeerate, alice, bob, alice2bob, bob2alice) - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val aliceCommitments = initialState.commitments - assert(alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(bob.underlyingActor.nodeParams.nodeId).dustTolerance.maxExposure == 25_000.sat) - val higherDustLimit = Seq(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.remoteCommitParams.dustLimit).max - val lowerDustLimit = Seq(aliceCommitments.latest.localCommitParams.dustLimit, aliceCommitments.latest.remoteCommitParams.dustLimit).min - // We have the following dust thresholds at the current feerate - assert(Transactions.offeredHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 6989.sat) - assert(Transactions.receivedHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 7109.sat) - assert(Transactions.offeredHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 2989.sat) - assert(Transactions.receivedHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = DustExposure.feerateForDustExposure(initialFeerate)), aliceCommitments.latest.commitmentFormat) == 3109.sat) - // And the following thresholds after the feerate update - // NB: we apply the real feerate when sending update_fee, not the one adjusted for dust - val updatedFeerate = FeeratePerKw(4000 sat) - assert(Transactions.offeredHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 7652.sat) - assert(Transactions.receivedHtlcTrimThreshold(higherDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 7812.sat) - assert(Transactions.offeredHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 3652.sat) - assert(Transactions.receivedHtlcTrimThreshold(lowerDustLimit, aliceCommitments.latest.localCommit.spec.copy(commitTxFeerate = updatedFeerate), aliceCommitments.latest.commitmentFormat) == 3812.sat) - - // Bob send HTLCs to Alice that are not included in the dust exposure at the current feerate. - // He signs them but Alice doesn't answer yet. - (1 to 3).foreach(_ => addHtlc(7400.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob)) - bob ! CMD_SIGN(Some(sender.ref)) - sender.expectMsgType[RES_SUCCESS[CMD_SIGN]] - bob2alice.expectMsgType[CommitSig] - - // Bob sends other HTLCs to Alice that are not included in the dust exposure at the current feerate, without signing them. - (1 to 2).foreach(_ => addHtlc(7400.sat.toMilliSatoshi, bob, alice, bob2alice, alice2bob)) - - // A feerate increase makes these HTLCs become dust in one of the commitments but not the other. - val tx = bob.signCommitTx() - bob.setBitcoinCoreFeerate(updatedFeerate) - bob ! UpdateFee(channelId(bob), updatedFeerate) - val error = bob2alice.expectMsgType[Error] - // NB: we don't need to distinguish local and remote, the error message is exactly the same. - assert(new String(error.data.toArray) == LocalDustHtlcExposureTooHigh(channelId(bob), 30000 sat, 37000000 msat).getMessage) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) - awaitCond(bob.stateName == CLOSING) - } - - test("recv UpdateFee (over max dust htlc exposure in local commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceBobAlice)) { f => - testUpdateFeeDustOverflowSingleCommit(f) - } - - test("recv UpdateFee (over max dust htlc exposure in remote commit only with pending local changes)", Tag(ChannelStateTestsTags.HighDustLimitDifferenceAliceBob)) { f => - testUpdateFeeDustOverflowSingleCommit(f) - } - test("recv CMD_UPDATE_RELAY_FEE ") { f => import f._ val sender = TestProbe() @@ -2619,11 +2222,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with testCmdClose(f, None) } - test("recv CMD_CLOSE (no pending htlcs) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testCmdClose(f, None) - } - - test("recv CMD_CLOSE (no pending htlcs) (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_CLOSE (no pending htlcs) (anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testCmdClose(f, None) } @@ -2669,14 +2268,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with sender.expectMsgType[RES_FAILURE[CMD_CLOSE, InvalidFinalScript]] } - test("recv CMD_CLOSE (with unsupported native segwit script)") { f => - import f._ - val sender = TestProbe() - alice ! CMD_CLOSE(sender.ref, Some(hex"51050102030405"), None) - sender.expectMsgType[RES_FAILURE[CMD_CLOSE, InvalidFinalScript]] - } - - test("recv CMD_CLOSE (with native segwit script)", Tag(ChannelStateTestsTags.ShutdownAnySegwit)) { f => + test("recv CMD_CLOSE (with native segwit script)") { f => testCmdClose(f, Some(hex"51050102030405")) } @@ -2796,11 +2388,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with testShutdown(f, None) } - test("recv Shutdown (no pending htlcs) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testShutdown(f, None) - } - - test("recv Shutdown (no pending htlcs) (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Shutdown (no pending htlcs) (anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testShutdown(f, None) } @@ -2833,6 +2421,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! Shutdown(ByteVector32.Zeroes, alice.underlyingActor.getOrGenerateFinalScriptPubKey(aliceData)) bob2alice.expectMsgType[Error] val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx") + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(commitTx.tx.txid) awaitCond(bob.stateName == CLOSING) @@ -2841,7 +2430,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv Shutdown (with unsigned fee update)") { f => import f._ val sender = TestProbe() - alice ! CMD_UPDATE_FEE(FeeratePerKw(10_000 sat), commit = true) + alice ! CMD_UPDATE_FEE(FeeratePerKw(3_000 sat), commit = true) alice2bob.expectMsgType[UpdateFee] alice2bob.forward(bob) val sig = alice2bob.expectMsgType[CommitSig] @@ -2877,18 +2466,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - test("recv Shutdown (with unsupported native segwit script)") { f => - import f._ - bob ! Shutdown(ByteVector32.Zeroes, hex"51050102030405") - bob2alice.expectMsgType[Warning] - // we should fail the connection as per the BOLTs - bobPeer.fishForMessage(3 seconds) { - case Peer.Disconnect(nodeId, _) if nodeId == bob.commitments.remoteNodeId => true - case _ => false - } - } - - test("recv Shutdown (with native segwit script)", Tag(ChannelStateTestsTags.ShutdownAnySegwit)) { f => + test("recv Shutdown (with native segwit script)") { f => testShutdown(f, Some(hex"51050102030405")) } @@ -2931,16 +2509,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == SHUTDOWN) } - test("recv Shutdown (with signed htlcs)") { - testShutdownWithHtlcs _ - } - - test("recv Shutdown (with signed htlcs) (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { - testShutdownWithHtlcs _ + test("recv Shutdown (with signed htlcs)") { f => + testShutdownWithHtlcs(f) } - test("recv Shutdown (with signed htlcs) (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { - testShutdownWithHtlcs _ + test("recv Shutdown (with signed htlcs) (anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testShutdownWithHtlcs(f) } test("recv Shutdown (while waiting for a RevokeAndAck)") { f => @@ -3018,8 +2592,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val aliceCommitTx = alice.signCommitTx() alice ! CurrentBlockHeight(BlockHeight(400145)) alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] alice2blockchain.expectFinalTxPublished("local-main-delayed") - alice2blockchain.expectFinalTxPublished("htlc-timeout") + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] } @@ -3051,8 +2626,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) bob2blockchain.expectFinalTxPublished(commitTx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectFinalTxPublished(htlcSuccessTx.tx.txid) + assert(bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input.outPoint == htlcSuccessTx.input.outPoint) bob2blockchain.expectWatchTxConfirmed(commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(100 millis) @@ -3085,8 +2661,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) bob2blockchain.expectFinalTxPublished(commitTx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectFinalTxPublished(htlcSuccessTx.tx.txid) + assert(bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input.outPoint == htlcSuccessTx.input.outPoint) bob2blockchain.expectWatchTxConfirmed(commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(100 millis) @@ -3123,23 +2700,15 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) bob2blockchain.expectFinalTxPublished(commitTx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectFinalTxPublished(htlcSuccessTx.tx.txid) + assert(bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input.outPoint == htlcSuccessTx.input.outPoint) bob2blockchain.expectWatchTxConfirmed(commitTx.txid) channelUpdateListener.expectMsgType[LocalChannelDown] alice2blockchain.expectNoMessage(100 millis) } test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => - import f._ - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat))) - alice.setBitcoinCoreFeerates(event.feeratesPerKw) - alice ! event - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.latest.commitmentFormat))) - } - - test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) @@ -3156,14 +2725,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => - import f._ - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat))) - alice.setBitcoinCoreFeerates(event.feeratesPerKw) - alice ! event - alice2bob.expectNoMessage(100 millis) - } - - test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) @@ -3184,22 +2745,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, with HTLCs)") { f => import f._ - addHtlc(10000000 msat, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(14000 sat))) - bob.setBitcoinCoreFeerates(event.feeratesPerKw) - bob ! event - bob2alice.expectMsgType[Error] - val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx").tx - bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - awaitCond(bob.stateName == CLOSING) - } - - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, with HTLCs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - import f._ - // We start with a feerate lower than the 10 sat/byte threshold. alice.setBitcoinCoreFeerate(TestConstants.anchorOutputsFeeratePerKw / 2) bob.setBitcoinCoreFeerate(TestConstants.anchorOutputsFeeratePerKw / 2) @@ -3218,24 +2763,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob.stateName == NORMAL) } - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, without HTLCs)") { f => - import f._ - - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(15_000 sat))) - bob.setBitcoinCoreFeerates(event.feeratesPerKw) - bob ! event - bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC - - // when we try to add an HTLC, we still disagree on the feerate so we close - alice2bob.send(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, 2500000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) - bob2alice.expectMsgType[Error] - val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx").tx - bob2blockchain.expectFinalTxPublished("local-main-delayed") - bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - awaitCond(bob.stateName == CLOSING) - } - - test("recv WatchFundingSpentTriggered (their commit w/ htlc)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (their commit w/ htlc)") { f => import f._ val (_, htlca1) = addHtlc(250_000_000 msat, CltvExpiryDelta(50), alice, bob, alice2bob, bob2alice) @@ -3316,7 +2844,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(addSettled.htlc == htlc1) } - test("recv WatchFundingSpentTriggered (their *next* commit w/ htlc)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (their *next* commit w/ htlc)") { f => import f._ val (_, htlca1) = addHtlc(250_000_000 msat, CltvExpiryDelta(24), alice, bob, alice2bob, bob2alice) @@ -3402,7 +2930,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(addSettled.htlc == htlc2) } - test("recv WatchFundingSpentTriggered (revoked commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (revoked commit)") { f => import f._ // initially we have : // alice = 800 000 @@ -3451,7 +2979,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with htlcPenaltyTxs.foreach(tx => assert(tx.txOut.head.amount == 4_200.sat)) } - test("recv WatchFundingSpentTriggered (revoked commit with identical htlcs)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchFundingSpentTriggered (revoked commit with identical htlcs)") { f => import f._ val sender = TestProbe() @@ -3558,7 +3086,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) - assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs + assert(aliceCommitTx.txOut.size == 8) // two main outputs, two anchor outputs and 4 pending htlcs awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get @@ -3571,23 +3099,24 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // - 1 tx to claim the main delayed output // - 3 txs for each htlc // NB: 3rd-stage txs will only be published once the htlc txs confirm - val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") - val htlcTx1 = alice2blockchain.expectFinalTxPublished("htlc-success") - val htlcTx2 = alice2blockchain.expectFinalTxPublished("htlc-timeout") - val htlcTx3 = alice2blockchain.expectFinalTxPublished("htlc-timeout") + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed").tx + val htlcTx1 = alice2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].sign() + val htlcTx2 = alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].sign() + val htlcTx3 = alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].sign() // the main delayed output and htlc txs spend the commitment transaction - Seq(claimMain, htlcTx1, htlcTx2, htlcTx3).foreach(tx => Transaction.correctlySpends(tx.tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + Seq(claimMain, htlcTx1, htlcTx2, htlcTx3).foreach(tx => Transaction.correctlySpends(tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(claimMain.input +: localCommitPublished.htlcOutputs.toSeq) + alice2blockchain.expectWatchOutputsSpent(Seq(localCommitPublished.localOutput_opt, localCommitPublished.anchorOutput_opt).flatten ++ localCommitPublished.htlcOutputs.toSeq) alice2blockchain.expectNoMessage(100 millis) // 3rd-stage txs are published when htlc txs confirm Seq(htlcTx1, htlcTx2, htlcTx3).foreach { htlcTx => - alice ! WatchOutputSpentTriggered(0 sat, htlcTx.tx) - alice2blockchain.expectWatchTxConfirmed(htlcTx.tx.txid) - alice ! WatchTxConfirmedTriggered(BlockHeight(2701), 3, htlcTx.tx) + alice ! WatchOutputSpentTriggered(0 sat, htlcTx) + alice2blockchain.expectWatchTxConfirmed(htlcTx.txid) + alice ! WatchTxConfirmedTriggered(BlockHeight(2701), 3, htlcTx) val htlcDelayedTx = alice2blockchain.expectFinalTxPublished("htlc-delayed") - Transaction.correctlySpends(htlcDelayedTx.tx, htlcTx.tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(htlcDelayedTx.tx, htlcTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) } awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 3) @@ -3642,7 +3171,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with )) } - test("recv Error (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error (anchor outputs zero fee htlc txs)") { f => testErrorAnchorOutputsWithHtlcs(f) } @@ -3650,7 +3179,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with testErrorAnchorOutputsWithHtlcs(f) } - test("recv Error (anchor outputs zero fee htlc txs, fee-bumping for commit txs without htlcs disabled)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.DontSpendAnchorWithoutHtlcs)) { f => + test("recv Error (anchor outputs zero fee htlc txs, fee-bumping for commit txs without htlcs disabled)", Tag(ChannelStateTestsTags.DontSpendAnchorWithoutHtlcs)) { f => // We should ignore the disable flag since there are htlcs in the commitment (funds at risk). testErrorAnchorOutputsWithHtlcs(f) } @@ -3677,7 +3206,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectNoMessage(100 millis) } - test("recv Error (anchor outputs zero fee htlc txs without htlcs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv Error (anchor outputs zero fee htlc txs without htlcs)") { f => testErrorAnchorOutputsWithoutHtlcs(f, commitFeeBumpDisabled = false) } @@ -3685,7 +3214,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with testErrorAnchorOutputsWithoutHtlcs(f, commitFeeBumpDisabled = false) } - test("recv Error (anchor outputs zero fee htlc txs without htlcs, fee-bumping for commit txs without htlcs disabled)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.DontSpendAnchorWithoutHtlcs)) { f => + test("recv Error (anchor outputs zero fee htlc txs without htlcs, fee-bumping for commit txs without htlcs disabled)", Tag(ChannelStateTestsTags.DontSpendAnchorWithoutHtlcs)) { f => testErrorAnchorOutputsWithoutHtlcs(f, commitFeeBumpDisabled = true) } @@ -3699,7 +3228,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val bobCommitTx = bob.signCommitTx() bob ! Error(ByteVector32.Zeroes, "oops") bob2blockchain.expectFinalTxPublished(bobCommitTx.txid) - assert(bobCommitTx.txOut.size == 1) // only one main output + assert(bobCommitTx.txOut.size == 2) // alice's main output and anchor output alice2blockchain.expectNoMessage(100 millis) awaitCond(bob.stateName == CLOSING) @@ -3722,7 +3251,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(addSettled.htlc == htlc1) } - test("recv WatchFundingConfirmedTriggered (public channel, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv WatchFundingConfirmedTriggered (public channel, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // For zero-conf channels we don't have a real short_channel_id when going to the NORMAL state. val aliceState = alice.stateData.asInstanceOf[DATA_NORMAL] @@ -3753,7 +3282,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == realShortChannelId) } - test("recv WatchFundingConfirmedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv WatchFundingConfirmedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // we create a new listener that registers after alice has published the funding tx val listener = TestProbe() @@ -3820,7 +3349,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice, annSigsB_invalid) alice2bob.expectMsg(Error(channelId, InvalidAnnouncementSignatures(channelId, annSigsB_invalid).getMessage)) alice2bob.forward(bob) - alice2bob.expectNoMessage(200 millis) + alice2bob.expectNoMessage(100 millis) awaitCond(alice.stateName == CLOSING) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 7f5dc708bd..08f7d96e18 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -28,12 +28,12 @@ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel -import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx} +import fr.acinq.eclair.channel.publish.TxPublisher.PublishFinalTx import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcTimeoutTx, ClaimRemoteAnchorTx, UnsignedHtlcSuccessTx} +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, TestUtils, randomBytes32} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -317,7 +317,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex == 4) } - test("reconnect with an outdated commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("reconnect with an outdated commitment", Tag(IgnoreChannelUpdates)) { f => import f._ val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice) @@ -367,7 +367,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("reconnect with an outdated commitment (but counterparty can't tell)", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("reconnect with an outdated commitment (but counterparty can't tell)", Tag(IgnoreChannelUpdates)) { f => import f._ // we start by storing the current state @@ -421,7 +421,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("counterparty lies about having a more recent commitment and publishes current commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("counterparty lies about having a more recent commitment and publishes current commitment", Tag(IgnoreChannelUpdates)) { f => import f._ // the current state contains a pending htlc @@ -455,7 +455,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(claimHtlc.sign(), bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("counterparty lies about having a more recent commitment and publishes revoked commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("counterparty lies about having a more recent commitment and publishes revoked commitment", Tag(IgnoreChannelUpdates)) { f => import f._ // we sign a new commitment to make sure the first one is revoked @@ -630,12 +630,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(err.isInstanceOf[HtlcsWillTimeoutUpstream]) bob2blockchain.expectFinalTxPublished(initialCommitTx.txid) + val anchorTx = bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val mainDelayedTx = bob2blockchain.expectFinalTxPublished("local-main-delayed") bob2blockchain.expectWatchTxConfirmed(initialCommitTx.txid) - bob2blockchain.expectWatchOutputSpent(mainDelayedTx.input) - bob2blockchain.expectWatchOutputSpent(htlcSuccessTx.input.outPoint) - val publishHtlcTx = bob2blockchain.expectFinalTxPublished("htlc-success") - assert(publishHtlcTx.input == htlcSuccessTx.input.outPoint) + bob2blockchain.expectWatchOutputsSpent(Seq(mainDelayedTx.input, anchorTx.input.outPoint, htlcSuccessTx.input.outPoint)) + assert(bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx].input == htlcSuccessTx.input) bob2blockchain.expectNoMessage(100 millis) } @@ -654,21 +653,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectNoMessage(100 millis) } - test("handle feerate changes while offline (funder scenario)") { f => - import f._ - - // we only close channels on feerate mismatch if there are HTLCs at risk in the commitment - addHtlc(125000000 msat, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - testHandleFeerateFunder(f, shouldClose = true) - } - test("handle feerate changes while offline without HTLCs (funder scenario)") { f => - testHandleFeerateFunder(f, shouldClose = false) + testHandleFeerateFunder(f) } - def testHandleFeerateFunder(f: FixtureParam, shouldClose: Boolean): Unit = { + def testHandleFeerateFunder(f: FixtureParam): Unit = { import f._ // we simulate a disconnection @@ -684,11 +673,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice is funder alice.setBitcoinCoreFeerates(networkFeerates) alice ! CurrentFeerates.BitcoinCore(networkFeerates) - if (shouldClose) { - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) - } else { - alice2blockchain.expectNoMessage(100 millis) - } + alice2blockchain.expectNoMessage(100 millis) } test("handle feerate changes while offline (don't close on mismatch)", Tag(DisableOfflineMismatch)) { f => @@ -715,90 +700,6 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectNoMessage(100 millis) } - def testUpdateFeeOnReconnect(f: FixtureParam, shouldUpdateFee: Boolean): Unit = { - import f._ - - // we simulate a disconnection - disconnect(alice, bob) - - val localFeeratePerKw = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate - val networkFeeratePerKw = localFeeratePerKw * 2 - val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw) - - // Alice ignores feerate changes while offline - alice.setBitcoinCoreFeerates(networkFeerates) - alice ! CurrentFeerates.BitcoinCore(networkFeerates) - alice2blockchain.expectNoMessage(100 millis) - alice2bob.expectNoMessage(100 millis) - - // then we reconnect them; Alice should send the feerate changes to Bob - reconnect(alice, bob, alice2bob, bob2alice) - - // peers exchange channel_reestablish messages - alice2bob.expectMsgType[ChannelReestablish] - bob2alice.expectMsgType[ChannelReestablish] - bob2alice.forward(alice) - - if (shouldUpdateFee) { - alice2bob.expectMsg(UpdateFee(channelId(alice), networkFeeratePerKw)) - } else { - alice2bob.expectMsgType[Shutdown] - alice2bob.expectNoMessage(100 millis) - } - } - - test("handle feerate changes while offline (update at reconnection)", Tag(IgnoreChannelUpdates)) { f => - testUpdateFeeOnReconnect(f, shouldUpdateFee = true) - } - - test("handle feerate changes while offline (shutdown sent, don't update at reconnection)", Tag(IgnoreChannelUpdates)) { f => - import f._ - - // alice initiates a shutdown - val sender = TestProbe() - alice ! CMD_CLOSE(sender.ref, None, None) - alice2bob.expectMsgType[Shutdown] - - testUpdateFeeOnReconnect(f, shouldUpdateFee = false) - } - - test("handle feerate changes while offline (fundee scenario)") { f => - import f._ - - // we only close channels on feerate mismatch if there are HTLCs at risk in the commitment - addHtlc(125000000 msat, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - testHandleFeerateFundee(f, shouldClose = true) - } - - test("handle feerate changes while offline without HTLCs (fundee scenario)") { f => - testHandleFeerateFundee(f, shouldClose = false) - } - - def testHandleFeerateFundee(f: FixtureParam, shouldClose: Boolean): Unit = { - import f._ - - // we simulate a disconnection - disconnect(alice, bob) - - val bobCommitTx = bob.signCommitTx() - val currentFeerate = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate - // we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1 - // to ensure the network's feerate is 10% above our threshold). - val networkFeerate = currentFeerate * (1.1 / bob.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Alice.nodeParams.nodeId).ratioLow) - val networkFeerates = FeeratesPerKw.single(networkFeerate) - - // bob is fundee - bob.setBitcoinCoreFeerates(networkFeerates) - bob ! CurrentFeerates.BitcoinCore(networkFeerates) - if (shouldClose) { - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == bobCommitTx.txid) - } else { - bob2blockchain.expectNoMessage(100 millis) - } - } - test("re-send announcement_signatures on reconnection", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.DoNotInterceptGossip)) { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index ad70162e3d..6628813716 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw} import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates} import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature import fr.acinq.eclair.channel._ -import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishTx, SetChannelId} +import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, SetChannelId} import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment._ @@ -34,7 +34,6 @@ import fr.acinq.eclair.payment.relay.Relayer._ import fr.acinq.eclair.payment.send.SpontaneousRecipient import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.testutils.PimpTestProbe.convert -import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelReestablish, ChannelUpdate, ClosingComplete, ClosingSig, ClosingSigned, CommitSig, Error, FailureMessageCodecs, FailureReason, Init, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey} @@ -231,11 +230,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice ! fulfill alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 - alice2blockchain.expectMsgType[WatchTxConfirmed] + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv UpdateFulfillHtlc (invalid preimage)") { f => @@ -244,11 +239,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice ! UpdateFulfillHtlc(ByteVector32.Zeroes, 42, ByteVector32.Zeroes) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 - alice2blockchain.expectMsgType[WatchTxConfirmed] + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv CMD_FAIL_HTLC") { f => @@ -335,10 +326,11 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice ! UpdateFailHtlc(ByteVector32.Zeroes, 42, ByteVector.fill(152)(0)) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 + alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + alice2blockchain.expectFinalTxPublished("local-main-delayed") + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] alice2blockchain.expectMsgType[WatchTxConfirmed] } @@ -358,11 +350,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit val error = alice2bob.expectMsgType[Error] assert(new String(error.data.toArray) == InvalidFailureCode(ByteVector32.Zeroes).getMessage) awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 - alice2blockchain.expectMsgType[WatchTxConfirmed] + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv CMD_SIGN") { f => @@ -434,27 +422,13 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice2bob.expectMsgType[RevokeAndAck] } - test("recv CommitSig (no changes)") { f => - import f._ - val tx = bob.signCommitTx() - // signature is invalid but it doesn't matter - bob ! CommitSig(ByteVector32.Zeroes, IndividualSignature(ByteVector64.Zeroes), Nil) - bob2alice.expectMsgType[Error] - awaitCond(bob.stateName == CLOSING) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[WatchTxConfirmed] - } - test("recv CommitSig (invalid signature)") { f => import f._ val tx = bob.signCommitTx() bob ! CommitSig(ByteVector32.Zeroes, IndividualSignature(ByteVector64.Zeroes), Nil) bob2alice.expectMsgType[Error] awaitCond(bob.stateName == CLOSING) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[WatchTxConfirmed] + bob2blockchain.expectFinalTxPublished(tx.txid) } test("recv RevokeAndAck (with remaining htlcs on both sides)") { f => @@ -521,10 +495,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit bob ! RevokeAndAck(ByteVector32.Zeroes, PrivateKey(randomBytes32()), PrivateKey(randomBytes32()).publicKey) bob2alice.expectMsgType[Error] awaitCond(bob.stateName == CLOSING) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[PublishTx] // htlc success - bob2blockchain.expectMsgType[WatchTxConfirmed] + bob2blockchain.expectFinalTxPublished(tx.txid) } test("recv RevokeAndAck (unexpectedly)") { f => @@ -534,11 +505,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice ! RevokeAndAck(ByteVector32.Zeroes, PrivateKey(randomBytes32()), PrivateKey(randomBytes32()).publicKey) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 - alice2blockchain.expectMsgType[WatchTxConfirmed] + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv RevokeAndAck (forward UpdateFailHtlc)") { f => @@ -611,7 +578,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee") { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) + val fee = UpdateFee(ByteVector32.Zeroes, TestConstants.anchorOutputsFeeratePerKw * 1.2) bob ! fee awaitCond(bob.stateData == initialData .modify(_.commitments.changes.remoteChanges.proposed).using(_ :+ fee) @@ -621,31 +588,10 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv UpdateFee (when sender is not funder)") { f => import f._ val tx = alice.signCommitTx() - alice ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(12000 sat)) + alice ! UpdateFee(ByteVector32.Zeroes, TestConstants.anchorOutputsFeeratePerKw * 1.2) alice2bob.expectMsgType[Error] awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 - alice2blockchain.expectMsgType[WatchTxConfirmed] - } - - test("recv UpdateFee (sender can't afford it)") { f => - import f._ - val tx = bob.signCommitTx() - val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100_000_000 sat)) - // we first update the feerates so that we don't trigger a 'fee too different' error - bob.setBitcoinCoreFeerate(fee.feeratePerKw) - bob ! fee - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == CannotAffordFees(channelId(bob), missing = 72120000L sat, reserve = 20000L sat, fees = 72400000L sat).getMessage) - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectFinalTxPublished(tx.txid) - // even though the feerate is extremely high, we publish our main transaction with a feerate capped by our max-closing-feerate - val mainTx = bob2blockchain.expectFinalTxPublished("local-main-delayed") - assert(Transactions.fee2rate(mainTx.fee, mainTx.tx.weight()) <= bob.nodeParams.onChainFeeConf.maxClosingFeerate * 1.1) - bob2blockchain.expectWatchTxConfirmed(tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) } test("recv UpdateFee (local/remote feerates are too different)") { f => @@ -653,11 +599,9 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit val tx = bob.signCommitTx() bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(65000 sat)) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) == "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=10000") + assert(new String(error.data.toArray) == "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=2500") awaitCond(bob.stateName == CLOSING) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[WatchTxConfirmed] + bob2blockchain.expectFinalTxPublished(tx.txid) } test("recv UpdateFee (remote feerate is too small)") { f => @@ -667,9 +611,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) == "remote fee rate is too small: remoteFeeratePerKw=252") awaitCond(bob.stateName == CLOSING) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[WatchTxConfirmed] + bob2blockchain.expectFinalTxPublished(tx.txid) } test("recv CMD_UPDATE_RELAY_FEE ") { f => @@ -693,23 +635,15 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val aliceCommitTx = alice.signCommitTx() alice ! CurrentBlockHeight(BlockHeight(400145)) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) // commit tx - alice2blockchain.expectMsgType[PublishTx] // main delayed - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishTx] // htlc timeout 2 + alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + alice2blockchain.expectFinalTxPublished("local-main-delayed") + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] + alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx] assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx.txid) } test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => - import f._ - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat))) - alice.setBitcoinCoreFeerates(event.feeratesPerKw) - alice ! event - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.latest.commitmentFormat))) - } - - test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) @@ -720,14 +654,6 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => - import f._ - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat))) - alice.setBitcoinCoreFeerates(event.feeratesPerKw) - alice ! event - alice2bob.expectNoMessage(500 millis) - } - - test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) @@ -745,19 +671,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit bob2alice.expectNoMessage(500 millis) } - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f => - import f._ - val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(25000 sat))) - bob.setBitcoinCoreFeerates(event.feeratesPerKw) - bob ! event - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishTx] // commit tx - bob2blockchain.expectMsgType[PublishTx] // main delayed - bob2blockchain.expectMsgType[WatchTxConfirmed] - awaitCond(bob.stateName == CLOSING) - } - - test("recv WatchFundingSpentTriggered (their commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => + test("recv WatchFundingSpentTriggered (their commit)") { f => import f._ // bob publishes his current commit tx, which contains two pending htlcs alice->bob val bobCommitTx = bob.signCommitTx() @@ -788,7 +702,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice2blockchain.expectNoMessage(100 millis) } - test("recv WatchFundingSpentTriggered (their next commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => + test("recv WatchFundingSpentTriggered (their next commit)") { f => import f._ // bob fulfills the first htlc fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob) @@ -834,7 +748,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice2blockchain.expectNoMessage(100 millis) } - test("recv WatchFundingSpentTriggered (revoked tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => + test("recv WatchFundingSpentTriggered (revoked tx)") { f => import f._ val revokedTx = bob.signCommitTx() // two main outputs + 2 htlc @@ -868,7 +782,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit alice2blockchain.expectNoMessage(100 millis) } - test("recv WatchFundingSpentTriggered (revoked tx with updated commitment)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => + test("recv WatchFundingSpentTriggered (revoked tx with updated commitment)") { f => import f._ val initialCommitTx = bob.signCommitTx() assert(initialCommitTx.txOut.size == 6) // two main outputs + 2 htlc @@ -951,7 +865,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val aliceCommitTx = alice.signCommitTx() - assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs + assert(aliceCommitTx.txOut.size == 6) // two main outputs, two anchor outputs and two htlcs val sender = TestProbe() alice ! CMD_FORCECLOSE(sender.ref) @@ -962,21 +876,22 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit val lcp = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get assert(lcp.htlcOutputs.size == 2) - val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") - val htlc1 = alice2blockchain.expectFinalTxPublished("htlc-timeout") - val htlc2 = alice2blockchain.expectFinalTxPublished("htlc-timeout") - Seq(claimMain, htlc1, htlc2).foreach(tx => Transaction.correctlySpends(tx.tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx].tx + val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed").tx + val htlc1 = alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].sign() + val htlc2 = alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].sign() + Seq(claimMain, htlc1, htlc2).foreach(tx => Transaction.correctlySpends(tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(Seq(claimMain, htlc1, htlc2).map(_.input)) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMain, anchorTx, htlc1, htlc2).map(_.txIn.head.outPoint)) alice2blockchain.expectNoMessage(100 millis) // 3rd-stage txs are published when htlc txs confirm Seq(htlc1, htlc2).foreach(htlcTimeoutTx => { - alice ! WatchOutputSpentTriggered(0 sat, htlcTimeoutTx.tx) - alice2blockchain.expectWatchTxConfirmed(htlcTimeoutTx.tx.txid) - alice ! WatchTxConfirmedTriggered(BlockHeight(2701), 3, htlcTimeoutTx.tx) + alice ! WatchOutputSpentTriggered(0 sat, htlcTimeoutTx) + alice2blockchain.expectWatchTxConfirmed(htlcTimeoutTx.txid) + alice ! WatchTxConfirmedTriggered(BlockHeight(2701), 3, htlcTimeoutTx) val htlcDelayedTx = alice2blockchain.expectFinalTxPublished("htlc-delayed") - Transaction.correctlySpends(htlcDelayedTx.tx, htlcTimeoutTx.tx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(htlcDelayedTx.tx, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) alice2blockchain.expectWatchOutputSpent(htlcDelayedTx.input) }) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.htlcDelayedOutputs.size == 2) @@ -1049,7 +964,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == closingTx.tx.txid) } - test("recv INPUT_RESTORED", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_RESTORED", Tag(ChannelStateTestsTags.SimpleClose)) { f => testInputRestored(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1062,7 +977,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit val aliceCommitTx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectFinalTxPublished(aliceCommitTx.txid) - assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs + assert(aliceCommitTx.txOut.size == 6) // two main outputs, two anchor outputs and two htlcs awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) @@ -1070,11 +985,14 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit // - 1 tx to claim the main delayed output // - 2 txs for each htlc // NB: 3rd-stage txs will only be published once the htlc txs confirm - val claimTxs = (0 until 3).map(_ => alice2blockchain.expectMsgType[PublishFinalTx].tx) + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed") + val htlcTxs = (0 until 2).map(_ => alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx]) // the main delayed output and htlc txs spend the commitment transaction - claimTxs.foreach(tx => Transaction.correctlySpends(tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + Transaction.correctlySpends(claimMain.tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + htlcTxs.foreach(tx => Transaction.correctlySpends(tx.sign(), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) alice2blockchain.expectWatchTxConfirmed(aliceCommitTx.txid) - alice2blockchain.expectWatchOutputsSpent(claimTxs.flatMap(_.txIn.map(_.outPoint))) + alice2blockchain.expectWatchOutputsSpent((Seq(anchorTx.tx, claimMain.tx) ++ htlcTxs.map(_.tx)).flatMap(_.txIn.map(_.outPoint))) alice2blockchain.expectNoMessage(100 millis) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index adc947c2f2..cda42664a1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -161,7 +161,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike // alice is funder so she initiates the negotiation val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned] - assert(aliceCloseSig1.feeSatoshis == 3370.sat) // matches a feerate of 5000 sat/kw + assert(aliceCloseSig1.feeSatoshis == 3850.sat) // matches a feerate of 5000 sat/kw assert(aliceCloseSig1.feeRange_opt.nonEmpty) assert(aliceCloseSig1.feeRange_opt.get.min < aliceCloseSig1.feeSatoshis) assert(aliceCloseSig1.feeSatoshis < aliceCloseSig1.feeRange_opt.get.max) @@ -209,11 +209,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike testClosingSignedDifferentFees(f, bobInitiates = true) } - test("recv ClosingSigned (theirCloseFee != ourCloseFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testClosingSignedDifferentFees(f) - } - - test("recv ClosingSigned (theirCloseFee != ourCloseFee, anchor outputs, upfront shutdown scripts)", Tag(ChannelStateTestsTags.AnchorOutputs), Tag(ChannelStateTestsTags.UpfrontShutdownScript)) { f => + test("recv ClosingSigned (theirCloseFee != ourCloseFee, upfront shutdown scripts)", Tag(ChannelStateTestsTags.UpfrontShutdownScript)) { f => testClosingSignedDifferentFees(f) } @@ -254,7 +250,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike // alice is funder so she initiates the negotiation val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned] - assert(aliceCloseSig1.feeSatoshis == 3370.sat) // matches a feerate of 5 000 sat/kw + assert(aliceCloseSig1.feeSatoshis == 3850.sat) // matches a feerate of 5 000 sat/kw assert(aliceCloseSig1.feeRange_opt.nonEmpty) alice2bob.forward(bob) // bob agrees with that proposal @@ -276,10 +272,6 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike testClosingSignedSameFees(f, bobInitiates = true) } - test("recv ClosingSigned (theirCloseFee == ourCloseFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testClosingSignedSameFees(f) - } - test("recv ClosingSigned (theirCloseFee == ourCloseFee, upfront shutdown script)", Tag(ChannelStateTestsTags.UpfrontShutdownScript)) { f => testClosingSignedSameFees(f) } @@ -291,15 +283,15 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike aliceClose(f, Some(ClosingFeerates(FeeratePerKw(2500 sat), FeeratePerKw(2000 sat), FeeratePerKw(3000 sat)))) // alice initiates the negotiation with a very low feerate val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned] - assert(aliceCloseSig.feeSatoshis == 1685.sat) - assert(aliceCloseSig.feeRange_opt.contains(FeeRange(1348 sat, 2022 sat))) + assert(aliceCloseSig.feeSatoshis == 1925.sat) + assert(aliceCloseSig.feeRange_opt.contains(FeeRange(1540 sat, 2310 sat))) alice2bob.forward(bob) // bob chooses alice's highest fee val bobCloseSig = bob2alice.expectMsgType[ClosingSigned] - assert(bobCloseSig.feeSatoshis == 2022.sat) + assert(bobCloseSig.feeSatoshis == 2310.sat) bob2alice.forward(alice) // alice accepts this proposition - assert(alice2bob.expectMsgType[ClosingSigned].feeSatoshis == 2022.sat) + assert(alice2bob.expectMsgType[ClosingSigned].feeSatoshis == 2310.sat) alice2bob.forward(bob) val mutualCloseTx = alice2blockchain.expectMsgType[PublishFinalTx].tx assert(bob2blockchain.expectMsgType[PublishFinalTx].tx == mutualCloseTx) @@ -314,7 +306,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike bobClose(f, Some(ClosingFeerates(FeeratePerKw(2500 sat), FeeratePerKw(2000 sat), FeeratePerKw(3000 sat)))) // alice is funder, so bob's override will simply be ignored val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned] - assert(aliceCloseSig.feeSatoshis == 6740.sat) // matches a feerate of 10000 sat/kw + assert(aliceCloseSig.feeSatoshis == 7700.sat) // matches a feerate of 10000 sat/kw alice2bob.forward(bob) // bob directly agrees because their fee estimator matches val bobCloseSig = bob2alice.expectMsgType[ClosingSigned] @@ -358,8 +350,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike aliceClose(f) val aliceClosing1 = alice2bob.expectMsgType[ClosingSigned] val Some(FeeRange(_, maxFee)) = aliceClosing1.feeRange_opt - assert(aliceClosing1.feeSatoshis == 674.sat) - assert(maxFee == 1348.sat) + assert(aliceClosing1.feeSatoshis == 770.sat) + assert(maxFee == 1540.sat) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.length == 1) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt.isEmpty) // bob makes a proposal outside our fee range @@ -367,21 +359,21 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike bob2alice.send(alice, bobClosing1) val aliceClosing2 = alice2bob.expectMsgType[ClosingSigned] assert(aliceClosing1.feeSatoshis < aliceClosing2.feeSatoshis) - assert(aliceClosing2.feeSatoshis < 1600.sat) + assert(aliceClosing2.feeSatoshis < 1700.sat) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.length == 2) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt.nonEmpty) val (_, bobClosing2) = makeLegacyClosingSigned(f, 2000 sat) bob2alice.send(alice, bobClosing2) val aliceClosing3 = alice2bob.expectMsgType[ClosingSigned] assert(aliceClosing2.feeSatoshis < aliceClosing3.feeSatoshis) - assert(aliceClosing3.feeSatoshis < 1800.sat) + assert(aliceClosing3.feeSatoshis < 1900.sat) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.length == 3) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt.nonEmpty) - val (_, bobClosing3) = makeLegacyClosingSigned(f, 1800 sat) + val (_, bobClosing3) = makeLegacyClosingSigned(f, 1900 sat) bob2alice.send(alice, bobClosing3) val aliceClosing4 = alice2bob.expectMsgType[ClosingSigned] assert(aliceClosing3.feeSatoshis < aliceClosing4.feeSatoshis) - assert(aliceClosing4.feeSatoshis < 1800.sat) + assert(aliceClosing4.feeSatoshis < 1900.sat) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.length == 4) assert(alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt.nonEmpty) val (_, bobClosing4) = makeLegacyClosingSigned(f, aliceClosing4.feeSatoshis) @@ -459,7 +451,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike bob2alice.expectNoMessage(100 millis) } - test("recv ClosingSigned (fee higher than commit tx fee)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv ClosingSigned (fee higher than commit tx fee)") { f => import f._ val commitment = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest val commitFee = Transactions.commitTxFeeMsat(commitment.localCommitParams.dustLimit, commitment.localCommit.spec, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) @@ -484,9 +476,10 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike bob ! aliceCloseSig.copy(signature = ByteVector64.Zeroes) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray).startsWith("invalid close signature")) - assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) - bob2blockchain.expectMsgType[PublishTx] - bob2blockchain.expectMsgType[WatchTxConfirmed] + bob2blockchain.expectFinalTxPublished(tx.txid) + bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + bob2blockchain.expectFinalTxPublished("local-main-delayed") + bob2blockchain.expectWatchTxConfirmed(tx.txid) } def testReceiveClosingCompleteBothOutputs(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { @@ -558,7 +551,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike assert(bob.stateName == NEGOTIATING_SIMPLE) } - test("recv ClosingComplete (both outputs)", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv ClosingComplete (both outputs)", Tag(ChannelStateTestsTags.SimpleClose)) { f => testReceiveClosingCompleteBothOutputs(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -596,7 +589,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike assert(bob.stateName == NEGOTIATING_SIMPLE) } - test("recv ClosingComplete (single output)", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.NoPushAmount)) { f => + test("recv ClosingComplete (single output)", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.NoPushAmount)) { f => testReceiveClosingCompleteSingleOutput(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -658,7 +651,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike bob2blockchain.expectNoMessage(100 millis) } - test("recv ClosingComplete (missing closee output)", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv ClosingComplete (missing closee output)", Tag(ChannelStateTestsTags.SimpleClose)) { f => testReceiveClosingCompleteMissingCloseeOutput(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -1069,9 +1062,10 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val tx = alice.signCommitTx() alice ! Error(ByteVector32.Zeroes, "oops") awaitCond(alice.stateName == CLOSING) - assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid) - alice2blockchain.expectMsgType[PublishTx] - assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) + alice2blockchain.expectFinalTxPublished(tx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] + alice2blockchain.expectFinalTxPublished("local-main-delayed") + alice2blockchain.expectWatchTxConfirmed(tx.txid) } test("recv Error (option_simple_close)", Tag(ChannelStateTestsTags.SimpleClose)) { f => 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 2be9104ac8..3216b151d3 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 @@ -142,7 +142,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) alice2blockchain.expectFinalTxPublished("commit-tx") - alice2blockchain.expectFinalTxPublished("local-main-delayed") eventListener.expectMsgType[ChannelAborted] // test starts here @@ -157,7 +156,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) alice2blockchain.expectFinalTxPublished("commit-tx") - alice2blockchain.expectFinalTxPublished("local-main-delayed") eventListener.expectMsgType[ChannelAborted] // test starts here @@ -174,9 +172,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) alice2bob.expectMsgType[Error] val commitTx = alice2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed").tx alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain.txIn.head.outPoint) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMain.txIn.head.outPoint, anchorTx.input.outPoint)) eventListener.expectMsgType[ChannelAborted] // test starts here @@ -194,9 +193,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) alice2bob.expectMsgType[Error] val commitTx = alice2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = alice2blockchain.expectFinalTxPublished("local-main-delayed").tx alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain.txIn.head.outPoint) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMain.txIn.head.outPoint, anchorTx.input.outPoint)) eventListener.expectMsgType[ChannelAborted] // test starts here @@ -215,9 +215,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == CLOSING) bob2alice.expectMsgType[Error] val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = bob2blockchain.expectFinalTxPublished("local-main-delayed").tx bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - bob2blockchain.expectWatchOutputSpent(claimMain.txIn.head.outPoint) + bob2blockchain.expectWatchOutputsSpent(Seq(claimMain.txIn.head.outPoint, anchorTx.input.outPoint)) eventListener.expectMsgType[ChannelAborted] // test starts here @@ -235,9 +236,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == CLOSING) bob2alice.expectMsgType[Error] val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = bob2blockchain.expectFinalTxPublished("local-main-delayed").tx bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - bob2blockchain.expectWatchOutputSpent(claimMain.txIn.head.outPoint) + bob2blockchain.expectWatchOutputsSpent(Seq(claimMain.txIn.head.outPoint, anchorTx.input.outPoint)) eventListener.expectMsgType[ChannelAborted] // test starts here @@ -255,9 +257,10 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == CLOSING) bob2alice.expectMsgType[Error] val commitTx = bob2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = bob2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain = bob2blockchain.expectFinalTxPublished("local-main-delayed").tx bob2blockchain.expectWatchTxConfirmed(commitTx.txid) - bob2blockchain.expectWatchOutputSpent(claimMain.txIn.head.outPoint) + bob2blockchain.expectWatchOutputsSpent(Seq(claimMain.txIn.head.outPoint, anchorTx.input.outPoint)) eventListener.expectMsgType[ChannelAborted] // test starts here @@ -323,11 +326,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchFundingSpentTriggered (mutual close before converging)") { f => - testMutualCloseBeforeConverge(f, DefaultCommitmentFormat) - } - - test("recv WatchFundingSpentTriggered (mutual close before converging, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testMutualCloseBeforeConverge(f, UnsafeLegacyAnchorOutputsCommitmentFormat) + testMutualCloseBeforeConverge(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } test("recv WatchTxConfirmedTriggered (mutual close)") { f => @@ -396,12 +395,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob claims the htlc output from Alice's commit tx using its preimage. bob ! WatchFundingSpentTriggered(lcp.commitTx) - initialState.commitments.latest.commitmentFormat match { - case DefaultCommitmentFormat => () - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => - bob2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] - bob2blockchain.expectFinalTxPublished("remote-main-delayed") - } + bob2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] + bob2blockchain.expectFinalTxPublished("remote-main-delayed") val claimHtlcSuccessTx1 = bob2blockchain.expectReplaceableTxPublished[ClaimHtlcSuccessTx].sign() val claimHtlcSuccessTx2 = bob2blockchain.expectReplaceableTxPublished[ClaimHtlcSuccessTx].sign() assert(Seq(claimHtlcSuccessTx1, claimHtlcSuccessTx2).flatMap(_.txIn.map(_.outPoint)).toSet == lcp.htlcOutputs) @@ -426,10 +421,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with extractPreimageFromClaimHtlcSuccess(f) } - test("recv WatchOutputSpentTriggered (extract preimage from Claim-HTLC-success tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - extractPreimageFromClaimHtlcSuccess(f) - } - test("recv WatchOutputSpentTriggered (extract preimage from Claim-HTLC-success tx, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => extractPreimageFromClaimHtlcSuccess(f) } @@ -470,10 +461,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with extractPreimageFromHtlcSuccess(f) } - test("recv WatchOutputSpentTriggered (extract preimage from HTLC-success tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - extractPreimageFromHtlcSuccess(f) - } - test("recv WatchOutputSpentTriggered (extract preimage from HTLC-success tx, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => extractPreimageFromHtlcSuccess(f) } @@ -513,18 +500,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Alice prepares Claim-HTLC-timeout transactions for each HTLC. alice ! WatchFundingSpentTriggered(rcp.commitTx) - val (anchorTx_opt, mainTx_opt) = bobStateWithHtlc.commitments.latest.commitmentFormat match { - case DefaultCommitmentFormat => (None, None) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => - val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] - val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") - (Some(anchorTx), Some(mainTx)) - } + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") val claimHtlcTimeoutTxs = Seq(htlc1, htlc2, htlc3).map(_ => alice2blockchain.expectReplaceableTxPublished[ClaimHtlcTimeoutTx]) assert(claimHtlcTimeoutTxs.map(_.htlcId).toSet == Set(htlc1, htlc2, htlc3).map(_.id)) alice2blockchain.expectWatchTxConfirmed(rcp.commitTx.txid) - mainTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.input)) - anchorTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.input.outPoint)) + alice2blockchain.expectWatchOutputSpent(mainTx.input) + alice2blockchain.expectWatchOutputSpent(anchorTx.input.outPoint) alice2blockchain.expectWatchOutputsSpent(claimHtlcTimeoutTxs.map(_.input.outPoint)) alice2blockchain.expectNoMessage(100 millis) @@ -560,10 +542,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with extractPreimageFromRemovedHtlc(f) } - test("recv WatchOutputSpentTriggered (extract preimage for removed HTLC, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - extractPreimageFromRemovedHtlc(f) - } - test("recv WatchOutputSpentTriggered (extract preimage for removed HTLC, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => extractPreimageFromRemovedHtlc(f) } @@ -598,30 +576,23 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(closingTxs.htlcTxs.isEmpty) // Bob receives the preimage for the first two HTLCs. bob ! CMD_FULFILL_HTLC(htlc1.id, preimage, None) - val htlcSuccessTxs = aliceStateWithoutHtlcs.commitments.latest.commitmentFormat match { - case DefaultCommitmentFormat => (0 until 2).map(_ => bob2blockchain.expectFinalTxPublished("htlc-success").tx) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => - val htlcSuccess = (0 until 2).map(_ => bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx]) - assert(htlcSuccess.map(_.htlcId).toSet == Set(htlc1.id, htlc2.id)) - htlcSuccess.map(_.sign()) + val htlcSuccessTxs = { + val htlcSuccess = (0 until 2).map(_ => bob2blockchain.expectReplaceableTxPublished[HtlcSuccessTx]) + assert(htlcSuccess.map(_.htlcId).toSet == Set(htlc1.id, htlc2.id)) + htlcSuccess.map(_.sign()) } bob2blockchain.expectNoMessage(100 millis) val batchHtlcSuccessTx = Transaction(2, htlcSuccessTxs.flatMap(_.txIn), htlcSuccessTxs.flatMap(_.txOut), 0) // Alice prepares Claim-HTLC-timeout transactions for each HTLC. alice ! WatchFundingSpentTriggered(rcp.commitTx) - val (anchorTx_opt, mainTx_opt) = aliceStateWithoutHtlcs.commitments.latest.commitmentFormat match { - case DefaultCommitmentFormat => (None, None) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => - val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] - val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") - (Some(anchorTx), Some(mainTx)) - } + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimRemoteAnchorTx] + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") val claimHtlcTimeoutTxs = Seq(htlc1, htlc2, htlc3).map(_ => alice2blockchain.expectReplaceableTxPublished[ClaimHtlcTimeoutTx]) assert(claimHtlcTimeoutTxs.map(_.htlcId).toSet == Set(htlc1, htlc2, htlc3).map(_.id)) alice2blockchain.expectWatchTxConfirmed(rcp.commitTx.txid) - mainTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.input)) - anchorTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.input.outPoint)) + alice2blockchain.expectWatchOutputSpent(mainTx.input) + alice2blockchain.expectWatchOutputSpent(anchorTx.input.outPoint) alice2blockchain.expectWatchOutputsSpent(claimHtlcTimeoutTxs.map(_.input.outPoint)) alice2blockchain.expectNoMessage(100 millis) @@ -658,21 +629,16 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with extractPreimageFromNextHtlcs(f) } - test("recv WatchOutputSpentTriggered (extract preimage for next batch of HTLCs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - extractPreimageFromNextHtlcs(f) - } - test("recv WatchOutputSpentTriggered (extract preimage for next batch of HTLCs, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => extractPreimageFromNextHtlcs(f) } - test("recv CMD_BUMP_FORCE_CLOSE_FEE (local commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_BUMP_FORCE_CLOSE_FEE (local commit)") { f => import f._ - val (localCommitPublished, closingTxs) = localClose(alice, alice2blockchain) + val (localCommitPublished, _) = localClose(alice, alice2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] assert(initialState.localCommitPublished.nonEmpty) - assert(closingTxs.anchorTx_opt.nonEmpty) val replyTo = TestProbe() alice ! CMD_BUMP_FORCE_CLOSE_FEE(replyTo.ref, ConfirmationTarget.Priority(ConfirmationPriority.Fast)) @@ -740,11 +706,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv WatchTxConfirmedTriggered (local commit)") { f => - testLocalCommitTxConfirmed(f, DefaultCommitmentFormat) - } - - test("recv WatchTxConfirmedTriggered (local commit, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv WatchTxConfirmedTriggered (local commit)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testLocalCommitTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } @@ -901,7 +863,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(bob, alice, bob2alice, alice2bob) assert(alice2relayer.expectMsgType[RelayForward].add == htlc) val aliceCommitTx = alice.signCommitTx() - assert(aliceCommitTx.txOut.size == 3) // 2 main outputs + 1 htlc + assert(aliceCommitTx.txOut.size == 5) // 2 main outputs + 2 anchor outputs + 1 htlc // alice fulfills the HTLC but bob doesn't receive the signature alice ! CMD_FULFILL_HTLC(htlc.id, r, None, commit = true) @@ -937,7 +899,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // note that alice doesn't receive the last revocation // then we make alice unilaterally close the channel val (closingState, closingTxs) = localClose(alice, alice2blockchain) - assert(closingState.commitTx.txOut.length == 2) // htlc has been removed + assert(closingState.commitTx.txOut.length == 4) // htlc has been removed (only main outputs and anchor outputs) // actual test starts here channelUpdateListener.expectMsgType[LocalChannelDown] @@ -953,7 +915,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2relayer.expectNoMessage(100 millis) } - test("recv WatchTxConfirmedTriggered (local commit followed by htlc settlement)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (local commit followed by htlc settlement)") { f => import f._ // Bob sends 2 HTLCs to Alice that will be settled during the force-close: one will be fulfilled, the other will be failed. val (r1, htlc1) = addHtlc(75_000_000 msat, CltvExpiryDelta(48), bob, alice, bob2alice, alice2bob) @@ -1044,13 +1006,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // then we should re-publish unconfirmed transactions alice2blockchain.expectFinalTxPublished(closingState.commitTx.txid) // we increase the feerate of our main transaction, but cap it to our max-closing-feerate + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val mainTx2 = closingTxs.mainTx_opt.map(_ => alice2blockchain.expectFinalTxPublished("local-main-delayed")).get assert(mainTx2.tx.txOut.head.amount < closingTxs.mainTx_opt.get.txOut.head.amount) val mainFeerate = Transactions.fee2rate(mainTx2.fee, mainTx2.tx.weight()) - assert(FeeratePerKw(14_500 sat) <= mainFeerate && mainFeerate <= FeeratePerKw(15_500 sat)) - assert(alice2blockchain.expectFinalTxPublished("htlc-timeout").input == htlcTimeoutTx.txIn.head.outPoint) + assert(mainFeerate <= FeeratePerKw(15_000 sat)) + assert(alice2blockchain.expectReplaceableTxPublished[HtlcTimeoutTx].input.outPoint == htlcTimeoutTx.txIn.head.outPoint) alice2blockchain.expectWatchTxConfirmed(closingState.commitTx.txid) closingTxs.mainTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.txIn.head.outPoint)) + alice2blockchain.expectWatchOutputSpent(closingTxs.anchorTx.txIn.head.outPoint) alice2blockchain.expectWatchOutputSpent(htlcTimeoutTx.txIn.head.outPoint) // the htlc transaction confirms, so we publish a 3rd-stage transaction @@ -1082,7 +1046,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv INPUT_RESTORED (local commit with htlc-delayed transactions)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv INPUT_RESTORED (local commit with htlc-delayed transactions)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ // Alice has one incoming and one outgoing HTLC. @@ -1097,7 +1061,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // The commit tx confirms. alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, closingState1.commitTx) - closingTxs.anchorTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(42), 1, tx)) + alice ! WatchTxConfirmedTriggered(BlockHeight(42), 1, closingTxs.anchorTx) alice2blockchain.expectNoMessage(100 millis) // Alice receives the preimage for the incoming HTLC. @@ -1149,7 +1113,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv INPUT_RESTORED (htlcs claimed by both local and remote)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_RESTORED (htlcs claimed by both local and remote)") { f => import f._ // Alice and Bob each sends 3 HTLCs: @@ -1188,7 +1152,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // The commit transaction and main transactions confirm. alice ! WatchTxConfirmedTriggered(BlockHeight(750_000), 3, closingStateAlice.commitTx) - alice ! WatchTxConfirmedTriggered(BlockHeight(750_000), 5, closingTxsAlice.anchorTx_opt.get) + alice ! WatchTxConfirmedTriggered(BlockHeight(750_000), 5, closingTxsAlice.anchorTx) alice ! WatchTxConfirmedTriggered(BlockHeight(750_001), 1, closingTxsAlice.mainTx_opt.get) alice ! WatchTxConfirmedTriggered(BlockHeight(750_001), 2, closingTxsBob.mainTx_opt.get) alice2blockchain.expectNoMessage(100 millis) @@ -1300,7 +1264,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test starts here channelUpdateListener.expectMsgType[LocalChannelDown] - assert(closingState.localOutput_opt.isEmpty) assert(closingState.htlcOutputs.isEmpty) // when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id) @@ -1362,11 +1325,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state val bobCommitTx = bobCommitTxs.last - assert(bobCommitTx.txOut.size == 2) // two main outputs - val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) - assert(closingState.localOutput_opt.isEmpty) + assert(bobCommitTx.txOut.size == 4) // two main outputs and two anchor outputs + val (closingState, _) = remoteClose(bobCommitTx, alice, alice2blockchain) assert(closingState.htlcOutputs.isEmpty) - assert(closingTxs.mainTx_opt.isEmpty) assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) @@ -1391,21 +1352,19 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) // Bob publishes his last current commit tx, the one it had when entering NEGOTIATING state. val bobCommitTx = bobCommitTxs.last - val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) + val (closingState, _) = remoteClose(bobCommitTx, alice, alice2blockchain) assert(closingState.htlcOutputs.isEmpty) - assert(closingTxs.mainTx_opt.isEmpty) val txPublished = txListener.expectMsgType[TransactionPublished] assert(txPublished.tx == bobCommitTx) assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit } - test("recv CMD_BUMP_FORCE_CLOSE_FEE (remote commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv CMD_BUMP_FORCE_CLOSE_FEE (remote commit)") { f => import f._ val bobCommitTx = bobCommitTxs.last - val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain) + val (closingState, _) = remoteClose(bobCommitTx, alice, alice2blockchain) assert(closingState.anchorOutput_opt.nonEmpty) - assert(closingTxs.anchorTx_opt.nonEmpty) val replyTo = TestProbe() alice ! CMD_BUMP_FORCE_CLOSE_FEE(replyTo.ref, ConfirmationTarget.Priority(ConfirmationPriority.Fast)) @@ -1417,7 +1376,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - test("recv WatchTxConfirmedTriggered (remote commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (remote commit)") { f => import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1438,24 +1397,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv WatchTxConfirmedTriggered (remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.SimpleClose)) { f => - import f._ - mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) - assert(alice.commitments.latest.commitmentFormat == DefaultCommitmentFormat) - // bob publishes his last current commit tx, the one it had when entering NEGOTIATING state - val bobCommitTx = bobCommitTxs.last - assert(bobCommitTx.txOut.size == 2) // two main outputs - alice ! WatchFundingSpentTriggered(bobCommitTx) - - // alice won't create a claimMainOutputTx because her main output is already spendable by the wallet - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.localOutput_opt.isEmpty) - assert(alice.stateName == CLOSING) - // once the remote commit is confirmed the channel is definitively closed - alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - awaitCond(alice.stateName == CLOSED) - } - - test("recv WatchTxConfirmedTriggered (remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (remote commit, anchor outputs zero fee htlc txs)") { f => import f._ mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) val initialState = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1491,10 +1433,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the latest commit tx. val bobCommitTx = bob.signCommitTx() - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs - case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs - } + assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 3) assert(closingState.htlcOutputs.size == 3) assert(closingTxs.htlcTimeoutTxs.length == 3) @@ -1516,23 +1455,19 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment)") { f => - testRemoteCommitTxWithHtlcsConfirmed(f, DefaultCommitmentFormat) + test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs zero fee htlc txs)") { f => + testRemoteCommitTxWithHtlcsConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testRemoteCommitTxWithHtlcsConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRemoteCommitTxWithHtlcsConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - } - test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => testRemoteCommitTxWithHtlcsConfirmed(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } - test("recv WatchTxConfirmedTriggered (remote commit) followed by htlc settlement", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (remote commit) followed by htlc settlement") { f => import f._ // Bob sends 2 HTLCs to Alice that will be settled during the force-close: one will be fulfilled, the other will be failed. val (r1, htlc1) = addHtlc(110_000_000 msat, CltvExpiryDelta(48), bob, alice, bob2alice, alice2bob) @@ -1563,7 +1498,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob's commitment confirms: the third htlc was not included in the commit tx published on-chain, so we can consider it failed. alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - closingTxs.anchorTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 1, tx)) + alice ! WatchTxConfirmedTriggered(BlockHeight(0), 1, closingTxs.anchorTx) assert(alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc == htlc3) // Alice's main transaction confirms. closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) @@ -1611,14 +1546,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Our main transaction should have a lower feerate. // HTLC transactions are unchanged: the feerate will be based on their expiry. closingTxs.mainTx_opt.foreach(tx => { - val tx2 = alice2blockchain.expectFinalTxPublished("local-main-delayed") + val tx2 = alice2blockchain.expectFinalTxPublished("remote-main-delayed") + assert(tx2.tx.txIn.head.outPoint == tx.txIn.head.outPoint) assert(tx2.tx.txOut.head.amount > tx.txOut.head.amount) - alice2blockchain.expectWatchOutputSpent(tx.txIn.head.outPoint) }) val htlcTimeout = alice2blockchain.expectReplaceableTxPublished[ClaimHtlcTimeoutTx](ConfirmationTarget.Absolute(htlc.cltvExpiry.blockHeight)) assert(htlcTimeout.input.outPoint == htlcTimeoutTx.txIn.head.outPoint) assert(htlcTimeout.tx.txid == htlcTimeoutTx.txid) - alice2blockchain.expectWatchOutputSpent(htlcTimeout.input.outPoint) + alice2blockchain.expectWatchOutputsSpent(Seq(closingTxs.mainTx_opt.get.txIn.head.outPoint, htlcTimeout.input.outPoint)) } private def testNextRemoteCommitTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, PublishedForceCloseTxs, Set[UpdateAddHtlc]) = { @@ -1643,59 +1578,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes the next commit tx. val bobCommitTx = bob.signCommitTx() - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs - case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs - } + assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs val (closingState, closingTxs) = remoteClose(bobCommitTx, alice, alice2blockchain, htlcTimeoutCount = 3) assert(closingState.htlcOutputs.size == 3) assert(closingTxs.htlcTimeoutTxs.size == 3) (bobCommitTx, closingTxs, Set(htlca1, htlca2, htlca3)) } - test("recv WatchTxConfirmedTriggered (next remote commit)") { f => - import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) - val txPublished = txListener.expectMsgType[TransactionPublished] - assert(txPublished.tx == bobCommitTx) - assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit - alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) - assert(txListener.expectMsgType[TransactionConfirmed].tx == bobCommitTx) - closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, tx)) - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(201), 0, closingTxs.htlcTimeoutTxs(0)) - val forwardedFail1 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(202), 0, closingTxs.htlcTimeoutTxs(1)) - val forwardedFail2 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(203), 1, closingTxs.htlcTimeoutTxs(2)) - val forwardedFail3 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - assert(Set(forwardedFail1, forwardedFail2, forwardedFail3) == htlcs) - alice2relayer.expectNoMessage(100 millis) - awaitCond(alice.stateName == CLOSED) - } - - test("recv WatchTxConfirmedTriggered (next remote commit, static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - import f._ - val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) - alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) - assert(closingTxs.mainTx_opt.isEmpty) // with static_remotekey we don't claim out main output - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(201), 0, closingTxs.htlcTimeoutTxs(0)) - val forwardedFail1 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(202), 0, closingTxs.htlcTimeoutTxs(1)) - val forwardedFail2 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - alice2relayer.expectNoMessage(100 millis) - alice ! WatchTxConfirmedTriggered(BlockHeight(203), 1, closingTxs.htlcTimeoutTxs(2)) - val forwardedFail3 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc - assert(Set(forwardedFail1, forwardedFail2, forwardedFail3) == htlcs) - alice2relayer.expectNoMessage(100 millis) - awaitCond(alice.stateName == CLOSED) - } - - test("recv WatchTxConfirmedTriggered (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (next remote commit, anchor outputs zero fee htlc txs)") { f => import f._ val (bobCommitTx, closingTxs, htlcs) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx) @@ -1737,7 +1627,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv WatchTxConfirmedTriggered (next remote commit) followed by htlc settlement", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (next remote commit) followed by htlc settlement") { f => import f._ // Bob sends 2 HTLCs to Alice that will be settled during the force-close: one will be fulfilled, the other will be failed. val (r1, htlc1) = addHtlc(110_000_000 msat, CltvExpiryDelta(64), bob, alice, bob2alice, alice2bob) @@ -1772,7 +1662,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob's commitment and Alice's main transaction confirm. alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - closingTxs.anchorTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) + alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingTxs.anchorTx) closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, tx)) // Alice receives a failure for the second HTLC from downstream; she can stop watching the corresponding HTLC output. @@ -1800,7 +1690,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv INPUT_RESTORED (next remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_RESTORED (next remote commit)") { f => import f._ val (bobCommitTx, closingTxs, _) = testNextRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) @@ -1862,42 +1752,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) // bob is nice and publishes its commitment val bobCommitTx = bob.signCommitTx() - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs - case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs - } + assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs alice ! WatchFundingSpentTriggered(bobCommitTx) bobCommitTx } - test("recv WatchTxConfirmedTriggered (future remote commit)") { f => - import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) - val txPublished = txListener.expectMsgType[TransactionPublished] - assert(txPublished.tx == bobCommitTx) - assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit - // bob's commit tx sends directly to alice's wallet - alice2blockchain.expectWatchTxConfirmed(bobCommitTx.txid) - awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined) - alice2blockchain.expectNoMessage(100 millis) // alice ignores the htlc-timeout - - // actual test starts here - alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - assert(txListener.expectMsgType[TransactionConfirmed].tx == bobCommitTx) - awaitCond(alice.stateName == CLOSED) - } - - test("recv WatchTxConfirmedTriggered (future remote commit, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) - // using option_static_remotekey alice doesn't need to sweep her output - awaitCond(alice.stateName == CLOSING) - alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) - // after the commit tx is confirmed the channel is closed, no claim transactions needed - awaitCond(alice.stateName == CLOSED) - } - - test("recv WatchTxConfirmedTriggered (future remote commit, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (future remote commit, anchor outputs zero fee htlc txs)") { f => import f._ val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // alice is able to claim its main output @@ -1933,7 +1793,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_RESTORED (future remote commit)") { f => import f._ - val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, DefaultCommitmentFormat) + val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) // simulate a node restart val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] @@ -1941,9 +1801,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! INPUT_RESTORED(beforeRestart) awaitCond(alice.stateName == CLOSING) - // bob's commit tx sends funds directly to our wallet + // bob republishes his main transaction + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") alice2blockchain.expectWatchTxConfirmed(bobCommitTx.txid) alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx) + alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, mainTx.tx) awaitCond(alice.stateName == CLOSED) } @@ -1951,15 +1813,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with case class RevokedCloseFixture(bobRevokedTxs: Seq[RevokedCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)]) - private def prepareRevokedClose(f: FixtureParam, commitmentFormat: CommitmentFormat): RevokedCloseFixture = { + private def prepareRevokedClose(f: FixtureParam): RevokedCloseFixture = { import f._ // Bob's first commit tx doesn't contain any htlc val bobCommit1 = RevokedCommit(bob.signCommitTx(), Nil) - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 4) // 2 main outputs + 2 anchors - case DefaultCommitmentFormat => assert(bobCommit1.commitTx.txOut.size == 2) // 2 main outputs - } + assert(bobCommit1.commitTx.txOut.size == 4) // 2 main outputs + 2 anchors // Bob's second commit tx contains 1 incoming htlc and 1 outgoing htlc val (bobCommit2, htlcAlice1, htlcBob1) = { @@ -1972,10 +1831,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit2.commitTx.txOut.size) - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 6) - case DefaultCommitmentFormat => assert(bobCommit2.commitTx.txOut.size == 4) - } + assert(bobCommit2.commitTx.txOut.size == 6) // Bob's third commit tx contains 2 incoming htlcs and 2 outgoing htlcs val (bobCommit3, htlcAlice2, htlcBob2) = { @@ -1988,10 +1844,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit3.commitTx.txOut.size) - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 8) - case DefaultCommitmentFormat => assert(bobCommit3.commitTx.txOut.size == 6) - } + assert(bobCommit3.commitTx.txOut.size == 8) // Bob's fourth commit tx doesn't contain any htlc val bobCommit4 = { @@ -2002,20 +1855,17 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } assert(alice.signCommitTx().txOut.size == bobCommit4.commitTx.txOut.size) - commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 4) - case DefaultCommitmentFormat => assert(bobCommit4.commitTx.txOut.size == 2) - } + assert(bobCommit4.commitTx.txOut.size == 4) RevokedCloseFixture(Seq(bobCommit1, bobCommit2, bobCommit3, bobCommit4), Seq(htlcAlice1, htlcAlice2), Seq(htlcBob1, htlcBob2)) } - case class RevokedCloseTxs(mainTx_opt: Option[Transaction], mainPenaltyTx: Transaction, htlcPenaltyTxs: Seq[Transaction]) + case class RevokedCloseTxs(mainTx: Transaction, mainPenaltyTx: Transaction, htlcPenaltyTxs: Seq[Transaction]) private def setupFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): (Transaction, RevokedCloseTxs) = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + val revokedCloseFixture = prepareRevokedClose(f) assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs @@ -2026,15 +1876,13 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedTx) - if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) { - assert(rvk.localOutput_opt.nonEmpty) - } + assert(rvk.localOutput_opt.nonEmpty) assert(rvk.remoteOutput_opt.nonEmpty) assert(rvk.htlcOutputs.size == 2) assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs - val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") val mainPenaltyTx = alice2blockchain.expectFinalTxPublished("main-penalty") Transaction.correctlySpends(mainPenaltyTx.tx, bobRevokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) val htlcPenaltyTxs = (0 until 2).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) @@ -2042,19 +1890,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with htlcPenaltyTxs.foreach(penaltyTx => Transaction.correctlySpends(penaltyTx.tx, bobRevokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) // alice spends all outpoints of the revoked tx, except her main output when it goes directly to our wallet - val spentOutpoints = mainTx_opt.map(_.input) ++ Seq(mainPenaltyTx.input) ++ htlcPenaltyTxs.map(_.input) - commitmentFormat match { - case DefaultCommitmentFormat if mainTx_opt.isEmpty => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 1) // we don't claim our main output, it directly goes to our wallet - case DefaultCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size) - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(spentOutpoints.size == bobRevokedTx.txOut.size - 2) // we don't claim the anchors - } + val spentOutpoints = Seq(mainTx.input, mainPenaltyTx.input) ++ htlcPenaltyTxs.map(_.input) + assert(spentOutpoints.size == bobRevokedTx.txOut.size - 2) // we don't claim the anchors // alice watches on-chain transactions alice2blockchain.expectWatchTxConfirmed(bobRevokedTx.txid) - alice2blockchain.expectWatchOutputsSpent(spentOutpoints.toSeq) + alice2blockchain.expectWatchOutputsSpent(spentOutpoints) alice2blockchain.expectNoMessage(100 millis) - (bobRevokedTx, RevokedCloseTxs(mainTx_opt.map(_.tx), mainPenaltyTx.tx, htlcPenaltyTxs.map(_.tx))) + (bobRevokedTx, RevokedCloseTxs(mainTx.tx, mainPenaltyTx.tx, htlcPenaltyTxs.map(_.tx))) } private def testFundingSpentRevokedTx(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { @@ -2069,7 +1913,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, bobRevokedTx) assert(txListener.expectMsgType[TransactionConfirmed].tx == bobRevokedTx) alice ! WatchTxConfirmedTriggered(BlockHeight(110), 1, closingTxs.mainPenaltyTx) - closingTxs.mainTx_opt.foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(110), 2, tx)) + alice ! WatchTxConfirmedTriggered(BlockHeight(110), 2, closingTxs.mainTx) closingTxs.htlcPenaltyTxs.dropRight(1).foreach(tx => alice ! WatchTxConfirmedTriggered(BlockHeight(115), 0, tx)) assert(alice.stateName == CLOSING) alice ! WatchTxConfirmedTriggered(BlockHeight(115), 2, closingTxs.htlcPenaltyTxs.last) @@ -2077,15 +1921,15 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val closedAlice = alice.stateData.asInstanceOf[DATA_CLOSED] assert(closedAlice.closingType == "revoked-close") assert(closedAlice.closingTxId == bobRevokedTx.txid) - assert(closedAlice.closingAmount == closingTxs.mainTx_opt.map(_.txOut.head.amount).getOrElse(0 sat) + closingTxs.mainPenaltyTx.txOut.head.amount + closingTxs.htlcPenaltyTxs.map(_.txOut.head.amount).sum) + assert(closedAlice.closingAmount == closingTxs.mainTx.txOut.head.amount + closingTxs.mainPenaltyTx.txOut.head.amount + closingTxs.htlcPenaltyTxs.map(_.txOut.head.amount).sum) } - test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testFundingSpentRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) + test("recv WatchFundingSpentTriggered (one revoked tx)") { f => + testFundingSpentRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testFundingSpentRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + test("recv WatchFundingSpentTriggered (one revoked tx, anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => + testFundingSpentRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } test("recv WatchFundingSpentTriggered (one revoked tx, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => @@ -2094,7 +1938,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingSpentTriggered (multiple revoked tx)", Tag(ChannelStateTestsTags.OptionSimpleTaprootPhoenix)) { f => import f._ - val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) + val revokedCloseFixture = prepareRevokedClose(f) assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTx.txid).toSet.size == revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct def broadcastBobRevokedTx(revokedTx: Transaction, htlcCount: Int, revokedCount: Int): RevokedCloseTxs = { @@ -2112,7 +1956,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectWatchOutputsSpent(mainTx.input +: mainPenalty.input +: htlcPenaltyTxs.map(_.input)) alice2blockchain.expectNoMessage(100 millis) - RevokedCloseTxs(Some(mainTx.tx), mainPenalty.tx, htlcPenaltyTxs.map(_.tx)) + RevokedCloseTxs(mainTx.tx, mainPenalty.tx, htlcPenaltyTxs.map(_.tx)) } // bob publishes a first revoked tx (no htlc in that commitment) @@ -2125,7 +1969,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob's second revoked tx confirms: once all penalty txs are confirmed, alice can move to the closed state // NB: if multiple txs confirm in the same block, we may receive the events in any order alice ! WatchTxConfirmedTriggered(BlockHeight(100), 1, closingTxs.mainPenaltyTx) - alice ! WatchTxConfirmedTriggered(BlockHeight(100), 2, closingTxs.mainTx_opt.get) + alice ! WatchTxConfirmedTriggered(BlockHeight(100), 2, closingTxs.mainTx) alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, revokedCloseFixture.bobRevokedTxs(1).commitTx) alice ! WatchTxConfirmedTriggered(BlockHeight(115), 0, closingTxs.htlcPenaltyTxs(0)) assert(alice.stateName == CLOSING) @@ -2148,35 +1992,31 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // the commit tx hasn't been confirmed yet, so we watch the funding output first alice2blockchain.expectMsgType[WatchFundingSpent] // then we should re-publish unconfirmed transactions - closingTxs.mainTx_opt.foreach(_ => alice2blockchain.expectFinalTxPublished("remote-main-delayed")) + alice2blockchain.expectFinalTxPublished("remote-main-delayed") assert(alice2blockchain.expectFinalTxPublished("main-penalty").input == closingTxs.mainPenaltyTx.txIn.head.outPoint) val htlcPenaltyTxs = closingTxs.htlcPenaltyTxs.map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) assert(htlcPenaltyTxs.map(_.input).toSet == closingTxs.htlcPenaltyTxs.map(_.txIn.head.outPoint).toSet) alice2blockchain.expectWatchTxConfirmed(bobRevokedTx.txid) - closingTxs.mainTx_opt.foreach(tx => alice2blockchain.expectWatchOutputSpent(tx.txIn.head.outPoint)) + alice2blockchain.expectWatchOutputSpent(closingTxs.mainTx.txIn.head.outPoint) alice2blockchain.expectWatchOutputSpent(closingTxs.mainPenaltyTx.txIn.head.outPoint) alice2blockchain.expectWatchOutputsSpent(htlcPenaltyTxs.map(_.input)) } test("recv INPUT_RESTORED (one revoked tx)") { f => - testInputRestoredRevokedTx(f, DefaultCommitmentFormat) + testInputRestoredRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv INPUT_RESTORED (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv INPUT_RESTORED (one revoked tx, anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testInputRestoredRevokedTx(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv INPUT_RESTORED (one revoked tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testInputRestoredRevokedTx(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - } - test("recv INPUT_RESTORED (one revoked tx, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => testInputRestoredRevokedTx(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } def testRevokedHtlcTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ - val revokedCloseFixture = prepareRevokedClose(f, commitmentFormat) + val revokedCloseFixture = prepareRevokedClose(f) assert(alice.commitments.latest.commitmentFormat == commitmentFormat) // bob publishes one of his revoked txs @@ -2187,27 +2027,23 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) val rvk = alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head assert(rvk.commitTx == bobRevokedCommit.commitTx) - if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.nonEmpty) { - assert(rvk.localOutput_opt.isEmpty) - } else { - assert(rvk.localOutput_opt.nonEmpty) - } + assert(rvk.localOutput_opt.nonEmpty) assert(rvk.remoteOutput_opt.nonEmpty) assert(rvk.htlcOutputs.size == 4) assert(rvk.htlcDelayedOutputs.isEmpty) // alice publishes the penalty txs and watches outputs - val mainTx_opt = if (alice.stateData.asInstanceOf[DATA_CLOSING].channelParams.localParams.walletStaticPaymentBasepoint.isEmpty) Some(alice2blockchain.expectFinalTxPublished("remote-main-delayed")) else None + val mainTx = alice2blockchain.expectFinalTxPublished("remote-main-delayed") val mainPenalty = alice2blockchain.expectFinalTxPublished("main-penalty") val htlcPenalty = (1 to 4).map(_ => alice2blockchain.expectFinalTxPublished("htlc-penalty")) alice2blockchain.expectWatchTxConfirmed(rvk.commitTx.txid) - alice2blockchain.expectWatchOutputsSpent(mainTx_opt.map(_.input).toSeq ++ Seq(mainPenalty.input) ++ htlcPenalty.map(_.input)) + alice2blockchain.expectWatchOutputsSpent(Seq(mainTx.input, mainPenalty.input) ++ htlcPenalty.map(_.input)) alice2blockchain.expectNoMessage(100 millis) // the revoked commit and main penalty transactions confirm alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, rvk.commitTx) alice ! WatchTxConfirmedTriggered(BlockHeight(110), 0, mainPenalty.tx) - mainTx_opt.foreach(p => alice ! WatchTxConfirmedTriggered(BlockHeight(110), 1, p.tx)) + alice ! WatchTxConfirmedTriggered(BlockHeight(110), 1, mainTx.tx) // bob publishes one of his HTLC-success transactions val (fulfilledHtlc, _) = revokedCloseFixture.htlcsAlice.head @@ -2257,18 +2093,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - testRevokedHtlcTxConfirmed(f, DefaultCommitmentFormat) + test("recv WatchTxConfirmedTriggered (revoked htlc-success tx)") { f => + testRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } - test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => testRevokedHtlcTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) } - test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => - testRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - } - test("recv WatchTxConfirmedTriggered (revoked htlc-success tx, taproot)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f => testRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat) } @@ -2277,7 +2109,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ // bob publishes one of his revoked txs - val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val revokedCloseFixture = prepareRevokedClose(f) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) alice ! WatchFundingSpentTriggered(bobRevokedCommit.commitTx) awaitCond(alice.stateData.isInstanceOf[DATA_CLOSING]) @@ -2340,7 +2172,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectNoMessage(100 millis) } - test("recv WatchTxConfirmedTriggered (revoked aggregated htlc tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv WatchTxConfirmedTriggered (revoked aggregated htlc tx)") { f => testRevokedAggregatedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2353,7 +2185,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Bob publishes one of his revoked txs. alice.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2_500 sat))) - val revokedCloseFixture = prepareRevokedClose(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + val revokedCloseFixture = prepareRevokedClose(f) val bobRevokedCommit = revokedCloseFixture.bobRevokedTxs(2) val commitTx = bobRevokedCommit.commitTx alice ! WatchFundingSpentTriggered(commitTx) @@ -2454,7 +2286,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSED) } - test("recv INPUT_RESTORED (revoked htlc transactions confirmed)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("recv INPUT_RESTORED (revoked htlc transactions confirmed)") { f => testInputRestoredRevokedHtlcTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2465,11 +2297,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with private def testRevokedTxConfirmed(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = { import f._ assert(alice.commitments.latest.commitmentFormat == commitmentFormat) - val initOutputCount = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 4 - case DefaultCommitmentFormat => 2 - } - assert(bob.signCommitTx().txOut.size == initOutputCount) + assert(bob.signCommitTx().txOut.size == 4) // bob's second commit tx contains 2 incoming htlcs val (bobRevokedTx, htlcs1) = { @@ -2477,7 +2305,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (_, htlc2) = addHtlc(20_000_000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) val bobCommitTx = bob.signCommitTx() - assert(bobCommitTx.txOut.size == initOutputCount + 2) + assert(bobCommitTx.txOut.size == 6) (bobCommitTx, Seq(htlc1, htlc2)) } @@ -2487,7 +2315,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val (_, htlc4) = addHtlc(18_000_000 msat, alice, bob, alice2bob, bob2alice) failHtlc(htlcs1.head.id, bob, alice, bob2alice, alice2bob) crossSign(bob, alice, bob2alice, alice2bob) - assert(bob.signCommitTx().txOut.size == initOutputCount + 3) + assert(bob.signCommitTx().txOut.size == 7) Seq(htlc3, htlc4) } @@ -2512,14 +2340,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs)") { f => - testRevokedTxConfirmed(f, DefaultCommitmentFormat) - } - - test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => - testRevokedTxConfirmed(f, UnsafeLegacyAnchorOutputsCommitmentFormat) - } - - test("recv WatchTxConfirmedTriggered (revoked commit tx, pending htlcs, anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => testRevokedTxConfirmed(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) } @@ -2563,21 +2383,24 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(alice.stateName == CLOSING) assert(alice.stateData.asInstanceOf[DATA_CLOSING].maxClosingFeerate_opt.isEmpty) val commitTx = alice2blockchain.expectFinalTxPublished("commit-tx").tx + val anchorTx = alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain1 = alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain1.input) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMain1.input, anchorTx.input.outPoint)) alice ! CMD_FORCECLOSE(sender.ref, maxClosingFeerate_opt = Some(FeeratePerKw(5_000 sat))) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].maxClosingFeerate_opt.contains(FeeratePerKw(5_000 sat))) alice2blockchain.expectFinalTxPublished(commitTx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain2 = alice2blockchain.expectFinalTxPublished("local-main-delayed") alice2blockchain.expectWatchTxConfirmed(commitTx.txid) - alice2blockchain.expectWatchOutputSpent(claimMain2.input) + alice2blockchain.expectWatchOutputsSpent(Seq(claimMain2.input, anchorTx.input.outPoint)) assert(claimMain2.fee != claimMain1.fee) alice ! CMD_FORCECLOSE(sender.ref, maxClosingFeerate_opt = Some(FeeratePerKw(10_000 sat))) awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].maxClosingFeerate_opt.contains(FeeratePerKw(10_000 sat))) alice2blockchain.expectFinalTxPublished(commitTx.txid) + alice2blockchain.expectReplaceableTxPublished[ClaimLocalAnchorTx] val claimMain3 = alice2blockchain.expectFinalTxPublished("local-main-delayed") assert(claimMain2.fee * 1.9 <= claimMain3.fee && claimMain3.fee <= claimMain2.fee * 2.1) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index 0350fead75..66c476229d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -17,7 +17,6 @@ package fr.acinq.eclair.integration import akka.actor.ActorRef -import akka.actor.Status.Failure import akka.actor.typed.scaladsl.adapter.ClassicActorRefOps import akka.pattern.pipe import akka.testkit.TestProbe @@ -26,7 +25,7 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxId, addressFromPublicKeyScript} import fr.acinq.eclair.blockchain.bitcoind.BitcoindService.BitcoinReq -import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, JsonRPCError} +import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.Sphinx.DecryptedFailurePacket import fr.acinq.eclair.io.{Peer, Switchboard} @@ -35,7 +34,6 @@ import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceiveStandardPayment import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler} import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentToNode import fr.acinq.eclair.router.Router -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat} import fr.acinq.eclair.transactions.{OutgoingHtlc, Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, randomBytes32} @@ -52,6 +50,8 @@ import scala.jdk.CollectionConverters._ abstract class ChannelIntegrationSpec extends IntegrationSpec { + def channelType: SupportedChannelType + def awaitAnnouncements(channels: Int): Unit = { val sender = TestProbe() awaitCond({ @@ -111,7 +111,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { case class ForceCloseFixture(sender: TestProbe, paymentSender: TestProbe, stateListenerC: TestProbe, stateListenerF: TestProbe, paymentId: UUID, htlc: UpdateAddHtlc, preimage: ByteVector32, minerAddress: String, finalAddressC: String, finalAddressF: String) /** Prepare a C <-> F channel for a force-close test by adding an HTLC that will be hodl-ed at F. */ - def prepareForceCloseCF(commitmentFormat: Transactions.CommitmentFormat): ForceCloseFixture = { + def prepareForceCloseCF(): ForceCloseFixture = { val sender = TestProbe() sender.send(bitcoincli, BitcoinReq("getnewaddress")) val JString(minerAddress) = sender.expectMsgType[JValue] @@ -121,7 +121,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { nodes("C").system.eventStream.subscribe(stateListenerC.ref, classOf[ChannelStateChanged]) nodes("F").system.eventStream.subscribe(stateListenerF.ref, classOf[ChannelStateChanged]) // we create and announce a channel between C and F; we use push_msat to ensure both nodes have an output in the commit tx - connect(nodes("C"), nodes("F"), 5000000 sat, 500000000 msat) + connect(nodes("C"), nodes("F"), 5000000 sat, 500000000 msat, channelType) awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds) awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds) // we exchange channel_ready and move to the NORMAL state after 8 blocks @@ -147,17 +147,17 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { // now that we have the channel id, we retrieve channels default final addresses sender.send(nodes("C").register, Register.Forward(sender.ref.toTyped[Any], htlc.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender))) val dataC = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data - assert(dataC.commitments.latest.commitmentFormat == commitmentFormat) + assert(dataC.commitments.latest.commitmentFormat == channelType.commitmentFormat) val Right(finalAddressC) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("C").wallet.getReceivePublicKeyScript(renew = false)) sender.send(nodes("F").register, Register.Forward(sender.ref.toTyped[Any], htlc.channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender))) val dataF = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data - assert(dataF.commitments.latest.commitmentFormat == commitmentFormat) + assert(dataF.commitments.latest.commitmentFormat == channelType.commitmentFormat) val Right(finalAddressF) = addressFromPublicKeyScript(Block.RegtestGenesisBlock.hash, nodes("F").wallet.getReceivePublicKeyScript(renew = false)) ForceCloseFixture(sender, paymentSender, stateListenerC, stateListenerF, paymentId, htlc, preimage, minerAddress, finalAddressC, finalAddressF) } - def testDownstreamFulfillLocalCommit(commitmentFormat: Transactions.CommitmentFormat): Unit = { - val forceCloseFixture = prepareForceCloseCF(commitmentFormat) + def testDownstreamFulfillLocalCommit(): Unit = { + val forceCloseFixture = prepareForceCloseCF() import forceCloseFixture._ // we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test @@ -180,10 +180,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { // we then generate enough blocks so that nodes get their main delayed output generateBlocks(25, Some(minerAddress)) val expectedTxCountC = 1 // C should have 1 recv transaction: its main output - val expectedTxCountF = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 2 // F should have 2 recv transactions: the redeemed htlc and its main output - case Transactions.DefaultCommitmentFormat => 1 // F's main output uses static_remotekey - } + val expectedTxCountF = 2 // F should have 2 recv transactions: the redeemed htlc and its main output awaitCond({ val receivedByC = listReceivedByAddress(finalAddressC, sender) val receivedByF = listReceivedByAddress(finalAddressF) @@ -197,8 +194,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { awaitAnnouncements(1) } - def testDownstreamFulfillRemoteCommit(commitmentFormat: Transactions.CommitmentFormat): Unit = { - val forceCloseFixture = prepareForceCloseCF(commitmentFormat) + def testDownstreamFulfillRemoteCommit(): Unit = { + val forceCloseFixture = prepareForceCloseCF() import forceCloseFixture._ // we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test @@ -220,10 +217,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { paymentSender.expectMsgType[PaymentSent](max = 60 seconds) // we then generate enough blocks so that F gets its htlc-success delayed output generateBlocks(25, Some(minerAddress)) - val expectedTxCountC = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 1 // C should have 1 recv transaction: its main output - case Transactions.DefaultCommitmentFormat => 0 // C's main output uses static_remotekey - } + val expectedTxCountC = 1 // C should have 1 recv transaction: its main output val expectedTxCountF = 2 // F should have 2 recv transactions: the redeemed htlc and its main output awaitCond({ val receivedByC = listReceivedByAddress(finalAddressC, sender) @@ -238,8 +232,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { awaitAnnouncements(1) } - def testDownstreamTimeoutLocalCommit(commitmentFormat: Transactions.CommitmentFormat): Unit = { - val forceCloseFixture = prepareForceCloseCF(commitmentFormat) + def testDownstreamTimeoutLocalCommit(): Unit = { + val forceCloseFixture = prepareForceCloseCF() import forceCloseFixture._ // we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test @@ -274,10 +268,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { // we then generate enough blocks to confirm all delayed transactions generateBlocks(25, Some(minerAddress)) val expectedTxCountC = 2 // C should have 2 recv transactions: its main output and the htlc timeout - val expectedTxCountF = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 1 // F should have 1 recv transaction: its main output - case Transactions.DefaultCommitmentFormat => 0 // F's main output uses static_remotekey - } + val expectedTxCountF = 1 // F should have 1 recv transaction: its main output awaitCond({ val receivedByC = listReceivedByAddress(finalAddressC, sender) val receivedByF = listReceivedByAddress(finalAddressF, sender) @@ -291,8 +282,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { awaitAnnouncements(1) } - def testDownstreamTimeoutRemoteCommit(commitmentFormat: Transactions.CommitmentFormat): Unit = { - val forceCloseFixture = prepareForceCloseCF(commitmentFormat) + def testDownstreamTimeoutRemoteCommit(): Unit = { + val forceCloseFixture = prepareForceCloseCF() import forceCloseFixture._ // we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test @@ -329,10 +320,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { assert(failed.failures.head.asInstanceOf[RemoteFailure].e == DecryptedFailurePacket(nodes("C").nodeParams.nodeId, PermanentChannelFailure())) // we then generate enough blocks to confirm all delayed transactions generateBlocks(25, Some(minerAddress)) - val expectedTxCountC = commitmentFormat match { - case _: AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => 2 // C should have 2 recv transactions: its main output and the htlc timeout - case Transactions.DefaultCommitmentFormat => 1 // C's main output uses static_remotekey - } + val expectedTxCountC = 2 // C should have 2 recv transactions: its main output and the htlc timeout val expectedTxCountF = 1 // F should have 1 recv transaction: its main output awaitCond({ val receivedByC = listReceivedByAddress(finalAddressC, sender) @@ -349,10 +337,10 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { case class RevokedCommitFixture(sender: TestProbe, stateListenerC: TestProbe, revokedCommitTx: Transaction, htlcSuccess: Seq[Transaction], htlcTimeout: Seq[Transaction], finalAddressC: String) - def testRevokedCommit(commitmentFormat: Transactions.CommitmentFormat): RevokedCommitFixture = { + def testRevokedCommit(): RevokedCommitFixture = { val sender = TestProbe() // we create and announce a channel between C and F; we use push_msat to ensure F has a balance - connect(nodes("C"), nodes("F"), 5000000 sat, 300000000 msat) + connect(nodes("C"), nodes("F"), 5000000 sat, 300000000 msat, channelType) generateBlocks(8) awaitAnnouncements(2) // we subscribe to C's channel state transitions @@ -400,16 +388,13 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { forwardHandlerC.forward(buffer.ref) val commitmentsF = sigListener.expectMsgType[ChannelSignatureReceived].commitments sigListener.expectNoMessage(1 second) - assert(commitmentsF.latest.commitmentFormat == commitmentFormat) + assert(commitmentsF.latest.commitmentFormat == channelType.commitmentFormat) // we prepare the revoked transactions F will publish val channelKeysF = nodes("F").nodeParams.channelKeyManager.channelKeys(commitmentsF.channelParams.channelConfig, commitmentsF.localChannelParams.fundingKeyPath) val commitmentKeysF = commitmentsF.latest.localKeys(channelKeysF) val revokedCommitTx = commitmentsF.latest.fullySignedLocalCommitTx(channelKeysF) // in this commitment, both parties should have a main output, there are four pending htlcs and anchor outputs if applicable - commitmentFormat match { - case Transactions.DefaultCommitmentFormat => assert(revokedCommitTx.txOut.size == 6) - case _: Transactions.AnchorOutputsCommitmentFormat | _: SimpleTaprootChannelCommitmentFormat => assert(revokedCommitTx.txOut.size == 8) - } + assert(revokedCommitTx.txOut.size == 8) val outgoingHtlcExpiry = commitmentsF.latest.localCommit.spec.htlcs.collect { case OutgoingHtlc(add) => add.cltvExpiry }.max val htlcTxsF = commitmentsF.latest.htlcTxs(channelKeysF) val htlcTimeoutTxs = htlcTxsF.collect { case (tx: Transactions.UnsignedHtlcTimeoutTx, remoteSig) => (tx, remoteSig) } @@ -452,102 +437,15 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { } -class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { - - test("start eclair nodes") { - instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29840 else 29740), "eclair.api.port" -> (if (useEclairSigner) 28190 else 28090)).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) - instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29841 else 29741), "eclair.api.port" -> (if (useEclairSigner) 28191 else 28091)).asJava).withFallback(withAnchorOutputs).withFallback(commonConfig)) - instantiateEclairNode("F", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29842 else 29742), "eclair.api.port" -> (if (useEclairSigner) 28192 else 28092)).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) - } - - test("connect nodes") { - // A --- C --- F - val eventListener = TestProbe() - nodes("A").system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged]) - nodes("C").system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged]) - - connect(nodes("A"), nodes("C"), 11000000 sat, 0 msat) - // confirm the funding tx - generateBlocks(8) - within(60 seconds) { - var count = 0 - while (count < 2) { - if (eventListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL) count = count + 1 - } - } - awaitAnnouncements(1) - } - - test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (local commit)") { - testDownstreamFulfillLocalCommit(Transactions.DefaultCommitmentFormat) - } - - test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (remote commit)") { - testDownstreamFulfillRemoteCommit(Transactions.DefaultCommitmentFormat) - } - - test("propagate a failure upstream when a downstream htlc times out (local commit)") { - testDownstreamTimeoutLocalCommit(Transactions.DefaultCommitmentFormat) - } - - test("propagate a failure upstream when a downstream htlc times out (remote commit)") { - testDownstreamTimeoutRemoteCommit(Transactions.DefaultCommitmentFormat) - } - - test("punish a node that has published a revoked commit tx") { - val revokedCommitFixture = testRevokedCommit(Transactions.DefaultCommitmentFormat) - import revokedCommitFixture._ - - val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient) - // we retrieve transactions already received so that we don't take them into account when evaluating the outcome of this test - val previouslyReceivedByC = listReceivedByAddress(finalAddressC, sender) - // F publishes the revoked commitment, one HTLC-success, one HTLC-timeout and leaves the other HTLC outputs unclaimed - bitcoinClient.publishTransaction(revokedCommitTx).pipeTo(sender.ref) - sender.expectMsg(revokedCommitTx.txid) - bitcoinClient.publishTransaction(htlcSuccess.head).pipeTo(sender.ref) - sender.expectMsgType[Any] match { - case txid: TxId => assert(txid == htlcSuccess.head.txid) - // 3rd stage txs (txs spending htlc txs) are not tested if C publishes the htlc-penalty transaction before F publishes its htlc-success - case Failure(e: JsonRPCError) => assert(e.error.message == "txn-mempool-conflict") - } - bitcoinClient.publishTransaction(htlcTimeout.head).pipeTo(sender.ref) - sender.expectMsgType[Any] match { - case txid: TxId => assert(txid == htlcTimeout.head.txid) - // 3rd stage txs (txs spending htlc txs) are not tested if C publishes the htlc-penalty transaction before F publishes its htlc-timeout - case Failure(e: JsonRPCError) => assert(e.error.message == "txn-mempool-conflict") - } - // we generate enough blocks for HTLC txs to be confirmed, in case they were successfully published - generateBlocks(8) - // at this point C should have 5 recv transactions: F's main output and all htlc outputs (taken as punishment) - // C's main output uses static_remotekey, so C doesn't need to claim it - awaitCond({ - val receivedByC = listReceivedByAddress(finalAddressC, sender) - (receivedByC diff previouslyReceivedByC).size == 5 - }, max = 30 seconds, interval = 1 second) - // we generate enough blocks for the channel to be deeply confirmed - generateBlocks(12) - // and we wait for C's channel to close - awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == CLOSED, max = 60 seconds) - awaitAnnouncements(1) - } - -} - -class StandardChannelIntegrationWithEclairSignerSpec extends StandardChannelIntegrationSpec { - override def useEclairSigner: Boolean = true -} - abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { - val commitmentFormat: AnchorOutputsCommitmentFormat - - def connectNodes(expectedCommitmentFormat: CommitmentFormat): Unit = { + def connectNodes(): Unit = { // A --- C --- F val eventListener = TestProbe() nodes("A").system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged]) nodes("C").system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged]) - connect(nodes("A"), nodes("C"), 11000000 sat, 0 msat) + connect(nodes("A"), nodes("C"), 11000000 sat, 0 msat, channelType) // confirm the funding tx generateBlocks(8) within(60 seconds) { @@ -556,7 +454,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { val stateEvent = eventListener.expectMsgType[ChannelStateChanged](max = 60 seconds) if (stateEvent.currentState == NORMAL) { assert(stateEvent.commitments_opt.nonEmpty) - assert(stateEvent.commitments_opt.get.latest.commitmentFormat == expectedCommitmentFormat) + assert(stateEvent.commitments_opt.get.latest.commitmentFormat == channelType.commitmentFormat) count = count + 1 } } @@ -564,8 +462,8 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { awaitAnnouncements(1) } - def testOpenPayClose(expectedCommitmentFormat: CommitmentFormat): Unit = { - connect(nodes("C"), nodes("F"), 5000000 sat, 0 msat) + def testOpenPayClose(): Unit = { + connect(nodes("C"), nodes("F"), 5000000 sat, 0 msat, channelType) generateBlocks(8) awaitAnnouncements(2) @@ -577,7 +475,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { sender.send(nodes("F").register, Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_DATA(ActorRef.noSender))) val initialStateDataF = sender.expectMsgType[RES_GET_CHANNEL_DATA[DATA_NORMAL]].data - assert(initialStateDataF.commitments.latest.commitmentFormat == expectedCommitmentFormat) + assert(initialStateDataF.commitments.latest.commitmentFormat == channelType.commitmentFormat) val initialCommitmentIndex = initialStateDataF.commitments.localCommitIndex val toRemoteAddress = { @@ -647,7 +545,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { } def testPunishRevokedCommit(): Unit = { - val revokedCommitFixture = testRevokedCommit(commitmentFormat) + val revokedCommitFixture = testRevokedCommit() import revokedCommitFixture._ val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient) @@ -677,78 +575,38 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { } -class AnchorOutputChannelIntegrationSpec extends AnchorChannelIntegrationSpec { - - override val commitmentFormat: AnchorOutputsCommitmentFormat = Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat - - test("start eclair nodes") { - instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29750, "eclair.api.port" -> 28093).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) - instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29751, "eclair.api.port" -> 28094).asJava).withFallback(withAnchorOutputs).withFallback(commonConfig)) - instantiateEclairNode("F", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29753, "eclair.api.port" -> 28095).asJava).withFallback(withAnchorOutputs).withFallback(commonConfig)) - } - - test("connect nodes") { - connectNodes(DefaultCommitmentFormat) - } - - test("open channel C <-> F, send payments and close (anchor outputs)") { - testOpenPayClose(commitmentFormat) - } - - test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (local commit, anchor outputs)") { - testDownstreamFulfillLocalCommit(commitmentFormat) - } - - test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (remote commit, anchor outputs)") { - testDownstreamFulfillRemoteCommit(commitmentFormat) - } - - test("propagate a failure upstream when a downstream htlc times out (local commit, anchor outputs)") { - testDownstreamTimeoutLocalCommit(commitmentFormat) - } - - test("propagate a failure upstream when a downstream htlc times out (remote commit, anchor outputs)") { - testDownstreamTimeoutRemoteCommit(commitmentFormat) - } - - test("punish a node that has published a revoked commit tx (anchor outputs)") { - testPunishRevokedCommit() - } - -} - class AnchorOutputZeroFeeHtlcTxsChannelIntegrationSpec extends AnchorChannelIntegrationSpec { - override val commitmentFormat: AnchorOutputsCommitmentFormat = Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat + override val channelType: SupportedChannelType = ChannelTypes.AnchorOutputsZeroFeeHtlcTx() test("start eclair nodes") { - instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29760, "eclair.api.port" -> 28096).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) - instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29761, "eclair.api.port" -> 28097).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) - instantiateEclairNode("F", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> 29763, "eclair.api.port" -> 28098).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) + instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29840 else 29740), "eclair.api.port" -> (if (useEclairSigner) 28190 else 28090)).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) + instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29841 else 29741), "eclair.api.port" -> (if (useEclairSigner) 28191 else 28091)).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) + instantiateEclairNode("F", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F", "eclair.channel.expiry-delta-blocks" -> 40, "eclair.channel.fulfill-safety-before-timeout-blocks" -> 12, "eclair.server.port" -> (if (useEclairSigner) 29842 else 29742), "eclair.api.port" -> (if (useEclairSigner) 28192 else 28092)).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) } test("connect nodes") { - connectNodes(DefaultCommitmentFormat) + connectNodes() } test("open channel C <-> F, send payments and close (anchor outputs zero fee htlc txs)") { - testOpenPayClose(commitmentFormat) + testOpenPayClose() } test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (local commit, anchor outputs zero fee htlc txs)") { - testDownstreamFulfillLocalCommit(commitmentFormat) + testDownstreamFulfillLocalCommit() } test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (remote commit, anchor outputs zero fee htlc txs)") { - testDownstreamFulfillRemoteCommit(commitmentFormat) + testDownstreamFulfillRemoteCommit() } test("propagate a failure upstream when a downstream htlc times out (local commit, anchor outputs zero fee htlc txs)") { - testDownstreamTimeoutLocalCommit(commitmentFormat) + testDownstreamTimeoutLocalCommit() } test("propagate a failure upstream when a downstream htlc times out (remote commit, anchor outputs zero fee htlc txs)") { - testDownstreamTimeoutRemoteCommit(commitmentFormat) + testDownstreamTimeoutRemoteCommit() } test("punish a node that has published a revoked commit tx (anchor outputs)") { @@ -756,3 +614,7 @@ class AnchorOutputZeroFeeHtlcTxsChannelIntegrationSpec extends AnchorChannelInte } } + +class AnchorOutputZeroFeeHtlcTxsChannelWithEclairSignerIntegrationSpec extends AnchorOutputZeroFeeHtlcTxsChannelIntegrationSpec { + override def useEclairSigner: Boolean = true +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index be17b9736b..d9bdd08397 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -22,6 +22,7 @@ import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.scalacompat.Satoshi import fr.acinq.eclair.Features._ import fr.acinq.eclair.blockchain.bitcoind.BitcoindService +import fr.acinq.eclair.channel.SupportedChannelType import fr.acinq.eclair.io.Peer.OpenChannelResponse import fr.acinq.eclair.io.{Peer, PeerConnection} import fr.acinq.eclair.payment.relay.Relayer.RelayFees @@ -90,8 +91,7 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit "eclair.auto-reconnect" -> false, "eclair.multi-part-payment-expiry" -> "20 seconds", "eclair.channel.channel-update.balance-thresholds" -> Nil.asJava, - "eclair.channel.channel-update.min-time-between-updates" -> java.time.Duration.ZERO, - "eclair.channel.accept-incoming-static-remote-key-channels" -> true).asJava).withFallback(ConfigFactory.load()) + "eclair.channel.channel-update.min-time-between-updates" -> java.time.Duration.ZERO).asJava).withFallback(ConfigFactory.load()) private val commonFeatures = ConfigFactory.parseMap(Map( s"eclair.features.${DataLossProtect.rfcName}" -> "optional", @@ -104,27 +104,18 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit s"eclair.features.${ShutdownAnySegwit.rfcName}" -> "optional", s"eclair.features.${ChannelType.rfcName}" -> "optional", s"eclair.features.${RouteBlinding.rfcName}" -> "optional", + s"eclair.features.${StaticRemoteKey.rfcName}" -> "mandatory", // We keep dual-funding disabled in tests, unless explicitly requested, as most of the network doesn't support it yet. s"eclair.features.${DualFunding.rfcName}" -> "disabled", ).asJava) - val withStaticRemoteKey = commonFeatures.withFallback(ConfigFactory.parseMap(Map( - s"eclair.features.${StaticRemoteKey.rfcName}" -> "mandatory", - s"eclair.features.${AnchorOutputs.rfcName}" -> "disabled", - s"eclair.features.${AnchorOutputsZeroFeeHtlcTx.rfcName}" -> "disabled", - ).asJava)) - - val withAnchorOutputs = ConfigFactory.parseMap(Map( - s"eclair.features.${AnchorOutputs.rfcName}" -> "optional" - ).asJava).withFallback(withStaticRemoteKey) - val withAnchorOutputsZeroFeeHtlcTxs = ConfigFactory.parseMap(Map( s"eclair.features.${AnchorOutputsZeroFeeHtlcTx.rfcName}" -> "optional" - ).asJava).withFallback(withStaticRemoteKey) + ).asJava).withFallback(commonFeatures) val withDualFunding = ConfigFactory.parseMap(Map( s"eclair.features.${DualFunding.rfcName}" -> "optional" - ).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs) + ).asJava).withFallback(commonFeatures) implicit val formats: Formats = DefaultFormats @@ -174,13 +165,13 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit sender.expectMsgType[PeerConnection.ConnectionResult.HasConnection](10 seconds) } - def connect(node1: Kit, node2: Kit, fundingAmount: Satoshi, pushMsat: MilliSatoshi): OpenChannelResponse.Created = { + def connect(node1: Kit, node2: Kit, fundingAmount: Satoshi, pushMsat: MilliSatoshi, channelType: SupportedChannelType): OpenChannelResponse.Created = { val sender = TestProbe() connect(node1, node2) sender.send(node1.switchboard, Peer.OpenChannel( remoteNodeId = node2.nodeParams.nodeId, fundingAmount = fundingAmount, - channelType_opt = None, + channelType_opt = Some(channelType), pushAmount_opt = Some(pushMsat), fundingTxFeerate_opt = None, fundingTxFeeBudget_opt = None, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala index 53fb435af5..21dfc1d61c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala @@ -23,12 +23,12 @@ import akka.testkit.TestProbe import akka.util.Timeout import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.Transaction -import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} import fr.acinq.eclair.TestUtils.waitEventStreamSynced import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{Watch, WatchFundingConfirmed} import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient -import fr.acinq.eclair.channel.{CMD_CLOSE, RES_SUCCESS, Register} +import fr.acinq.eclair.channel.{CMD_CLOSE, ChannelTypes, RES_SUCCESS, Register} import fr.acinq.eclair.io.Switchboard import fr.acinq.eclair.message.OnionMessages import fr.acinq.eclair.message.OnionMessages.{IntermediateNode, Recipient, buildRoute} @@ -36,7 +36,7 @@ import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.OnionMessagePayloadTlv.ReplyPath import fr.acinq.eclair.wire.protocol.TlvCodecs.genericTlv import fr.acinq.eclair.wire.protocol.{GenericTlv, NodeAnnouncement} -import fr.acinq.eclair.{EclairImpl, EncodedNodeId, Features, MilliSatoshi, SendOnionMessageResponse, UInt64, randomBytes, randomKey} +import fr.acinq.eclair.{EclairImpl, EncodedNodeId, Features, MilliSatoshiLong, SendOnionMessageResponse, UInt64, randomBytes, randomKey} import scodec.bits.{ByteVector, HexStringSyntax} import scala.concurrent.ExecutionContext.Implicits.global @@ -231,21 +231,20 @@ class MessageIntegrationSpec extends IntegrationSpec { eventListener.expectNoMessage() } - // TODO: fails... test("open channels") { val probe = TestProbe() // We connect A -> B -> C - connect(nodes("B"), nodes("A"), Satoshi(100_000), MilliSatoshi(0)) - connect(nodes("B"), nodes("C"), Satoshi(100_000), MilliSatoshi(0)) + connect(nodes("B"), nodes("A"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("B"), nodes("C"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) // We connect A -> E -> C - connect(nodes("E"), nodes("A"), Satoshi(100_000), MilliSatoshi(0)) - connect(nodes("E"), nodes("C"), Satoshi(100_000), MilliSatoshi(0)) + connect(nodes("E"), nodes("A"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("E"), nodes("C"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) // We connect A -> F -> C - connect(nodes("F"), nodes("A"), Satoshi(100_000), MilliSatoshi(0)) - connect(nodes("F"), nodes("C"), Satoshi(100_000), MilliSatoshi(0)) + connect(nodes("F"), nodes("A"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("F"), nodes("C"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) // we make sure all channels have set up their WatchConfirmed for the funding tx awaitCond({ @@ -265,9 +264,9 @@ class MessageIntegrationSpec extends IntegrationSpec { }, max = 20 seconds, interval = 500 millis) // We also connect A -> D, B -> D, C -> D - connect(nodes("D"), nodes("A"), Satoshi(100_000), MilliSatoshi(0)) - connect(nodes("D"), nodes("B"), Satoshi(100_000), MilliSatoshi(0)) - connect(nodes("D"), nodes("C"), Satoshi(100_000), MilliSatoshi(0)) + connect(nodes("D"), nodes("A"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("D"), nodes("B"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("D"), nodes("C"), 100_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) // we make sure all channels have set up their WatchConfirmed for the funding tx awaitCond({ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 6ff332ba06..5dd30389b1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -64,10 +64,10 @@ import scala.jdk.CollectionConverters._ class PaymentIntegrationSpec extends IntegrationSpec { test("start eclair nodes") { - instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 130, "eclair.server.port" -> 29730, "eclair.api.port" -> 28080, "eclair.channel.channel-flags.announce-channel" -> false).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) // A's channels are private - instantiateEclairNode("B", ConfigFactory.parseMap(Map("eclair.node-alias" -> "B", "eclair.channel.expiry-delta-blocks" -> 131, "eclair.server.port" -> 29731, "eclair.api.port" -> 28081, "eclair.trampoline-payments-enable" -> true, "eclair.onion-messages.relay-policy" -> "relay-all").asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) + instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.channel.expiry-delta-blocks" -> 130, "eclair.server.port" -> 29730, "eclair.api.port" -> 28080, "eclair.channel.channel-flags.announce-channel" -> false).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) // A's channels are private + instantiateEclairNode("B", ConfigFactory.parseMap(Map("eclair.node-alias" -> "B", "eclair.channel.expiry-delta-blocks" -> 131, "eclair.server.port" -> 29731, "eclair.api.port" -> 28081, "eclair.trampoline-payments-enable" -> true, "eclair.onion-messages.relay-policy" -> "relay-all").asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.channel.expiry-delta-blocks" -> 132, "eclair.server.port" -> 29732, "eclair.api.port" -> 28082, "eclair.trampoline-payments-enable" -> true).asJava).withFallback(withDualFunding).withFallback(commonConfig)) - instantiateEclairNode("D", ConfigFactory.parseMap(Map("eclair.node-alias" -> "D", "eclair.channel.expiry-delta-blocks" -> 133, "eclair.server.port" -> 29733, "eclair.api.port" -> 28083, "eclair.trampoline-payments-enable" -> true).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig)) + instantiateEclairNode("D", ConfigFactory.parseMap(Map("eclair.node-alias" -> "D", "eclair.channel.expiry-delta-blocks" -> 133, "eclair.server.port" -> 29733, "eclair.api.port" -> 28083, "eclair.trampoline-payments-enable" -> true).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig)) instantiateEclairNode("E", ConfigFactory.parseMap(Map("eclair.node-alias" -> "E", "eclair.channel.expiry-delta-blocks" -> 134, "eclair.server.port" -> 29734, "eclair.api.port" -> 28084).asJava).withFallback(withDualFunding).withFallback(commonConfig)) instantiateEclairNode("F", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F", "eclair.channel.expiry-delta-blocks" -> 135, "eclair.server.port" -> 29735, "eclair.api.port" -> 28085, "eclair.trampoline-payments-enable" -> true).asJava).withFallback(commonConfig)) instantiateEclairNode("G", ConfigFactory.parseMap(Map("eclair.node-alias" -> "G", "eclair.channel.expiry-delta-blocks" -> 136, "eclair.server.port" -> 29736, "eclair.api.port" -> 28086, "eclair.relay.fees.public-channels.fee-base-msat" -> 1010, "eclair.relay.fees.public-channels.fee-proportional-millionths" -> 102, "eclair.trampoline-payments-enable" -> true).asJava).withFallback(commonConfig)) @@ -86,15 +86,15 @@ class PaymentIntegrationSpec extends IntegrationSpec { val eventListener = TestProbe() nodes.values.foreach(_.system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged])) - connect(nodes("A"), nodes("B"), 11000000 sat, 0 msat) - connect(nodes("B"), nodes("C"), 2000000 sat, 0 msat) - connect(nodes("C"), nodes("D"), 5000000 sat, 0 msat) - connect(nodes("C"), nodes("D"), 5000000 sat, 0 msat) - connect(nodes("C"), nodes("F"), 16000000 sat, 0 msat) - connect(nodes("B"), nodes("E"), 10000000 sat, 0 msat) - connect(nodes("E"), nodes("C"), 10000000 sat, 0 msat) - connect(nodes("B"), nodes("G"), 16000000 sat, 0 msat) - connect(nodes("G"), nodes("C"), 16000000 sat, 0 msat) + connect(nodes("A"), nodes("B"), 11000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("B"), nodes("C"), 2000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("C"), nodes("D"), 5000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("C"), nodes("D"), 5000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("C"), nodes("F"), 16000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("B"), nodes("E"), 10000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("E"), nodes("C"), 10000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("B"), nodes("G"), 16000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) + connect(nodes("G"), nodes("C"), 16000000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) val numberOfChannels = 9 val channelEndpointsCount = 2 * numberOfChannels diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala index 4329f251ae..50958aefa5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala @@ -35,7 +35,6 @@ import java.util.concurrent.Executors import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext, Future} import scala.jdk.CollectionConverters._ -import scala.util.{Success, Try} /** * Created by PM on 12/07/2021. @@ -60,7 +59,7 @@ class PerformanceIntegrationSpec extends IntegrationSpec { val eventListener = TestProbe() nodes.values.foreach(_.system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged])) - connect(nodes("A"), nodes("B"), 100_000_000 sat, 0 msat) + connect(nodes("A"), nodes("B"), 100_000_000 sat, 0 msat, ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) // confirming the funding tx generateBlocks(6) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala index b33bfdca2f..68bcb8d186 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/StartupIntegrationSpec.scala @@ -33,7 +33,7 @@ import scala.jdk.CollectionConverters._ class StartupIntegrationSpec extends IntegrationSpec { private def createConfig(wallet_opt: Option[String], waitForBitcoind: Boolean = false): Config = { - val defaultConfig = ConfigFactory.parseMap(Map("eclair.bitcoind.wait-for-bitcoind-up" -> waitForBitcoind, "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withStaticRemoteKey).withFallback(commonConfig) + val defaultConfig = ConfigFactory.parseMap(Map("eclair.bitcoind.wait-for-bitcoind-up" -> waitForBitcoind, "eclair.server.port" -> TestUtils.availablePort).asJava).withFallback(withAnchorOutputsZeroFeeHtlcTxs).withFallback(commonConfig) wallet_opt match { case Some(wallet) => ConfigFactory.parseMap(Map("eclair.bitcoind.wallet" -> wallet).asJava).withFallback(defaultConfig) case None => defaultConfig.withoutPath("eclair.bitcoind.wallet") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 0256580b75..e49f8cc91a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -183,7 +183,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat sender.expectMsgType[ConnectionResult.Connected] } - def openChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, funding: Satoshi, channelType_opt: Option[SupportedChannelType] = None)(implicit system: ActorSystem): OpenChannelResponse.Created = { + def openChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, funding: Satoshi, channelType_opt: Option[SupportedChannelType] = Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()))(implicit system: ActorSystem): OpenChannelResponse.Created = { val sender = TestProbe("sender") sender.send(node1.switchboard, Peer.OpenChannel(node2.nodeParams.nodeId, funding, channelType_opt, None, None, None, None, None, None)) sender.expectMsgType[OpenChannelResponse.Created] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfActivationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfActivationSpec.scala index c626cc5a4a..8523fd5ee6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfActivationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfActivationSpec.scala @@ -43,7 +43,7 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience { fixture.cleanup() } - private def createChannel(f: FixtureParam, channelType_opt: Option[SupportedChannelType] = None): ByteVector32 = { + private def createChannel(f: FixtureParam, channelType_opt: Option[SupportedChannelType] = Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())): ByteVector32 = { import f._ alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob), confirm = false)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala index 9313a2669a..6b582ec107 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala @@ -36,8 +36,8 @@ import fr.acinq.eclair.io.PeerSpec.{createOpenChannelMessage, createOpenDualFund import fr.acinq.eclair.io.PendingChannelsRateLimiter.AddOrRejectChannel import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec -import fr.acinq.eclair.wire.protocol.{ChannelReestablish, ChannelTlv, Error, IPAddress, LiquidityAds, NodeAddress, OpenChannel, OpenChannelTlv, Shutdown, TlvStream} -import fr.acinq.eclair.{AcceptOpenChannel, BlockHeight, FeatureSupport, Features, InitFeature, InterceptOpenChannelCommand, InterceptOpenChannelPlugin, InterceptOpenChannelReceived, MilliSatoshiLong, RejectOpenChannel, TestConstants, UInt64, UnknownFeature, randomBytes32, randomKey} +import fr.acinq.eclair.wire.protocol.{ChannelReestablish, Error, IPAddress, LiquidityAds, NodeAddress, OpenChannel, Shutdown, TlvStream} +import fr.acinq.eclair.{AcceptOpenChannel, BlockHeight, FeatureSupport, Features, InitFeature, InterceptOpenChannelCommand, InterceptOpenChannelPlugin, InterceptOpenChannelReceived, MilliSatoshiLong, RejectOpenChannel, TestConstants, UnknownFeature, randomBytes32, randomKey} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -47,12 +47,10 @@ import scala.concurrent.duration.DurationInt class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("application")) with FixtureAnyFunSuiteLike { val remoteNodeId: Crypto.PublicKey = randomKey().publicKey - val openChannel: OpenChannel = createOpenChannelMessage() + val openChannel: OpenChannel = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) val remoteAddress: NodeAddress = IPAddress(InetAddress.getLoopbackAddress, 19735) - val defaultFeatures: Features[InitFeature] = Features(Map[InitFeature, FeatureSupport](StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional)) - val staticRemoteKeyFeatures: Features[InitFeature] = Features(Map[InitFeature, FeatureSupport](StaticRemoteKey -> Optional)) + val defaultFeatures: Features[InitFeature] = Features(Map[InitFeature, FeatureSupport](StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, ChannelType -> Optional)) - val acceptStaticRemoteKeyChannelsTag = "accept static_remote_key channels" val noPlugin = "no plugin" override def withFixture(test: OneArgTest): Outcome = { @@ -69,7 +67,6 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory } val nodeParams = TestConstants.Alice.nodeParams .modify(_.pluginParams).usingIf(!test.tags.contains(noPlugin))(_ :+ plugin) - .modify(_.channelConf).usingIf(test.tags.contains(acceptStaticRemoteKeyChannelsTag))(_.copy(acceptIncomingStaticRemoteKeyChannels = true)) val eventListener = TestProbe[ChannelAborted]() system.eventStream ! EventStream.Subscribe(eventListener.ref) @@ -123,9 +120,8 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory val features = defaultFeatures.add(Features.SplicePrototype, FeatureSupport.Optional).add(Features.OnTheFlyFunding, FeatureSupport.Optional) val requestFunding = LiquidityAds.RequestFunding(250_000 sat, TestConstants.defaultLiquidityRates.fundingRates.head, LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(randomBytes32() :: Nil)) - val open = createOpenDualFundedChannelMessage().copy( + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Some(requestFunding)).copy( channelFlags = ChannelFlags(nonInitiatorPaysCommitFees = true, announceChannel = false), - tlvStream = TlvStream(ChannelTlv.RequestFundingTlv(requestFunding)) ) val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Right(open), features, features, peerConnection.ref, remoteAddress) openChannelInterceptor ! openChannelNonInitiator @@ -150,7 +146,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory val probe = TestProbe[Any]() val requestFunding = LiquidityAds.RequestFunding(150_000 sat, LiquidityAds.FundingRate(0 sat, 200_000 sat, 400, 100, 0 sat, 0 sat), LiquidityAds.PaymentDetails.FromChannelBalance) - val openChannelInitiator = OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, 300_000 sat, None, None, None, None, Some(requestFunding), None, None), defaultFeatures, defaultFeatures) + val openChannelInitiator = OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, 300_000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, Some(requestFunding), None, None), defaultFeatures, defaultFeatures) openChannelInterceptor ! openChannelInitiator val result = peer.expectMessageType[SpawnChannelInitiator] assert(result.cmd == openChannelInitiator.open) @@ -169,15 +165,6 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory assert(peer.expectMessageType[SpawnChannelNonInitiator].addFunding_opt.isEmpty) } - test("reject open channel request if channel type is obsolete") { f => - import f._ - - val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress) - openChannelInterceptor ! openChannelNonInitiator - assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rejecting incoming channel: anchor outputs must be used for new channels")) - eventListener.expectMessageType[ChannelAborted] - } - test("reject open channel request if rejected by the plugin") { f => import f._ @@ -220,33 +207,13 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory eventListener.expectMessageType[ChannelAborted] } - test("reject static_remote_key open channel request") { f => - import f._ - - val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), staticRemoteKeyFeatures, staticRemoteKeyFeatures, peerConnection.ref, remoteAddress) - openChannelInterceptor ! openChannelNonInitiator - assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rejecting incoming static_remote_key channel: anchor outputs must be used for new channels")) - eventListener.expectMessageType[ChannelAborted] - } - - test("accept static_remote_key open channel request if node is configured to accept them", Tag(acceptStaticRemoteKeyChannelsTag)) { f => - import f._ - - val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), staticRemoteKeyFeatures, staticRemoteKeyFeatures, peerConnection.ref, remoteAddress) - openChannelInterceptor ! openChannelNonInitiator - pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel - pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), Some(LiquidityAds.AddFunding(50_000 sat, None))) - peer.expectMessageType[SpawnChannelNonInitiator] - } - test("reject on-the-fly channel if another channel exists", Tag(noPlugin)) { f => import f._ val features = defaultFeatures.add(Features.SplicePrototype, FeatureSupport.Optional).add(Features.OnTheFlyFunding, FeatureSupport.Optional) val requestFunding = LiquidityAds.RequestFunding(250_000 sat, TestConstants.defaultLiquidityRates.fundingRates.head, LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(randomBytes32() :: Nil)) - val open = createOpenDualFundedChannelMessage().copy( + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Some(requestFunding)).copy( channelFlags = ChannelFlags(nonInitiatorPaysCommitFees = true, announceChannel = false), - tlvStream = TlvStream(ChannelTlv.RequestFundingTlv(requestFunding)) ) val currentChannel = Seq( Peer.ChannelInfo(TestProbe().ref, NORMAL, ChannelCodecsSpec.normal), @@ -283,41 +250,34 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory test("don't spawn a channel if we don't support their channel type") { f => import f._ - // They only support anchor outputs and we don't. - { - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs()))) - openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), Features.empty, Features.empty, peerConnection.ref, remoteAddress) - peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=anchor_outputs, expected channel_type=standard"), peerConnection.ref.toClassic)) - eventListener.expectMessageType[ChannelAborted] - } - // They only support anchor outputs with zero fee htlc txs and we don't. + // We don't support non-anchor static_remotekey channels. { - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()))) - openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), Features.empty, Features.empty, peerConnection.ref, remoteAddress) - peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=anchor_outputs_zero_fee_htlc_tx, expected channel_type=standard"), peerConnection.ref.toClassic)) + val open = createOpenChannelMessage(ChannelTypes.UnsupportedChannelType(Features(StaticRemoteKey -> Mandatory))) + openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress) + peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=0x1000"), peerConnection.ref.toClassic)) eventListener.expectMessageType[ChannelAborted] } - // They want to use a channel type that doesn't exist in the spec. + // They only support unsafe anchor outputs and we don't. { - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(UnsupportedChannelType(Features(AnchorOutputs -> Optional))))) - openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), Features.empty, Features.empty, peerConnection.ref, remoteAddress) - peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=0x200000, expected channel_type=standard"), peerConnection.ref.toClassic)) + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputs()) + openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures, defaultFeatures.add(AnchorOutputs, Optional), peerConnection.ref, remoteAddress) + peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=anchor_outputs"), peerConnection.ref.toClassic)) eventListener.expectMessageType[ChannelAborted] } // They want to use a channel type we don't support yet. { - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(UnsupportedChannelType(Features(Map(StaticRemoteKey -> Mandatory), unknown = Set(UnknownFeature(22))))))) - openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), Features.empty, Features.empty, peerConnection.ref, remoteAddress) - peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=0x401000, expected channel_type=standard"), peerConnection.ref.toClassic)) + val open = createOpenChannelMessage(UnsupportedChannelType(Features(activated = Map.empty, unknown = Set(UnknownFeature(120))))) + openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress) + peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "invalid channel_type=0x01000000000000000000000000000000"), peerConnection.ref.toClassic)) eventListener.expectMessageType[ChannelAborted] } } - test("don't spawn a channel if channel type is missing with the feature bit set") { f => + test("don't spawn a channel if channel type is missing") { f => import f._ - val open = createOpenChannelMessage() - openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures.add(ChannelType, Optional), defaultFeatures.add(ChannelType, Optional), peerConnection.ref, remoteAddress) + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()).copy(tlvStream = TlvStream.empty) + openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress) peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "option_channel_type was negotiated but channel_type is missing"), peerConnection.ref.toClassic)) eventListener.expectMessageType[ChannelAborted] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index df207e6924..af697c84db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -37,7 +37,6 @@ import fr.acinq.eclair.testutils.FixtureSpec import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec import fr.acinq.eclair.wire.protocol import fr.acinq.eclair.wire.protocol._ -import org.scalatest.Inside.inside import org.scalatest.{Tag, TestData} import scodec.bits.{ByteVector, HexStringSyntax} @@ -70,9 +69,7 @@ class PeerSpec extends FixtureSpec { import com.softwaremill.quicklens._ val aliceParams = TestConstants.Alice.nodeParams - .modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.StaticRemoteKey))(Features(StaticRemoteKey -> Optional)) - .modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.AnchorOutputs))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional)) - .modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional)) + .modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional)) .modify(_.features).setToIf(testData.tags.contains(ChannelStateTestsTags.DualFunding))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional)) .modify(_.channelConf.maxHtlcValueInFlightMsat).setToIf(testData.tags.contains("max-htlc-value-in-flight-percent"))(100_000_000 msat) .modify(_.channelConf.maxHtlcValueInFlightPercent).setToIf(testData.tags.contains("max-htlc-value-in-flight-percent"))(25) @@ -339,29 +336,26 @@ class PeerSpec extends FixtureSpec { connect(remoteNodeId, peer, peerConnection, switchboard, channels = Set(ChannelCodecsSpec.normal)) // We regularly update our internal feerates. - val bitcoinCoreFeerates = FeeratesPerKw(FeeratePerKw(253 sat), FeeratePerKw(1000 sat), FeeratePerKw(2500 sat), FeeratePerKw(5000 sat), FeeratePerKw(10_000 sat)) + val bitcoinCoreFeerates = FeeratesPerKw(FeeratePerKw(253 sat), FeeratePerKw(400 sat), FeeratePerKw(500 sat), FeeratePerKw(1000 sat), FeeratePerKw(1500 sat)) nodeParams.setBitcoinCoreFeerates(bitcoinCoreFeerates) peer ! CurrentFeerates.BitcoinCore(bitcoinCoreFeerates) peerConnection.expectMsg(RecommendedFeerates( chainHash = Block.RegtestGenesisBlock.hash, - fundingFeerate = FeeratePerKw(2_500 sat), - commitmentFeerate = FeeratePerKw(5000 sat), + fundingFeerate = FeeratePerKw(500 sat), + commitmentFeerate = FeeratePerKw(1000 sat), tlvStream = TlvStream[RecommendedFeeratesTlv]( - RecommendedFeeratesTlv.FundingFeerateRange(FeeratePerKw(1250 sat), FeeratePerKw(20_000 sat)), - RecommendedFeeratesTlv.CommitmentFeerateRange(FeeratePerKw(2500 sat), FeeratePerKw(40_000 sat)) + RecommendedFeeratesTlv.FundingFeerateRange(FeeratePerKw(253 sat), FeeratePerKw(4_000 sat)), + RecommendedFeeratesTlv.CommitmentFeerateRange(FeeratePerKw(500 sat), FeeratePerKw(8_000 sat)) ) )) } - test("reject funding requests if funding feerate is too low for on-the-fly funding", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("reject funding requests if funding feerate is too low for on-the-fly funding", Tag(ChannelStateTestsTags.DualFunding)) { f => import f._ connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))) val requestFunds = LiquidityAds.RequestFunding(50_000 sat, LiquidityAds.FundingRate(10_000 sat, 100_000 sat, 0, 0, 0 sat, 0 sat), LiquidityAds.PaymentDetails.FromFutureHtlc(randomBytes32() :: Nil)) - val open = { - val open = createOpenDualFundedChannelMessage() - open.copy(fundingFeerate = FeeratePerKw(5000 sat), tlvStream = TlvStream(ChannelTlv.RequestFundingTlv(requestFunds))) - } + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputs(), Some(requestFunds)).copy(fundingFeerate = FeeratePerKw(5000 sat)) // Our current and previous feerates are higher than what will be proposed. Seq(FeeratePerKw(7500 sat), FeeratePerKw(6000 sat)).foreach(feerate => { @@ -406,7 +400,7 @@ class PeerSpec extends FixtureSpec { channel.expectMsg(splice) } - test("don't spawn a channel with duplicate temporary channel id", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("don't spawn a channel with duplicate temporary channel id") { f => import f._ val probe = TestProbe() @@ -414,7 +408,7 @@ class PeerSpec extends FixtureSpec { connect(remoteNodeId, peer, peerConnection, switchboard) assert(peer.stateData.channels.isEmpty) - val open = createOpenChannelMessage() + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) eventually { assert(peer.stateData.channels.nonEmpty) @@ -445,7 +439,7 @@ class PeerSpec extends FixtureSpec { assert(peer.stateData.channels.isEmpty) val requestFunds = LiquidityAds.RequestFunding(50_000 sat, LiquidityAds.FundingRate(10_000 sat, 100_000 sat, 0, 0, 0 sat, 0 sat), LiquidityAds.PaymentDetails.FromChannelBalance) - val open = Peer.OpenChannel(remoteNodeId, 10000 sat, None, None, None, None, Some(requestFunds), None, None) + val open = Peer.OpenChannel(remoteNodeId, 10000 sat, Some(ChannelTypes.AnchorOutputs()), None, None, None, Some(requestFunds), None, None) peerConnection.send(peer, open) assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].requestFunding_opt.contains(requestFunds)) } @@ -454,7 +448,7 @@ class PeerSpec extends FixtureSpec { import f._ connect(remoteNodeId, peer, peerConnection, switchboard) - val open = createOpenDualFundedChannelMessage() + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) peerConnection.expectMsg(Error(open.temporaryChannelId, "dual funding is not supported")) } @@ -466,7 +460,7 @@ class PeerSpec extends FixtureSpec { // Both peers support option_dual_fund, so it is automatically used. connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))) assert(peer.stateData.channels.isEmpty) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 25000 sat, None, None, None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 25000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, None, None, None)) assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].dualFunded) } @@ -476,7 +470,7 @@ class PeerSpec extends FixtureSpec { // Both peers support option_dual_fund, so it is automatically used. connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))) assert(peer.stateData.channels.isEmpty) - val open = createOpenDualFundedChannelMessage() + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) eventually { assert(peer.stateData.channels.nonEmpty) @@ -485,13 +479,13 @@ class PeerSpec extends FixtureSpec { channel.expectMsg(open) } - test("use their channel type when spawning a channel", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("use their channel type when spawning a channel", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ - // We both support option_anchors_zero_fee_htlc_tx they want to open an anchor_outputs channel. - connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(AnchorOutputsZeroFeeHtlcTx -> Optional))) + // We both support option_anchors_zero_fee_htlc_tx, but they want to open an anchor_outputs channel. + connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional))) assert(peer.stateData.channels.isEmpty) - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs()))) + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputs()) peerConnection.send(peer, open) eventually { assert(peer.stateData.channels.nonEmpty) @@ -502,48 +496,44 @@ class PeerSpec extends FixtureSpec { channel.expectMsg(open) } - test("use requested channel type when spawning a channel", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => + test("use requested channel type when spawning a channel", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val probe = TestProbe() - connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Mandatory))) + connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional))) assert(peer.stateData.channels.isEmpty) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None)) - assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].channelType == ChannelTypes.StaticRemoteKey()) - - // We can create channels that don't use the features we have enabled. - probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.Standard()), None, None, None, None, None, None)) - assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].channelType == ChannelTypes.Standard()) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, None, None, None)) + assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) - // We can create channels that use features that we haven't enabled. + // We can create channels that use a different channel type. probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.AnchorOutputs()), None, None, None, None, None, None)) assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].channelType == ChannelTypes.AnchorOutputs()) } - test("handle OpenChannelInterceptor accepting an open channel message", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("handle OpenChannelInterceptor accepting an open channel message") { f => import f._ connect(remoteNodeId, peer, peerConnection, switchboard) - val open = createOpenChannelMessage() + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) assert(channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR].temporaryChannelId == open.temporaryChannelId) channel.expectMsg(open) } - test("handle OpenChannelInterceptor rejecting an open channel message", Tag("rate_limited"), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("handle OpenChannelInterceptor rejecting an open channel message", Tag("rate_limited")) { f => import f._ connect(remoteNodeId, peer, peerConnection, switchboard) - val open = createOpenChannelMessage() + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) peerConnection.expectMsg(Error(open.temporaryChannelId, "rate limit reached")) assert(peer.stateData.channels.isEmpty) } - test("use correct on-chain fee rates when spawning a channel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => + test("use correct on-chain fee rates when spawning a channel (anchor outputs phoenix)", Tag(ChannelStateTestsTags.AnchorOutputsPhoenix)) { f => import f._ val probe = TestProbe() @@ -552,7 +542,7 @@ class PeerSpec extends FixtureSpec { // We ensure the current network feerate is higher than the default anchor output feerate. nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.AnchorOutputs()), None, None, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.channelType == ChannelTypes.AnchorOutputs()) assert(!init.dualFunded) @@ -561,7 +551,7 @@ class PeerSpec extends FixtureSpec { assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeeratesForFundingClosing)) } - test("use correct on-chain fee rates when spawning a channel (anchor outputs zero fee htlc)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("use correct on-chain fee rates when spawning a channel (anchor outputs zero fee htlc)") { f => import f._ val probe = TestProbe() @@ -570,7 +560,7 @@ class PeerSpec extends FixtureSpec { // We ensure the current network feerate is higher than the default anchor output feerate. nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) assert(!init.dualFunded) @@ -579,20 +569,7 @@ class PeerSpec extends FixtureSpec { assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeeratesForFundingClosing)) } - test("use correct final script if option_static_remotekey is negotiated", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => - import f._ - - val probe = TestProbe() - connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Mandatory))) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 24000 sat, None, None, None, None, None, None, None)) - val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] - assert(init.channelType == ChannelTypes.StaticRemoteKey()) - assert(!init.dualFunded) - assert(init.localChannelParams.walletStaticPaymentBasepoint.isDefined) - assert(init.localChannelParams.upfrontShutdownScript_opt.isEmpty) - } - - test("compute max-htlc-value-in-flight based on funding amount", Tag("max-htlc-value-in-flight-percent"), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => + test("compute max-htlc-value-in-flight based on funding amount", Tag("max-htlc-value-in-flight-percent")) { f => import f._ val probe = TestProbe() @@ -601,24 +578,24 @@ class PeerSpec extends FixtureSpec { assert(peer.underlyingActor.nodeParams.channelConf.maxHtlcValueInFlightMsat == 100_000_000.msat) { - probe.send(peer, Peer.OpenChannel(remoteNodeId, 200_000 sat, None, None, None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 200_000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.proposedCommitParams.localMaxHtlcValueInFlight == UInt64(50_000_000)) // max-htlc-value-in-flight-percent } { - probe.send(peer, Peer.OpenChannel(remoteNodeId, 500_000 sat, None, None, None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 500_000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), None, None, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.proposedCommitParams.localMaxHtlcValueInFlight == UInt64(100_000_000)) // max-htlc-value-in-flight-msat } { - val open = createOpenChannelMessage().copy(fundingSatoshis = 200_000 sat) + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()).copy(fundingSatoshis = 200_000 sat) peerConnection.send(peer, open) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR] assert(init.proposedCommitParams.localMaxHtlcValueInFlight == UInt64(50_000_000)) // max-htlc-value-in-flight-percent channel.expectMsg(open) } { - val open = createOpenChannelMessage().copy(fundingSatoshis = 500_000 sat) + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()).copy(fundingSatoshis = 500_000 sat) peerConnection.send(peer, open) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR] assert(init.proposedCommitParams.localMaxHtlcValueInFlight == UInt64(100_000_000)) // max-htlc-value-in-flight-msat @@ -639,7 +616,7 @@ class PeerSpec extends FixtureSpec { val probe = TestProbe() connect(remoteNodeId, peer, peerConnection, switchboard) - probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, Some(100 msat), None, None, None, None, None)) + probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), Some(100 msat), None, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.replyTo == probe.ref.toTyped[OpenChannelResponse]) } @@ -710,10 +687,10 @@ class PeerSpec extends FixtureSpec { test("abort channel open request if peer reconnects before channel is accepted") { f => import f._ val probe = TestProbe() - val open = createOpenChannelMessage() + val open = createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) system.eventStream.subscribe(probe.ref, classOf[ChannelAborted]) connect(remoteNodeId, peer, peerConnection, switchboard) - peer ! SpawnChannelNonInitiator(Left(open), ChannelConfig.standard, ChannelTypes.Standard(), None, ChannelCodecsSpec.localChannelParams, ActorRef.noSender) + peer ! SpawnChannelNonInitiator(Left(open), ChannelConfig.standard, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), None, ChannelCodecsSpec.localChannelParams, ActorRef.noSender) val channelAborted = probe.expectMsgType[ChannelAborted] assert(channelAborted.remoteNodeId == remoteNodeId) assert(channelAborted.channelId == open.temporaryChannelId) @@ -723,7 +700,7 @@ class PeerSpec extends FixtureSpec { import f._ connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))) - val open = createOpenDualFundedChannelMessage() + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) peerConnection.send(peer, open) assert(channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR].dualFunded) channel.expectMsg(open) @@ -755,7 +732,7 @@ class PeerSpec extends FixtureSpec { val paymentHash = randomBytes32() connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, DualFunding -> Optional))) val requestFunds = LiquidityAds.RequestFunding(50_000 sat, LiquidityAds.FundingRate(10_000 sat, 100_000 sat, 0, 0, 0 sat, 0 sat), LiquidityAds.PaymentDetails.FromFutureHtlc(paymentHash :: Nil)) - val open = inside(createOpenDualFundedChannelMessage()) { msg => msg.copy(tlvStream = TlvStream(ChannelTlv.RequestFundingTlv(requestFunds))) } + val open = createOpenDualFundedChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), Some(requestFunds)) peerConnection.send(peer, open) peerConnection.expectMsg(CancelOnTheFlyFunding(open.temporaryChannelId, paymentHash :: Nil, "payments paid with future HTLCs are currently disabled")) channel.expectNoMessage(100 millis) @@ -842,7 +819,7 @@ class PeerSpec extends FixtureSpec { assert(nodeParams.db.peers.getPeer(remoteNodeId).isEmpty) // Our peer wants to open a channel to us, but we disconnect before we have a confirmed channel. - peer ! SpawnChannelNonInitiator(Left(createOpenChannelMessage()), ChannelConfig.standard, ChannelTypes.Standard(), None, ChannelCodecsSpec.localChannelParams, peerConnection.ref) + peer ! SpawnChannelNonInitiator(Left(createOpenChannelMessage(ChannelTypes.AnchorOutputsZeroFeeHtlcTx())), ChannelConfig.standard, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), None, ChannelCodecsSpec.localChannelParams, peerConnection.ref) peer ! Peer.ConnectionDown(peerConnection.ref) probe.send(peer, Peer.GetPeerInfo(Some(probe.ref.toTyped))) assert(probe.expectMsgType[Peer.PeerInfo].state == Peer.DISCONNECTED) @@ -889,12 +866,16 @@ object PeerSpec { (mockServer, mockServer.getLocalAddress.asInstanceOf[InetSocketAddress]) } - def createOpenChannelMessage(openTlv: TlvStream[OpenChannelTlv] = TlvStream.empty): protocol.OpenChannel = { - protocol.OpenChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), 250_000 sat, 0 msat, 483 sat, UInt64(100), 1000 sat, 1 msat, TestConstants.feeratePerKw, CltvExpiryDelta(144), 10, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false), openTlv) + def createOpenChannelMessage(channelType: ChannelType): protocol.OpenChannel = { + protocol.OpenChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), 250_000 sat, 0 msat, 483 sat, UInt64(100), 1000 sat, 1 msat, TestConstants.feeratePerKw, CltvExpiryDelta(144), 10, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false), TlvStream(ChannelTlv.ChannelTypeTlv(channelType))) } - def createOpenDualFundedChannelMessage(): protocol.OpenDualFundedChannel = { - protocol.OpenDualFundedChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), TestConstants.feeratePerKw, TestConstants.anchorOutputsFeeratePerKw, 250_000 sat, 483 sat, UInt64(100), 1 msat, CltvExpiryDelta(144), 10, 0, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false)) + def createOpenDualFundedChannelMessage(channelType: ChannelType, requestFunding_opt: Option[LiquidityAds.RequestFunding] = None): protocol.OpenDualFundedChannel = { + val tlvs = TlvStream(Set( + Some(ChannelTlv.ChannelTypeTlv(channelType)), + requestFunding_opt.map(ChannelTlv.RequestFundingTlv(_)), + ).flatten[OpenDualFundedChannelTlv]) + protocol.OpenDualFundedChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), TestConstants.feeratePerKw, TestConstants.anchorOutputsFeeratePerKw, 250_000 sat, 483 sat, UInt64(100), 1 msat, CltvExpiryDelta(144), 10, 0, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false), tlvs) } } \ No newline at end of file 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 5ee4ebec18..0582de64ce 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 @@ -121,11 +121,11 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val probe = TestProbe()(system) val dummyPublicKey = PrivateKey(hex"0101010101010101010101010101010101010101010101010101010101010101").publicKey val dummyBytes32 = ByteVector32(hex"0202020202020202020202020202020202020202020202020202020202020202") - val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(1000 sat), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val localChannelParams = LocalChannelParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), Some(1000 sat), isChannelOpener = true, paysCommitTxFees = true, None, Features.empty) val localCommitParams = CommitParams(546 sat, 1 msat, UInt64(Long.MaxValue), 50, CltvExpiryDelta(144)) val remoteChannelParams = RemoteChannelParams(dummyPublicKey, Some(1000 sat), dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) val remoteCommitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 50, CltvExpiryDelta(144)) - val commitmentInput = Transactions.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) + val commitmentInput = Transactions.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), TxId(dummyBytes32), IndividualSignature(ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 50_000_000 msat, 100_000_000 msat), TxId(dummyBytes32), dummyPublicKey) val channelInfo = RES_GET_CHANNEL_INFO( @@ -137,7 +137,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat Commitments( ChannelParams(dummyBytes32, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, ChannelFlags(announceChannel = true)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, 0, commitmentInput.outPoint, 150_000 sat, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), + List(Commitment(0, 0, commitmentInput.outPoint, 150_000 sat, dummyPublicKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), inactive = Nil, Right(dummyPublicKey), ShaChain.init, @@ -196,7 +196,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat | "fundingAmount": 150000, | "localFunding": { "status":"unconfirmed" }, | "remoteFunding": { "status":"locked" }, - | "commitmentFormat": "legacy", + | "commitmentFormat": "anchor_outputs", | "localCommitParams": { | "dustLimit": 546, | "htlcMinimum": 1, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index f216d20b59..cfac6c68d5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -35,7 +35,7 @@ import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFromUpdate} import fr.acinq.eclair.router.BlindedRouteCreation import fr.acinq.eclair.router.Router.{NodeHop, Route} -import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat +import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} import fr.acinq.eclair.wire.protocol._ @@ -745,7 +745,7 @@ object PaymentPacketSpec { def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat, channelFeatures: ChannelFeatures = ChannelFeatures(), announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { val channelReserve = testCapacity * 0.01 - val localChannelParams = LocalChannelParams(null, null, Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) + val localChannelParams = LocalChannelParams(null, null, Some(channelReserve), isChannelOpener = true, paysCommitTxFees = true, None, Features.empty) val remoteChannelParams = RemoteChannelParams(randomKey().publicKey, Some(channelReserve), null, null, null, null, null, None) val commitParams = CommitParams(546 sat, 1 msat, UInt64.MaxValue, 30, CltvExpiryDelta(720)) val fundingTx = Transaction(2, Nil, Seq(TxOut(testCapacity, Nil)), 0) @@ -758,7 +758,7 @@ object PaymentPacketSpec { case None => LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None) } val channelFlags = ChannelFlags(announceChannel = announcement_opt.nonEmpty) - val commitmentFormat = DefaultCommitmentFormat + val commitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat new Commitments( ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(localChanges, remoteChanges, 0, 0), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala index 00569b880e..da6f822b49 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala @@ -78,8 +78,8 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { assert(peerInfo.state == Peer.CONNECTED) } - def openChannel(fundingAmount: Satoshi): ByteVector32 = { - peer ! Peer.OpenChannel(remoteNodeId, fundingAmount, None, None, None, None, None, None, None) + def openChannel(fundingAmount: Satoshi, channelType: SupportedChannelType = ChannelTypes.AnchorOutputsZeroFeeHtlcTx()): ByteVector32 = { + peer ! Peer.OpenChannel(remoteNodeId, fundingAmount, Some(channelType), None, None, None, None, None, None) val temporaryChannelId = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].temporaryChannelId val channelId = randomBytes32() peer ! ChannelIdAssigned(channel.ref, remoteNodeId, temporaryChannelId, channelId) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 0f4776454c..51dee1ef93 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -203,7 +203,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu internalTest(f) } - test("private local channel (zeroconf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("private local channel (zeroconf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => internalTest(f) } @@ -211,7 +211,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu internalTest(f) } - test("public local channel (zeroconf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("public local channel (zeroconf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.ZeroConf)) { f => internalTest(f) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index d4d278029e..8ed41eead4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -1098,7 +1098,7 @@ class RouterSpec extends BaseRouterSpec { assert(!data.channels.contains(scid2)) assert(data.scid2PrivateChannels.get(aliases2.localAlias.toLong).contains(commitments2.channelId)) val chan2 = data.privateChannels(commitments2.channelId) - assert(chan2.capacity == 99_000.sat) // for private channels, we use the balance to compute the channel's capacity + assert(chan2.capacity == 98_340.sat) // for private channels, we use the balance to compute the channel's capacity assert((chan2.update_1_opt.toSet ++ chan2.update_2_opt.toSet) == Set(update2)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala index 8def34e8fc..b74813d416 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala @@ -71,7 +71,6 @@ class CommitmentSpecSpec extends AnyFunSuite { test("compute htlc tx feerate based on commitment format") { val spec = CommitmentSpec(htlcs = Set(), commitTxFeerate = FeeratePerKw(2500 sat), toLocal = (5000 * 1000) msat, toRemote = (2500 * 1000) msat) - assert(spec.htlcTxFeerate(Transactions.DefaultCommitmentFormat) == FeeratePerKw(2500 sat)) assert(spec.htlcTxFeerate(Transactions.UnsafeLegacyAnchorOutputsCommitmentFormat) == FeeratePerKw(2500 sat)) assert(spec.htlcTxFeerate(Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) == FeeratePerKw(0 sat)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 1931aa8b1b..9ce0d162b3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -40,10 +40,8 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val commitmentFormat: CommitmentFormat = if (channelFeatures.contains(Features.AnchorOutputsZeroFeeHtlcTx)) { ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - } else if (channelFeatures.contains(Features.AnchorOutputs)) { - UnsafeLegacyAnchorOutputsCommitmentFormat } else { - DefaultCommitmentFormat + UnsafeLegacyAnchorOutputsCommitmentFormat } val tests = { @@ -135,7 +133,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { ) // Keys used by the remote node to spend outputs of our local commitment. val remoteCommitmentKeys = RemoteCommitmentKeys( - ourPaymentKey = Right(Remote.payment_privkey), + ourPaymentKey = Remote.payment_privkey, theirDelayedPaymentPublicKey = Local.delayed_payment_privkey.publicKey, ourPaymentBasePoint = Remote.payment_basepoint, ourHtlcKey = Remote.htlc_privkey, @@ -444,20 +442,6 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { } -class DefaultCommitmentTestVectorSpec extends TestVectorsSpec { - // @formatter:off - override def filename: String = "/bolt3-tx-test-vectors-default-commitment-format.txt" - override def channelFeatures: Set[ChannelTypeFeature] = Set.empty - // @formatter:on -} - -class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec { - // @formatter:off - override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt" - override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey) - // @formatter:on -} - class AnchorOutputsTestVectorSpec extends TestVectorsSpec { // @formatter:off override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-format.txt" 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 af9f165a69..513c5485be 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 @@ -18,7 +18,7 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.SigHash._ import fr.acinq.bitcoin.scalacompat.Crypto._ -import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, Crypto, MilliBtc, MilliBtcDouble, Musig2, OP_2, OP_CHECKMULTISIG, OP_PUSHDATA, OP_RETURN, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxId, TxIn, TxOut, millibtc2satoshi} +import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, Crypto, MilliBtc, MilliBtcDouble, Musig2, OP_2, OP_CHECKMULTISIG, OP_PUSHDATA, OP_RETURN, OutPoint, Satoshi, SatoshiLong, Script, Transaction, TxIn, TxOut} import fr.acinq.bitcoin.{ScriptFlags, SigVersion} import fr.acinq.eclair.TestUtils.randomTxId import fr.acinq.eclair._ @@ -26,7 +26,6 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelSpendSignature import fr.acinq.eclair.crypto.keymanager.{LocalCommitmentKeys, RemoteCommitmentKeys} import fr.acinq.eclair.reputation.Reputation -import fr.acinq.eclair.transactions.CommitmentOutput.OutHtlc import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions.AnchorOutputsCommitmentFormat.anchorAmount import fr.acinq.eclair.transactions.Transactions._ @@ -35,7 +34,6 @@ import grizzled.slf4j.Logging import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ -import scala.io.Source import scala.util.Random /** @@ -63,9 +61,9 @@ class TransactionsSpec extends AnyFunSuite with Logging { ) // Keys used by the remote node to spend outputs of our local commitment. private val remoteKeys = RemoteCommitmentKeys( - ourPaymentKey = Right(remotePaymentPriv), + ourPaymentKey = remotePaymentPriv, theirDelayedPaymentPublicKey = localDelayedPaymentPriv.publicKey, - ourPaymentBasePoint = localPaymentBasePoint, + ourPaymentBasePoint = remotePaymentPriv.publicKey, ourHtlcKey = remoteHtlcPriv, theirHtlcPublicKey = localHtlcPriv.publicKey, revocationPublicKey = localRevocationPriv.publicKey, @@ -116,7 +114,6 @@ class TransactionsSpec extends AnyFunSuite with Logging { } test("compute fees") { - // see BOLT #3 specs val htlcs = Set[DirectedHtlc]( OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 5000000 msat, ByteVector32.Zeroes, CltvExpiry(552), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)), OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 1000000 msat, ByteVector32.Zeroes, CltvExpiry(553), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)), @@ -124,8 +121,8 @@ class TransactionsSpec extends AnyFunSuite with Logging { IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 800000 msat, ByteVector32.Zeroes, CltvExpiry(551), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) ) val spec = CommitmentSpec(htlcs, FeeratePerKw(5000 sat), toLocal = 0 msat, toRemote = 0 msat) - val fee = commitTxFeeMsat(546 sat, spec, DefaultCommitmentFormat) - assert(fee == 5340000.msat) + val fee = commitTxFeeMsat(546 sat, spec, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) + assert(fee == 9_060_000.msat) } test("pre-compute wallet input and output weight") { @@ -153,50 +150,13 @@ class TransactionsSpec extends AnyFunSuite with Logging { private def checkExpectedWeight(actual: Int, expected: Int, commitmentFormat: CommitmentFormat): Unit = { commitmentFormat match { case _: SimpleTaprootChannelCommitmentFormat => assert(actual == expected) - case _: AnchorOutputsCommitmentFormat | DefaultCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => // ECDSA signatures are der-encoded, which creates some variability in signature size compared to the baseline. assert(actual <= expected + 2) assert(actual >= expected - 2) } } - test("generate valid commitment with some outputs that don't materialize (default commitment format)") { - val spec = CommitmentSpec(htlcs = Set.empty, commitTxFeerate = feeratePerKw, toLocal = 400.millibtc.toMilliSatoshi, toRemote = 300.millibtc.toMilliSatoshi) - val commitFee = commitTxTotalCost(localDustLimit, spec, DefaultCommitmentFormat) - val belowDust = (localDustLimit * 0.9).toMilliSatoshi - val belowDustWithFee = (localDustLimit + commitFee * 0.9).toMilliSatoshi - - { - val toRemoteFundeeBelowDust = spec.copy(toRemote = belowDust) - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = true, localDustLimit, toLocalDelay, toRemoteFundeeBelowDust, DefaultCommitmentFormat) - assert(outputs.forall(_.isInstanceOf[CommitmentOutput.ToLocal])) - assert(outputs.head.txOut.amount.toMilliSatoshi == toRemoteFundeeBelowDust.toLocal - commitFee) - } - { - val toLocalFunderBelowDust = spec.copy(toLocal = belowDustWithFee) - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = true, localDustLimit, toLocalDelay, toLocalFunderBelowDust, DefaultCommitmentFormat) - assert(outputs.forall(_.isInstanceOf[CommitmentOutput.ToRemote])) - assert(outputs.head.txOut.amount.toMilliSatoshi == toLocalFunderBelowDust.toRemote) - } - { - val toRemoteFunderBelowDust = spec.copy(toRemote = belowDustWithFee) - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = false, localDustLimit, toLocalDelay, toRemoteFunderBelowDust, DefaultCommitmentFormat) - assert(outputs.forall(_.isInstanceOf[CommitmentOutput.ToLocal])) - assert(outputs.head.txOut.amount.toMilliSatoshi == toRemoteFunderBelowDust.toLocal) - } - { - val toLocalFundeeBelowDust = spec.copy(toLocal = belowDust) - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = false, localDustLimit, toLocalDelay, toLocalFundeeBelowDust, DefaultCommitmentFormat) - assert(outputs.forall(_.isInstanceOf[CommitmentOutput.ToRemote])) - assert(outputs.head.txOut.amount.toMilliSatoshi == toLocalFundeeBelowDust.toRemote - commitFee) - } - { - val allBelowDust = spec.copy(toLocal = belowDust, toRemote = belowDust) - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = true, localDustLimit, toLocalDelay, allBelowDust, DefaultCommitmentFormat) - assert(outputs.isEmpty) - } - } - test("generate valid commitment with some outputs that don't materialize (anchor outputs)") { val spec = CommitmentSpec(htlcs = Set.empty, commitTxFeerate = feeratePerKw, toLocal = 400.millibtc.toMilliSatoshi, toRemote = 300.millibtc.toMilliSatoshi) val commitFeeAndAnchorCost = commitTxTotalCost(localDustLimit, spec, UnsafeLegacyAnchorOutputsCommitmentFormat) @@ -305,7 +265,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { tx <- txInfo.aggregateSigs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localPartialSig, remotePartialSig) } yield tx commitTx - case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + case _: AnchorOutputsCommitmentFormat => val localSig = txInfo.sign(localFundingPriv, remoteFundingPriv.publicKey) val remoteSig = txInfo.sign(remoteFundingPriv, localFundingPriv.publicKey) assert(txInfo.checkRemoteSig(localFundingPubkey = localFundingPriv.publicKey, remoteFundingPriv.publicKey, remoteSig)) @@ -347,18 +307,13 @@ class TransactionsSpec extends AnyFunSuite with Logging { checkExpectedWeight(claimMainOutputTx.weight(), commitmentFormat.toLocalDelayedWeight, commitmentFormat) Transaction.correctlySpends(claimMainOutputTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - if (commitmentFormat != DefaultCommitmentFormat) { - // remote cannot spend main output with default commitment format - val Left(failure) = ClaimP2WPKHOutputTx.createUnsignedTx(remoteKeys, commitTx, localDustLimit, finalPubKeyScript, feeratePerKw, commitmentFormat) - assert(failure == OutputNotFound) - } - if (commitmentFormat != DefaultCommitmentFormat) { + { // remote spends main delayed output val Right(claimRemoteDelayedOutputTx) = ClaimRemoteDelayedOutputTx.createUnsignedTx(remoteKeys, commitTx, localDustLimit, finalPubKeyScript, feeratePerKw, commitmentFormat).map(_.sign()) checkExpectedWeight(claimRemoteDelayedOutputTx.weight(), commitmentFormat.toRemoteWeight, commitmentFormat) Transaction.correctlySpends(claimRemoteDelayedOutputTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - if (commitmentFormat != DefaultCommitmentFormat) { + { // local spends local anchor with additional wallet inputs val walletAmount = 50_000 sat val walletInputs = WalletInputs(Seq( @@ -381,7 +336,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val anchorTxWeight = claimAnchorTx.tx.copy(txIn = claimAnchorTx.tx.txIn.take(1), txOut = Nil).weight() checkExpectedWeight(anchorTxWeight, claimAnchorTx.expectedWeight, commitmentFormat) } - if (commitmentFormat != DefaultCommitmentFormat) { + { // remote spends remote anchor val Right(claimAnchorOutputTx) = ClaimRemoteAnchorTx.createUnsignedTx(remoteFundingPriv, remoteKeys, commitTx, commitmentFormat) assert(!claimAnchorOutputTx.validate(Map.empty)) @@ -402,10 +357,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val signedTx = htlcTimeoutTx.addRemoteSig(localKeys, remoteSig).sign() Transaction.correctlySpends(signedTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) // local detects when remote doesn't use the right sighash flags - val invalidSighash = commitmentFormat match { - case DefaultCommitmentFormat => Seq(SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) - case _ => Seq(SIGHASH_ALL, SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) - } + val invalidSighash = Seq(SIGHASH_ALL, SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) for (sighash <- invalidSighash) { val invalidRemoteSig = htlcTimeoutTx.localSigWithInvalidSighash(remoteKeys, sighash) assert(!htlcTimeoutTx.checkRemoteSig(localKeys, invalidRemoteSig)) @@ -430,10 +382,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { Transaction.correctlySpends(signedTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) assert(htlcSuccessTx.checkRemoteSig(localKeys, remoteSig)) // local detects when remote doesn't use the right sighash flags - val invalidSighash = commitmentFormat match { - case DefaultCommitmentFormat => Seq(SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) - case _ => Seq(SIGHASH_ALL, SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) - } + val invalidSighash = Seq(SIGHASH_ALL, SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) for (sighash <- invalidSighash) { val invalidRemoteSig = htlcSuccessTx.localSigWithInvalidSighash(remoteKeys, sighash) assert(!htlcSuccessTx.checkRemoteSig(localKeys, invalidRemoteSig)) @@ -535,10 +484,6 @@ class TransactionsSpec extends AnyFunSuite with Logging { } } - test("generate valid commitment and htlc transactions (default commitment format)") { - testCommitAndHtlcTxs(DefaultCommitmentFormat) - } - test("generate valid commitment and htlc transactions (legacy anchor outputs)") { testCommitAndHtlcTxs(UnsafeLegacyAnchorOutputsCommitmentFormat) } @@ -572,83 +517,6 @@ class TransactionsSpec extends AnyFunSuite with Logging { assert(Taproot.musig2Aggregate(pubkey2, pubkey1) == Musig2.aggregateKeys(Seq(pubkey1, pubkey2))) } - test("sort the htlc outputs using BIP69 and cltv expiry") { - val localFundingPriv = PrivateKey(hex"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1") - val remoteFundingPriv = PrivateKey(hex"a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2") - val localRevocationPriv = PrivateKey(hex"a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3") - val localPaymentPriv = PrivateKey(hex"a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4") - val localDelayedPaymentPriv = PrivateKey(hex"a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") - val remotePaymentPriv = PrivateKey(hex"a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6") - val localHtlcPriv = PrivateKey(hex"a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7") - val remoteHtlcPriv = PrivateKey(hex"a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8") - val localKeys = LocalCommitmentKeys( - ourDelayedPaymentKey = localDelayedPaymentPriv, - theirPaymentPublicKey = remotePaymentPriv.publicKey, - ourPaymentBasePoint = localPaymentBasePoint, - ourHtlcKey = localHtlcPriv, - theirHtlcPublicKey = remoteHtlcPriv.publicKey, - revocationPublicKey = localRevocationPriv.publicKey, - ) - val commitInput = makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) - - // htlc1 and htlc2 are two regular incoming HTLCs with different amounts. - // htlc2 and htlc3 have the same amounts and should be sorted according to their scriptPubKey - // htlc4 is identical to htlc3 and htlc5 has same payment_hash/amount but different CLTV - val paymentPreimage1 = ByteVector32(hex"1111111111111111111111111111111111111111111111111111111111111111") - val paymentPreimage2 = ByteVector32(hex"2222222222222222222222222222222222222222222222222222222222222222") - val paymentPreimage3 = ByteVector32(hex"3333333333333333333333333333333333333333333333333333333333333333") - val htlc1 = UpdateAddHtlc(randomBytes32(), 1, millibtc2satoshi(MilliBtc(100)).toMilliSatoshi, sha256(paymentPreimage1), CltvExpiry(300), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - val htlc2 = UpdateAddHtlc(randomBytes32(), 2, millibtc2satoshi(MilliBtc(200)).toMilliSatoshi, sha256(paymentPreimage2), CltvExpiry(300), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - val htlc3 = UpdateAddHtlc(randomBytes32(), 3, millibtc2satoshi(MilliBtc(200)).toMilliSatoshi, sha256(paymentPreimage3), CltvExpiry(300), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - val htlc4 = UpdateAddHtlc(randomBytes32(), 4, millibtc2satoshi(MilliBtc(200)).toMilliSatoshi, sha256(paymentPreimage3), CltvExpiry(300), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - val htlc5 = UpdateAddHtlc(randomBytes32(), 5, millibtc2satoshi(MilliBtc(200)).toMilliSatoshi, sha256(paymentPreimage3), CltvExpiry(301), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None) - - val spec = CommitmentSpec( - htlcs = Set( - OutgoingHtlc(htlc1), - OutgoingHtlc(htlc2), - OutgoingHtlc(htlc3), - OutgoingHtlc(htlc4), - OutgoingHtlc(htlc5) - ), - commitTxFeerate = feeratePerKw, - toLocal = millibtc2satoshi(MilliBtc(400)).toMilliSatoshi, - toRemote = millibtc2satoshi(MilliBtc(300)).toMilliSatoshi) - - val commitTxNumber = 0x404142434446L - val (commitTx, outputs, htlcTxs) = { - val outputs = makeCommitTxOutputs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localKeys.publicKeys, payCommitTxFees = true, localDustLimit, toLocalDelay, spec, DefaultCommitmentFormat) - val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) - val localSig = txInfo.sign(localFundingPriv, remoteFundingPriv.publicKey) - val remoteSig = txInfo.sign(remoteFundingPriv, localFundingPriv.publicKey) - val commitTx = txInfo.aggregateSigs(localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) - commitTx.correctlySpends(Map(commitInput.outPoint -> commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - val htlcTxs = makeHtlcTxs(commitTx, outputs, DefaultCommitmentFormat) - (commitTx, outputs, htlcTxs) - } - - // htlc1 comes before htlc2 because of the smaller amount (BIP69) - // htlc2 and htlc3 have the same amount but htlc2 comes first because its pubKeyScript is lexicographically smaller than htlc3's - // htlc5 comes after htlc3 and htlc4 because of the higher CLTV - val htlcOut1 :: htlcOut2 :: htlcOut3 :: htlcOut4 :: htlcOut5 :: _ = commitTx.txOut.toList - assert(htlcOut1.amount == 10000000.sat) - for (htlcOut <- Seq(htlcOut2, htlcOut3, htlcOut4, htlcOut5)) { - assert(htlcOut.amount == 20000000.sat) - } - - // htlc3 and htlc4 are completely identical, their relative order can't be enforced. - assert(htlcTxs.length == 5) - htlcTxs.foreach(tx => assert(tx.isInstanceOf[UnsignedHtlcTimeoutTx])) - val htlcIds = htlcTxs.sortBy(_.input.outPoint.index).map(_.htlcId) - assert(htlcIds == Seq(1, 2, 3, 4, 5) || htlcIds == Seq(1, 2, 4, 3, 5)) - - assert(htlcOut2.publicKeyScript.toHex < htlcOut3.publicKeyScript.toHex) - assert(outputs.collectFirst { case o: OutHtlc if o.htlc.add == htlc2 => o.txOut.publicKeyScript }.contains(htlcOut2.publicKeyScript)) - assert(outputs.collectFirst { case o: OutHtlc if o.htlc.add == htlc3 => o.txOut.publicKeyScript }.contains(htlcOut3.publicKeyScript)) - assert(outputs.collectFirst { case o: OutHtlc if o.htlc.add == htlc4 => o.txOut.publicKeyScript }.contains(htlcOut4.publicKeyScript)) - assert(outputs.collectFirst { case o: OutHtlc if o.htlc.add == htlc5 => o.txOut.publicKeyScript }.contains(htlcOut5.publicKeyScript)) - } - test("find our output in closing tx") { val commitInput = makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val localPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) @@ -788,49 +656,4 @@ class TransactionsSpec extends AnyFunSuite with Logging { } } - test("BOLT 3 fee tests") { - val dustLimit = 546 sat - val bolt3 = { - val fetch = Source.fromURL("https://raw.githubusercontent.com/lightning/bolts/master/03-transactions.md") - // We'll use character '$' to separate tests: - val formatted = fetch.mkString.replace(" name:", "$ name:") - fetch.close() - formatted - } - - def htlcIn(amount: Satoshi): DirectedHtlc = IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) - - def htlcOut(amount: Satoshi): DirectedHtlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None)) - - case class TestVector(name: String, spec: CommitmentSpec, expectedFee: Satoshi) - - // this regex extract params from a given test - val testRegex = ("""name: (.*)\n""" + - """.*to_local_msat: ([0-9]+)\n""" + - """.*to_remote_msat: ([0-9]+)\n""" + - """.*feerate_per_kw: ([0-9]+)\n""" + - """.*base commitment transaction fee = ([0-9]+)\n""" + - """[^$]+""").r - // this regex extracts htlc direction and amounts - val htlcRegex = """.*HTLC #[0-9] ([a-z]+) amount ([0-9]+).*""".r - val tests = testRegex.findAllIn(bolt3).map(s => { - val testRegex(name, to_local_msat, to_remote_msat, feerate_per_kw, fee) = s - val htlcs = htlcRegex.findAllIn(s).map(l => { - val htlcRegex(direction, amount) = l - direction match { - case "offered" => htlcOut(Satoshi(amount.toLong)) - case "received" => htlcIn(Satoshi(amount.toLong)) - } - }).toSet - TestVector(name, CommitmentSpec(htlcs, FeeratePerKw(feerate_per_kw.toLong.sat), MilliSatoshi(to_local_msat.toLong), MilliSatoshi(to_remote_msat.toLong)), Satoshi(fee.toLong)) - }).toSeq - - assert(tests.size == 15, "there were 15 tests at e042c615efb5139a0bfdca0c6391c3c13df70418") // simple non-reg to make sure we are not missing tests - tests.foreach(test => { - logger.info(s"running BOLT 3 test: '${test.name}'") - val fee = commitTxTotalCost(dustLimit, test.spec, DefaultCommitmentFormat) - assert(fee == test.expectedFee) - }) - } - } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index f55059fb5f..8630a2f4e1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, LocalChannelKeyMana import fr.acinq.eclair.json.JsonSerializers import fr.acinq.eclair.reputation.Reputation import fr.acinq.eclair.router.Announcements -import fr.acinq.eclair.transactions.Transactions.DefaultCommitmentFormat +import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.internal.channel.ChannelCodecs._ import fr.acinq.eclair.wire.protocol.{CommonCodecs, UpdateAddHtlc} @@ -124,7 +124,6 @@ object ChannelCodecsSpec { fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)), initialRequestedChannelReserve_opt = Some(10000 sat), upfrontShutdownScript_opt = None, - walletStaticPaymentBasepoint = None, isChannelOpener = true, paysCommitTxFees = true, initFeatures = Features.empty) @@ -178,7 +177,7 @@ object ChannelCodecsSpec { val commitments = Commitments( ChannelParams(channelId, ChannelConfig.standard, ChannelFeatures(), localChannelParams, remoteChannelParams, channelFlags), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32, remoteNextHtlcId = 4), - Seq(Commitment(fundingTxIndex, 0, OutPoint(fundingTx.txid, 0), fundingAmount, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, DefaultCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), + Seq(Commitment(fundingTxIndex, 0, OutPoint(fundingTx.txid, 0), fundingAmount, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, localCommitParams, localCommit, remoteCommitParams, remoteCommit, None)), remoteNextCommitInfo = Right(randomKey().publicKey), remotePerCommitmentSecrets = ShaChain.init, originChannels = origins) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 4c08afbf01..589ca88ece 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -281,27 +281,27 @@ class LightningMessageCodecsSpec extends AnyFunSuite { // non-empty upfront_shutdown_script + unknown odd tlv records defaultEncoded ++ hex"0002 1234 0303010203" -> defaultOpen.copy(tlvStream = TlvStream(Set[OpenChannelTlv](ChannelTlv.UpfrontShutdownScriptTlv(hex"1234")), Set(GenericTlv(UInt64(3), hex"010203")))), // empty upfront_shutdown_script + default channel type - defaultEncoded ++ hex"0000" ++ hex"0100" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard()))), + defaultEncoded ++ hex"0000" ++ hex"0100" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features.empty)))), // empty upfront_shutdown_script + unsupported channel type defaultEncoded ++ hex"0000" ++ hex"0103501000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory, Features.AnchorOutputsZeroFeeHtlcTx -> FeatureSupport.Mandatory))))), // empty upfront_shutdown_script + channel type - defaultEncoded ++ hex"0000" ++ hex"01021000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey()))), + defaultEncoded ++ hex"0000" ++ hex"01021000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory))))), // non-empty upfront_shutdown_script + channel type defaultEncoded ++ hex"0004 01abcdef" ++ hex"0103101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs()))), defaultEncoded ++ hex"0002 abcd" ++ hex"0103401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"abcd"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()))), // empty upfront_shutdown_script + channel type (scid-alias) - defaultEncoded ++ hex"0000" ++ hex"0106 400000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard(scidAlias = true)))), - defaultEncoded ++ hex"0000" ++ hex"0106 400000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey(scidAlias = true)))), + defaultEncoded ++ hex"0000" ++ hex"0106 400000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.ScidAlias -> FeatureSupport.Mandatory))))), + defaultEncoded ++ hex"0000" ++ hex"0106 400000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.ScidAlias -> FeatureSupport.Mandatory))))), defaultEncoded ++ hex"0000" ++ hex"0106 400000101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs(scidAlias = true)))), defaultEncoded ++ hex"0000" ++ hex"0106 400000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true)))), // empty upfront_shutdown_script + channel type (zeroconf) - defaultEncoded ++ hex"0000" ++ hex"0107 04000000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard(zeroConf = true)))), - defaultEncoded ++ hex"0000" ++ hex"0107 04000000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey(zeroConf = true)))), + defaultEncoded ++ hex"0000" ++ hex"0107 04000000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.ZeroConf -> FeatureSupport.Mandatory))))), + defaultEncoded ++ hex"0000" ++ hex"0107 04000000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.ZeroConf -> FeatureSupport.Mandatory))))), defaultEncoded ++ hex"0000" ++ hex"0107 04000000101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs(zeroConf = true)))), defaultEncoded ++ hex"0000" ++ hex"0107 04000000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(zeroConf = true)))), // empty upfront_shutdown_script + channel type (scid-alias + zeroconf) - defaultEncoded ++ hex"0000" ++ hex"0107 04400000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard(scidAlias = true, zeroConf = true)))), - defaultEncoded ++ hex"0000" ++ hex"0107 04400000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey(scidAlias = true, zeroConf = true)))), + defaultEncoded ++ hex"0000" ++ hex"0107 04400000000000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.ScidAlias -> FeatureSupport.Mandatory, Features.ZeroConf -> FeatureSupport.Mandatory))))), + defaultEncoded ++ hex"0000" ++ hex"0107 04400000001000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.ScidAlias -> FeatureSupport.Mandatory, Features.ZeroConf -> FeatureSupport.Mandatory))))), defaultEncoded ++ hex"0000" ++ hex"0107 04400000101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs(scidAlias = true, zeroConf = true)))), defaultEncoded ++ hex"0000" ++ hex"0107 04400000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)))), // taproot channel type + nonce @@ -377,10 +377,10 @@ class LightningMessageCodecsSpec extends AnyFunSuite { val testCases = Map( defaultEncoded -> defaultAccept, // legacy encoding without upfront_shutdown_script defaultEncoded ++ hex"0000" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))), // empty upfront_shutdown_script - defaultEncoded ++ hex"0000" ++ hex"0100" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard()))), // empty upfront_shutdown_script with channel type + defaultEncoded ++ hex"0000" ++ hex"0100" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features.empty)))), // empty upfront_shutdown_script with channel type defaultEncoded ++ hex"0004 01abcdef" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"))), // non-empty upfront_shutdown_script defaultEncoded ++ hex"0000" ++ hex"01 17 1000000000000000000000000000000000000000000000" ++ hex"04 42 2062534ccb3be5a8997843f3b6bc530a94cbc60eceb538674ceedd62d8be07f2dfa5df6acf3ded7444268d56925bb2c33afe71a55f4fa88f3985451a681415930f6b" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.SimpleTaprootChannelsStaging()), ChannelTlv.NextLocalNonceTlv(nonce))), // empty upfront_shutdown_script with taproot channel type and nonce - defaultEncoded ++ hex"0004 01abcdef" ++ hex"01021000" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey()))), // non-empty upfront_shutdown_script with channel type + defaultEncoded ++ hex"0004 01abcdef" ++ hex"0103401000" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()))), // non-empty upfront_shutdown_script with channel type defaultEncoded ++ hex"0000 0302002a 050102" -> defaultAccept.copy(tlvStream = TlvStream(Set[AcceptChannelTlv](ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty)), Set(GenericTlv(UInt64(3), hex"002a"), GenericTlv(UInt64(5), hex"02")))), // empty upfront_shutdown_script + unknown odd tlv records defaultEncoded ++ hex"0002 1234 0303010203" -> defaultAccept.copy(tlvStream = TlvStream(Set[AcceptChannelTlv](ChannelTlv.UpfrontShutdownScriptTlv(hex"1234")), Set(GenericTlv(UInt64(3), hex"010203")))), // non-empty upfront_shutdown_script + unknown odd tlv records defaultEncoded ++ hex"0303010203 05020123" -> defaultAccept.copy(tlvStream = TlvStream(Set.empty[AcceptChannelTlv], Set(GenericTlv(UInt64(3), hex"010203"), GenericTlv(UInt64(5), hex"0123")))), // no upfront_shutdown_script + unknown odd tlv records @@ -399,9 +399,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { val defaultEncoded = hex"0041 0100000000000000000000000000000000000000000000000000000000000000 000000000000c350 00000000000001d9 0000000005f5e100 0000000000000001 00000006 0090 0032 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766 02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337 03462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b 0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7 03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a 02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f" val testCases = Seq( defaultAccept -> defaultEncoded, - defaultAccept.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.StaticRemoteKey()))) -> (defaultEncoded ++ hex"01021000"), defaultAccept.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), PushAmountTlv(1729 msat))) -> (defaultEncoded ++ hex"0103401000 fe470000070206c1"), - defaultAccept.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.StaticRemoteKey()), RequireConfirmedInputsTlv())) -> (defaultEncoded ++ hex"01021000 0200"), defaultAccept.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), FeeCreditUsedTlv(0 msat))) -> (defaultEncoded ++ hex"0103401000 fda05200"), defaultAccept.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()), FeeCreditUsedTlv(1729 msat))) -> (defaultEncoded ++ hex"0103401000 fda0520206c1"), ) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index 8b8453daed..d598dc2f97 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.scalacompat.{Satoshi, Script} import fr.acinq.eclair.api.Service import fr.acinq.eclair.api.directives.EclairDirectives import fr.acinq.eclair.api.serde.FormParamExtractors._ -import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte, FeeratePerKw} +import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerByte} import fr.acinq.eclair.channel.{ChannelTypes, ClosingFeerates} import fr.acinq.eclair.{MilliSatoshi, Paginated} import scodec.bits.ByteVector @@ -32,26 +32,15 @@ trait Channel { import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization} - val supportedChannelTypes = Set( - ChannelTypes.Standard(), - ChannelTypes.Standard(zeroConf = true), - ChannelTypes.Standard(scidAlias = true), - ChannelTypes.Standard(scidAlias = true, zeroConf = true), - ChannelTypes.StaticRemoteKey(), - ChannelTypes.StaticRemoteKey(zeroConf = true), - ChannelTypes.StaticRemoteKey(scidAlias = true), - ChannelTypes.StaticRemoteKey(scidAlias = true, zeroConf = true), - ChannelTypes.AnchorOutputs(), - ChannelTypes.AnchorOutputs(zeroConf = true), - ChannelTypes.AnchorOutputs(scidAlias = true), - ChannelTypes.AnchorOutputs(scidAlias = true, zeroConf = true), + private val supportedChannelTypes = Set( ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(zeroConf = true), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), ChannelTypes.SimpleTaprootChannelsStaging(), + ChannelTypes.SimpleTaprootChannelsStaging(zeroConf = true), ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true), - ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true) + ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true), ).map(ct => ct.toString -> ct).toMap // we use the toString method as name in the api val open: Route = postRequest("open") { implicit t => diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index c1bef7753b..691703ca13 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -294,7 +294,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } } - test("'open' channels with bad channelType") { + test("'open' channels with unknown channelType") { val nodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87") val eclair = mock[Eclair] @@ -310,48 +310,6 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } } - test("'open' channels with standard channelType") { - val nodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87") - val channelId = ByteVector32(hex"56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e") - val fundingTxId = TxId.fromValidHex("a86b3f93c1b2ea3f221159869d6f556cae1ba2622cc8c7eb71c7f4f64e0fbca4") - - val eclair = mock[Eclair] - eclair.open(any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(OpenChannelResponse.Created(channelId, fundingTxId, 0 sat)) - val mockService = new MockService(eclair) - - Post("/open", FormData("nodeId" -> nodeId.toString(), "fundingSatoshis" -> "25000", "channelType" -> "standard").toEntity) ~> - addCredentials(BasicHttpCredentials("", mockApi().password)) ~> - addHeader("Content-Type", "application/json") ~> - Route.seal(mockService.route) ~> - check { - assert(handled) - assert(status == OK) - assert(entityAs[String] == "\"created channel 56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e with fundingTxId=a86b3f93c1b2ea3f221159869d6f556cae1ba2622cc8c7eb71c7f4f64e0fbca4 and fees=0 sat\"") - eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.Standard()), None, None, None, None)(any[Timeout]).wasCalled(once) - } - } - - test("'open' channels with static_remotekey channelType") { - val nodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87") - val channelId = ByteVector32(hex"56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e") - val fundingTxId = TxId.fromValidHex("a86b3f93c1b2ea3f221159869d6f556cae1ba2622cc8c7eb71c7f4f64e0fbca4") - - val eclair = mock[Eclair] - eclair.open(any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(OpenChannelResponse.Created(channelId, fundingTxId, 1 sat)) - val mockService = new MockService(eclair) - - Post("/open", FormData("nodeId" -> nodeId.toString(), "fundingSatoshis" -> "25000", "channelType" -> "static_remotekey").toEntity) ~> - addCredentials(BasicHttpCredentials("", mockApi().password)) ~> - addHeader("Content-Type", "application/json") ~> - Route.seal(mockService.route) ~> - check { - assert(handled) - assert(status == OK) - assert(entityAs[String] == "\"created channel 56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e with fundingTxId=a86b3f93c1b2ea3f221159869d6f556cae1ba2622cc8c7eb71c7f4f64e0fbca4 and fees=1 sat\"") - eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.StaticRemoteKey()), None, None, None, None)(any[Timeout]).wasCalled(once) - } - } - test("'open' channels with anchor_outputs channelType") { val nodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87") val channelId = ByteVector32(hex"56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e") @@ -361,17 +319,6 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM eclair.open(any, any, any, any, any, any, any, any)(any[Timeout]) returns Future.successful(OpenChannelResponse.Created(channelId, fundingTxId, 500 sat)) val mockService = new MockService(eclair) - Post("/open", FormData("nodeId" -> nodeId.toString(), "fundingSatoshis" -> "25000", "channelType" -> "anchor_outputs").toEntity) ~> - addCredentials(BasicHttpCredentials("", mockApi().password)) ~> - addHeader("Content-Type", "application/json") ~> - Route.seal(mockService.route) ~> - check { - assert(handled) - assert(status == OK) - assert(entityAs[String] == "\"created channel 56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e with fundingTxId=a86b3f93c1b2ea3f221159869d6f556cae1ba2622cc8c7eb71c7f4f64e0fbca4 and fees=500 sat\"") - eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.AnchorOutputs()), None, None, None, None)(any[Timeout]).wasCalled(once) - } - Post("/open", FormData("nodeId" -> nodeId.toString(), "fundingSatoshis" -> "25000", "channelType" -> "anchor_outputs_zero_fee_htlc_tx").toEntity) ~> addCredentials(BasicHttpCredentials("", mockApi().password)) ~> addHeader("Content-Type", "application/json") ~>