1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 11:35:47 +01:00

Simplify on-chain fee management (#2696)

Move away from the "block target" approach.

Get rid of the `FeeEstimator` abstraction and use an `AtomicReference` to store and update the current feerates, similar to the block count.
This commit is contained in:
Pierre-Marie Padiou 2023-06-20 11:56:24 +02:00 committed by GitHub
parent 194f5dd2b8
commit da98e19540
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 807 additions and 826 deletions

View file

@ -4,7 +4,23 @@
## Major changes
<insert changes>
### Use priority instead of block target for feerates
Eclair now uses a `slow`/`medium`/`fast` notation for feerates (in the style of mempool.space),
instead of block targets. Only the funding and closing priorities can be configured, the feerate
for commitment transactions is managed by eclair, so is the fee bumping for htlcs in force close
scenarii. Note that even in a force close scenario, when an output is only spendable by eclair, then
the normal closing priority is used.
Default setting is `medium` for both funding and closing. Node operators may configure their values like so:
```eclair.conf
eclair.on-chain-fees.confirmation-priority {
funding = fast
closing = slow
}
```
This configuration section replaces the previous `eclair.on-chain-fees.target-blocks` section.
### API changes

View file

@ -201,31 +201,18 @@ eclair {
min-feerate = 1 // minimum feerate in satoshis per byte
smoothing-window = 6 // 1 = no smoothing
default-feerates { // those are per target block, in satoshis per kilobyte
1 = 210000
2 = 180000
6 = 150000
12 = 110000
36 = 50000
72 = 20000
144 = 15000
1008 = 5000
default-feerates { // the following values are in satoshis per byte
minimum = 5
slow = 5
medium = 10
fast = 20
fastest = 30
}
// number of blocks to target when computing fees for each transaction type
target-blocks {
// target for the funding transaction
funding = 6
// target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing*
commitment = 2
// target for the commitment transaction when we have no htlcs to claim (used in force-close scenario) *do not change this unless you know what you are doing*
commitment-without-htlcs = 12
// target for the mutual close transaction
mutual-close = 12
// target for the claim main transaction (tx that spends main channel output back to wallet)
claim-main = 12
// when our utxos count is below this threshold, we will use more aggressive confirmation targets in force-close scenarios
safe-utxos-threshold = 10
// confirmation priority for each transaction type, can be slow/medium/fast
confirmation-priority {
funding = medium
closing = medium
}
feerate-tolerance {
@ -260,6 +247,9 @@ eclair {
# }
]
// when our utxos count is below this threshold, we will use more aggressive confirmation targets in force-close scenarios
safe-utxos-threshold = 10
// if false, the commitment transaction will not be fee-bumped when we have no htlcs to claim (used in force-close scenario)
// *do not change this unless you know what you are doing*
spend-anchor-without-htlcs = true

View file

@ -45,7 +45,7 @@ import java.net.InetSocketAddress
import java.nio.file.Files
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
@ -56,6 +56,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
channelKeyManager: ChannelKeyManager,
instanceId: UUID, // a unique instance ID regenerated after each restart
private val blockHeight: AtomicLong,
private val feerates: AtomicReference[FeeratesPerKw],
alias: String,
color: Color,
publicAddresses: List[NodeAddress],
@ -97,6 +98,11 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get)
def currentFeerates: FeeratesPerKw = feerates.get()
/** Only to be used in tests. */
def setFeerates(value: FeeratesPerKw) = feerates.set(value)
/** Returns the features that should be used in our init message with the given peer. */
def initFeaturesFor(nodeId: PublicKey): Features[InitFeature] = overrideInitFeatures.getOrElse(nodeId, features).initFeatures()
}
@ -206,7 +212,7 @@ object NodeParams extends Logging {
}
def makeNodeParams(config: Config, instanceId: UUID, nodeKeyManager: NodeKeyManager, channelKeyManager: ChannelKeyManager,
torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, feeEstimator: FeeEstimator,
torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, feerates: AtomicReference[FeeratesPerKw],
pluginParams: Seq[PluginParams] = Nil): NodeParams = {
// check configuration for keys that have been renamed
val deprecatedKeyPaths = Map(
@ -265,7 +271,11 @@ object NodeParams extends Logging {
"payment-request-expiry" -> "invoice-expiry",
"override-features" -> "override-init-features",
"channel.min-funding-satoshis" -> "channel.min-public-funding-satoshis, channel.min-private-funding-satoshis",
"bitcoind.batch-requests" -> "bitcoind.batch-watcher-requests"
// v0.8.0
"bitcoind.batch-requests" -> "bitcoind.batch-watcher-requests",
// vx.x.x
"on-chain-fees.target-blocks.safe-utxos-threshold" -> "on-chain-fees.safe-utxos-threshold",
"on-chain-fees.target-blocks" -> "on-chain-fees.confirmation-priority"
)
deprecatedKeyPaths.foreach {
case (old, new_) => require(!config.hasPath(old), s"configuration key '$old' has been replaced by '$new_'")
@ -370,13 +380,15 @@ object NodeParams extends Logging {
validateAddresses(addresses)
def getConfirmationPriority(path: String): ConfirmationPriority = config.getString(path) match {
case "slow" => ConfirmationPriority.Slow
case "medium" => ConfirmationPriority.Medium
case "fast" => ConfirmationPriority.Fast
}
val feeTargets = FeeTargets(
fundingBlockTarget = config.getInt("on-chain-fees.target-blocks.funding"),
commitmentBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment"),
commitmentWithoutHtlcsBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment-without-htlcs"),
mutualCloseBlockTarget = config.getInt("on-chain-fees.target-blocks.mutual-close"),
claimMainBlockTarget = config.getInt("on-chain-fees.target-blocks.claim-main"),
safeUtxosThreshold = config.getInt("on-chain-fees.target-blocks.safe-utxos-threshold"),
funding = getConfirmationPriority("on-chain-fees.confirmation-priority.funding"),
closing = getConfirmationPriority("on-chain-fees.confirmation-priority.closing"),
)
def getRelayFees(relayFeesConfig: Config): RelayFees = {
@ -465,6 +477,7 @@ object NodeParams extends Logging {
channelKeyManager = channelKeyManager,
instanceId = instanceId,
blockHeight = blockHeight,
feerates = feerates,
alias = nodeAlias,
color = Color(color(0), color(1), color(2)),
publicAddresses = addresses,
@ -504,7 +517,7 @@ object NodeParams extends Logging {
),
onChainFeeConf = OnChainFeeConf(
feeTargets = feeTargets,
feeEstimator = feeEstimator,
safeUtxosThreshold = config.getInt("on-chain-fees.safe-utxos-threshold"),
spendAnchorWithoutHtlcs = config.getBoolean("on-chain-fees.spend-anchor-without-htlcs"),
closeOnOfflineMismatch = config.getBoolean("on-chain-fees.close-on-offline-feerate-mismatch"),
updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee-min-diff-ratio"),

View file

@ -114,22 +114,8 @@ class Setup(val datadir: File,
* This holds the current feerates, in satoshi-per-kilobytes.
* The value is read by all actors, hence it needs to be thread-safe.
*/
val feeratesPerKB = new AtomicReference[FeeratesPerKB](null)
/**
* This holds the current feerates, in satoshi-per-kw.
* The value is read by all actors, hence it needs to be thread-safe.
*/
val feeratesPerKw = new AtomicReference[FeeratesPerKw](null)
val feeEstimator = new FeeEstimator {
// @formatter:off
override def getFeeratePerKb(target: Int): FeeratePerKB = feeratesPerKB.get().feePerBlock(target)
override def getFeeratePerKw(target: Int): FeeratePerKw = feeratesPerKw.get().feePerBlock(target)
override def getMempoolMinFeeratePerKw(): FeeratePerKw = feeratesPerKw.get().mempoolMinFee
// @formatter:on
}
val serverBindingAddress = new InetSocketAddress(config.getString("server.binding-ip"), config.getInt("server.port"))
// early checks
@ -198,7 +184,7 @@ class Setup(val datadir: File,
logger.info(s"connecting to database with instanceId=$instanceId")
val databases = Databases.init(config.getConfig("db"), instanceId, chaindir, db)
val nodeParams = NodeParams.makeNodeParams(config, instanceId, nodeKeyManager, channelKeyManager, initTor(), databases, blockHeight, feeEstimator, pluginParams)
val nodeParams = NodeParams.makeNodeParams(config, instanceId, nodeKeyManager, channelKeyManager, initTor(), databases, blockHeight, feeratesPerKw, pluginParams)
logger.info(s"nodeid=${nodeParams.nodeId} alias=${nodeParams.alias}")
assert(bitcoinChainHash == nodeParams.chainHash, s"chainHash mismatch (conf=${nodeParams.chainHash} != bitcoind=$bitcoinChainHash)")
@ -221,17 +207,12 @@ class Setup(val datadir: File,
defaultFeerates = {
val confDefaultFeerates = FeeratesPerKB(
mempoolMinFee = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.1008"))),
block_1 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.1"))),
blocks_2 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.2"))),
blocks_6 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.6"))),
blocks_12 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.12"))),
blocks_36 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.36"))),
blocks_72 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.72"))),
blocks_144 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.144"))),
blocks_1008 = FeeratePerKB(Satoshi(config.getLong("on-chain-fees.default-feerates.1008"))),
minimum = FeeratePerKB(FeeratePerByte(Satoshi(config.getLong("on-chain-fees.default-feerates.minimum")))),
slow = FeeratePerKB(FeeratePerByte(Satoshi(config.getLong("on-chain-fees.default-feerates.slow")))),
medium = FeeratePerKB(FeeratePerByte(Satoshi(config.getLong("on-chain-fees.default-feerates.medium")))),
fast = FeeratePerKB(FeeratePerByte(Satoshi(config.getLong("on-chain-fees.default-feerates.fast")))),
fastest = FeeratePerKB(FeeratePerByte(Satoshi(config.getLong("on-chain-fees.default-feerates.fastest")))),
)
feeratesPerKB.set(confDefaultFeerates)
feeratesPerKw.set(FeeratesPerKw(confDefaultFeerates))
confDefaultFeerates
}
@ -244,13 +225,15 @@ class Setup(val datadir: File,
FallbackFeeProvider(SmoothFeeProvider(BitcoinCoreFeeProvider(bitcoin, defaultFeerates), smoothFeerateWindow) :: Nil, minFeeratePerByte)
}
_ = system.scheduler.scheduleWithFixedDelay(0 seconds, 10 minutes)(() => feeProvider.getFeerates.onComplete {
case Success(feerates) =>
feeratesPerKB.set(feerates)
feeratesPerKw.set(FeeratesPerKw(feerates))
channel.Monitoring.Metrics.LocalFeeratePerKw.withoutTags().update(feeratesPerKw.get.feePerBlock(nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget).toLong.toDouble)
blockchain.Monitoring.Metrics.MempoolMinFeeratePerKw.withoutTags().update(feeratesPerKw.get.mempoolMinFee.toLong.toDouble)
case Success(feeratesPerKB) =>
feeratesPerKw.set(FeeratesPerKw(feeratesPerKB))
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Minimum).update(feeratesPerKw.get.minimum.toLong.toDouble)
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Slow).update(feeratesPerKw.get.slow.toLong.toDouble)
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Medium).update(feeratesPerKw.get.medium.toLong.toDouble)
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Fast).update(feeratesPerKw.get.fast.toLong.toDouble)
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Fastest).update(feeratesPerKw.get.fastest.toLong.toDouble)
system.eventStream.publish(CurrentFeerates(feeratesPerKw.get))
logger.info(s"current feeratesPerKB=${feeratesPerKB.get} feeratesPerKw=${feeratesPerKw.get}")
logger.info(s"current feeratesPerKB=${feeratesPerKB} feeratesPerKw=${feeratesPerKw.get}")
feeratesRetrieved.trySuccess(Done)
case Failure(exception) =>
logger.warn(s"cannot retrieve feerates: ${exception.getMessage}")

View file

@ -26,12 +26,21 @@ object Monitoring {
val RpcBasicInvokeCount: Metric.Counter = Kamon.counter("bitcoin.rpc.basic.invoke.count")
val RpcBasicInvokeDuration: Metric.Timer = Kamon.timer("bitcoin.rpc.basic.invoke.duration")
val RpcBatchInvokeDuration: Metric.Timer = Kamon.timer("bitcoin.rpc.batch.invoke.duration")
val MempoolMinFeeratePerKw: Metric.Gauge = Kamon.gauge("bitcoin.mempool.min-feerate-per-kw", "Minimum feerate (sat/kw) for a tx to be accepted in our mempool")
val FeeratesPerByte: Metric.Gauge = Kamon.gauge("bitcoin.feerates-per-byte", "Current feerates in sat/byte")
val CannotRetrieveFeeratesCount: Metric.Counter = Kamon.counter("bitcoin.rpc.feerates.error", "Number of failures to retrieve on-chain feerates")
}
object Tags {
val Method = "method"
val Priority = "priority"
object Priorities {
val Minimum = "0-minimum"
val Slow = "1-slow"
val Medium = "2-medium"
val Fast = "3-fast"
val Fastest = "4-fastest"
}
}
}

View file

@ -50,22 +50,14 @@ case class BitcoinCoreFeeProvider(rpcClient: BitcoinJsonRPCClient, defaultFeerat
mempoolMinFee <- mempoolMinFee()
block_1 <- estimateSmartFee(1)
blocks_2 <- estimateSmartFee(2)
blocks_6 <- estimateSmartFee(6)
blocks_12 <- estimateSmartFee(12)
blocks_36 <- estimateSmartFee(36)
blocks_72 <- estimateSmartFee(72)
blocks_144 <- estimateSmartFee(144)
blocks_1008 <- estimateSmartFee(1008)
} yield FeeratesPerKB(
mempoolMinFee = if (mempoolMinFee.feerate > 0.sat) mempoolMinFee else defaultFeerates.mempoolMinFee,
block_1 = if (block_1.feerate > 0.sat) block_1 else defaultFeerates.block_1,
blocks_2 = if (blocks_2.feerate > 0.sat) blocks_2 else defaultFeerates.blocks_2,
blocks_6 = if (blocks_6.feerate > 0.sat) blocks_6 else defaultFeerates.blocks_6,
blocks_12 = if (blocks_12.feerate > 0.sat) blocks_12 else defaultFeerates.blocks_12,
blocks_36 = if (blocks_36.feerate > 0.sat) blocks_36 else defaultFeerates.blocks_36,
blocks_72 = if (blocks_72.feerate > 0.sat) blocks_72 else defaultFeerates.blocks_72,
blocks_144 = if (blocks_144.feerate > 0.sat) blocks_144 else defaultFeerates.blocks_144,
blocks_1008 = if (blocks_1008.feerate > 0.sat) blocks_1008 else defaultFeerates.blocks_1008)
minimum = if (mempoolMinFee.feerate > 0.sat) mempoolMinFee else defaultFeerates.minimum,
fastest = if (block_1.feerate > 0.sat) block_1 else defaultFeerates.fastest,
fast = if (blocks_2.feerate > 0.sat) blocks_2 else defaultFeerates.fast,
medium = if (blocks_12.feerate > 0.sat) blocks_12 else defaultFeerates.medium,
slow = if (blocks_1008.feerate > 0.sat) blocks_1008 else defaultFeerates.slow)
}
object BitcoinCoreFeeProvider {

View file

@ -44,15 +44,11 @@ case class FallbackFeeProvider(providers: Seq[FeeProvider], minFeeratePerByte: F
object FallbackFeeProvider {
private def enforceMinimumFeerate(feeratesPerKB: FeeratesPerKB, minFeeratePerKB: FeeratePerKB): FeeratesPerKB = FeeratesPerKB(
mempoolMinFee = feeratesPerKB.mempoolMinFee.max(minFeeratePerKB),
block_1 = feeratesPerKB.block_1.max(minFeeratePerKB),
blocks_2 = feeratesPerKB.blocks_2.max(minFeeratePerKB),
blocks_6 = feeratesPerKB.blocks_6.max(minFeeratePerKB),
blocks_12 = feeratesPerKB.blocks_12.max(minFeeratePerKB),
blocks_36 = feeratesPerKB.blocks_36.max(minFeeratePerKB),
blocks_72 = feeratesPerKB.blocks_72.max(minFeeratePerKB),
blocks_144 = feeratesPerKB.blocks_144.max(minFeeratePerKB),
blocks_1008 = feeratesPerKB.blocks_1008.max(minFeeratePerKB)
minimum = feeratesPerKB.minimum.max(minFeeratePerKB),
fastest = feeratesPerKB.fastest.max(minFeeratePerKB),
fast = feeratesPerKB.fast.max(minFeeratePerKB),
medium = feeratesPerKB.medium.max(minFeeratePerKB),
slow = feeratesPerKB.slow.max(minFeeratePerKB)
)
}

View file

@ -110,64 +110,36 @@ object FeeratePerKw {
* The mempoolMinFee is the minimal fee required for a tx to enter the mempool (and then be relayed to other nodes and eventually get confirmed).
* If our fee provider doesn't expose this data, using its biggest block target should be a good enough estimation.
*/
case class FeeratesPerKB(mempoolMinFee: FeeratePerKB, block_1: FeeratePerKB, blocks_2: FeeratePerKB, blocks_6: FeeratePerKB, blocks_12: FeeratePerKB, blocks_36: FeeratePerKB, blocks_72: FeeratePerKB, blocks_144: FeeratePerKB, blocks_1008: FeeratePerKB) {
require(mempoolMinFee.feerate > 0.sat && block_1.feerate > 0.sat && blocks_2.feerate > 0.sat && blocks_6.feerate > 0.sat && blocks_12.feerate > 0.sat && blocks_36.feerate > 0.sat && blocks_72.feerate > 0.sat && blocks_144.feerate > 0.sat && blocks_1008.feerate > 0.sat, "all feerates must be strictly greater than 0")
def feePerBlock(target: Int): FeeratePerKB = {
require(target > 0)
target match {
case 1 => block_1
case 2 => blocks_2
case t if t <= 6 => blocks_6
case t if t <= 12 => blocks_12
case t if t <= 36 => blocks_36
case t if t <= 72 => blocks_72
case t if t <= 144 => blocks_144
case _ => blocks_1008
}
}
case class FeeratesPerKB(minimum: FeeratePerKB,
slow: FeeratePerKB,
medium: FeeratePerKB,
fast: FeeratePerKB,
fastest: FeeratePerKB) {
require(minimum.feerate > 0.sat && slow.feerate > 0.sat && medium.feerate > 0.sat && fast.feerate > 0.sat && fastest.feerate > 0.sat, "all feerates must be strictly greater than 0")
}
/** Fee rates in satoshi-per-kilo-weight (1 kw = 1000 weight units). */
case class FeeratesPerKw(mempoolMinFee: FeeratePerKw, block_1: FeeratePerKw, blocks_2: FeeratePerKw, blocks_6: FeeratePerKw, blocks_12: FeeratePerKw, blocks_36: FeeratePerKw, blocks_72: FeeratePerKw, blocks_144: FeeratePerKw, blocks_1008: FeeratePerKw) {
require(mempoolMinFee.feerate > 0.sat && block_1.feerate > 0.sat && blocks_2.feerate > 0.sat && blocks_6.feerate > 0.sat && blocks_12.feerate > 0.sat && blocks_36.feerate > 0.sat && blocks_72.feerate > 0.sat && blocks_144.feerate > 0.sat && blocks_1008.feerate > 0.sat, "all feerates must be strictly greater than 0")
def feePerBlock(target: Int): FeeratePerKw = {
require(target > 0)
target match {
case 1 => block_1
case 2 => blocks_2
case t if t <= 6 => blocks_6
case t if t <= 12 => blocks_12
case t if t <= 36 => blocks_36
case t if t <= 72 => blocks_72
case t if t <= 144 => blocks_144
case _ => blocks_1008
}
}
case class FeeratesPerKw(minimum: FeeratePerKw,
slow: FeeratePerKw,
medium: FeeratePerKw,
fast: FeeratePerKw,
fastest: FeeratePerKw) {
require(minimum.feerate > 0.sat && slow.feerate > 0.sat && medium.feerate > 0.sat && fast.feerate > 0.sat && fastest.feerate > 0.sat, "all feerates must be strictly greater than 0")
}
object FeeratesPerKw {
def apply(feerates: FeeratesPerKB): FeeratesPerKw = FeeratesPerKw(
mempoolMinFee = FeeratePerKw(feerates.mempoolMinFee),
block_1 = FeeratePerKw(feerates.block_1),
blocks_2 = FeeratePerKw(feerates.blocks_2),
blocks_6 = FeeratePerKw(feerates.blocks_6),
blocks_12 = FeeratePerKw(feerates.blocks_12),
blocks_36 = FeeratePerKw(feerates.blocks_36),
blocks_72 = FeeratePerKw(feerates.blocks_72),
blocks_144 = FeeratePerKw(feerates.blocks_144),
blocks_1008 = FeeratePerKw(feerates.blocks_1008))
minimum = FeeratePerKw(feerates.minimum),
slow = FeeratePerKw(feerates.slow),
medium = FeeratePerKw(feerates.medium),
fast = FeeratePerKw(feerates.fast),
fastest = FeeratePerKw(feerates.fastest))
/** Used in tests */
def single(feeratePerKw: FeeratePerKw): FeeratesPerKw = FeeratesPerKw(
mempoolMinFee = feeratePerKw,
block_1 = feeratePerKw,
blocks_2 = feeratePerKw,
blocks_6 = feeratePerKw,
blocks_12 = feeratePerKw,
blocks_36 = feeratePerKw,
blocks_72 = feeratePerKw,
blocks_144 = feeratePerKw,
blocks_1008 = feeratePerKw)
minimum = feeratePerKw,
slow = feeratePerKw,
medium = feeratePerKw,
fast = feeratePerKw,
fastest = feeratePerKw)
}

View file

@ -18,25 +18,32 @@ package fr.acinq.eclair.blockchain.fee
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.Satoshi
import fr.acinq.eclair.blockchain.CurrentFeerates
import fr.acinq.eclair.BlockHeight
import fr.acinq.eclair.channel.{ChannelTypes, SupportedChannelType}
import fr.acinq.eclair.transactions.Transactions
trait FeeEstimator {
// @formatter:off
def getFeeratePerKb(target: Int): FeeratePerKB
def getFeeratePerKw(target: Int): FeeratePerKw
def getMempoolMinFeeratePerKw(): FeeratePerKw
// @formatter:on
// @formatter:off
sealed trait ConfirmationPriority {
def getFeerate(feerates: FeeratesPerKw): FeeratePerKw = this match {
case ConfirmationPriority.Slow => feerates.slow
case ConfirmationPriority.Medium => feerates.medium
case ConfirmationPriority.Fast => feerates.fast
}
override def toString: String = super.toString.toLowerCase
}
object ConfirmationPriority {
case object Slow extends ConfirmationPriority
case object Medium extends ConfirmationPriority
case object Fast extends ConfirmationPriority
}
sealed trait ConfirmationTarget
object ConfirmationTarget {
case class Absolute(confirmBefore: BlockHeight) extends ConfirmationTarget
case class Priority(priority: ConfirmationPriority) extends ConfirmationTarget
}
// @formatter:on
case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, commitmentWithoutHtlcsBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int, safeUtxosThreshold: Int) {
require(fundingBlockTarget > 0)
require(commitmentBlockTarget > 0)
require(commitmentWithoutHtlcsBlockTarget > 0)
require(mutualCloseBlockTarget > 0)
require(claimMainBlockTarget > 0)
}
case class FeeTargets(funding: ConfirmationPriority, closing: ConfirmationPriority)
/**
* @param maxExposure maximum exposure to pending dust htlcs we tolerate: we will automatically fail HTLCs when going above this threshold.
@ -62,7 +69,7 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax
}
}
case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, spendAnchorWithoutHtlcs: Boolean, closeOnOfflineMismatch: Boolean, updateFeeMinDiffRatio: Double, private val defaultFeerateTolerance: FeerateTolerance, private val perNodeFeerateTolerance: Map[PublicKey, FeerateTolerance]) {
case class OnChainFeeConf(feeTargets: FeeTargets, safeUtxosThreshold: Int, spendAnchorWithoutHtlcs: Boolean, closeOnOfflineMismatch: Boolean, updateFeeMinDiffRatio: Double, private val defaultFeerateTolerance: FeerateTolerance, private val perNodeFeerateTolerance: Map[PublicKey, FeerateTolerance]) {
def feerateToleranceFor(nodeId: PublicKey): FeerateTolerance = perNodeFeerateTolerance.getOrElse(nodeId, defaultFeerateTolerance)
@ -70,6 +77,8 @@ case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, sp
def shouldUpdateFee(currentFeeratePerKw: FeeratePerKw, nextFeeratePerKw: FeeratePerKw): Boolean =
currentFeeratePerKw.toLong == 0 || Math.abs((currentFeeratePerKw.toLong - nextFeeratePerKw.toLong).toDouble / currentFeeratePerKw.toLong) > updateFeeMinDiffRatio
def getFundingFeerate(feerates: FeeratesPerKw): FeeratePerKw = feeTargets.funding.getFeerate(feerates)
/**
* Get the feerate that should apply to a channel commitment transaction:
* - if we're using anchor outputs, we use a feerate that allows network propagation of the commit tx: we will use CPFP to speed up confirmation if needed
@ -79,11 +88,10 @@ case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, sp
* @param channelType channel type
* @param currentFeerates_opt if provided, will be used to compute the most up-to-date network fee, otherwise we rely on the fee estimator
*/
def getCommitmentFeerate(remoteNodeId: PublicKey, channelType: SupportedChannelType, channelCapacity: Satoshi, currentFeerates_opt: Option[CurrentFeerates]): FeeratePerKw = {
val (networkFeerate, networkMinFee) = currentFeerates_opt match {
case Some(currentFeerates) => (currentFeerates.feeratesPerKw.feePerBlock(feeTargets.commitmentBlockTarget), currentFeerates.feeratesPerKw.mempoolMinFee)
case None => (feeEstimator.getFeeratePerKw(feeTargets.commitmentBlockTarget), feeEstimator.getMempoolMinFeeratePerKw())
}
def getCommitmentFeerate(feerates: FeeratesPerKw, remoteNodeId: PublicKey, channelType: SupportedChannelType, channelCapacity: Satoshi): FeeratePerKw = {
val networkFeerate = feerates.fast
val networkMinFee = feerates.minimum
channelType.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => networkFeerate
case _: Transactions.AnchorOutputsCommitmentFormat =>
@ -92,4 +100,6 @@ case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, sp
targetFeerate.max(networkMinFee * 1.25)
}
}
def getClosingFeerate(feerates: FeeratesPerKw): FeeratePerKw = feeTargets.closing.getFeerate(feerates)
}

View file

@ -43,14 +43,10 @@ object SmoothFeeProvider {
def smooth(rates: Seq[FeeratesPerKB]): FeeratesPerKB =
FeeratesPerKB(
mempoolMinFee = avg(rates.map(_.mempoolMinFee)),
block_1 = avg(rates.map(_.block_1)),
blocks_2 = avg(rates.map(_.blocks_2)),
blocks_6 = avg(rates.map(_.blocks_6)),
blocks_12 = avg(rates.map(_.blocks_12)),
blocks_36 = avg(rates.map(_.blocks_36)),
blocks_72 = avg(rates.map(_.blocks_72)),
blocks_144 = avg(rates.map(_.blocks_144)),
blocks_1008 = avg(rates.map(_.blocks_1008)))
minimum = avg(rates.map(_.minimum)),
fastest = avg(rates.map(_.fastest)),
fast = avg(rates.map(_.fast)),
medium = avg(rates.map(_.medium)),
slow = avg(rates.map(_.slow)))
}

View file

@ -4,9 +4,9 @@ import akka.event.LoggingAdapter
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong, Script, Transaction}
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, OnChainFeeConf}
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw, OnChainFeeConf}
import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel.Monitoring.Metrics
import fr.acinq.eclair.channel.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.ChannelConf
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.SharedTransaction
@ -16,7 +16,7 @@ import fr.acinq.eclair.payment.OutgoingPaymentPacket
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, payment}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, channel, payment}
import scodec.bits.ByteVector
/** Static channel parameters shared by all commitments. */
@ -411,11 +411,11 @@ case class Commitment(fundingTxIndex: Long,
localCommit.spec.htlcs.collect(DirectedHtlc.incoming).filter(nearlyExpired)
}
def canSendAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = {
def canSendAdd(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 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 localFeeratePerKw = feeConf.getCommitmentFeerate(params.remoteNodeId, params.channelType, capacity, None)
val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.channelType, capacity)
val remoteFeeratePerKw = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw }
remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isFeeDiffTooHigh(params.channelType, localFeeratePerKw, feerate)) match {
case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = feerate))
@ -475,11 +475,11 @@ case class Commitment(fundingTxIndex: Long,
Right(())
}
def canReceiveAdd(amount: MilliSatoshi, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = {
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 localFeeratePerKw = feeConf.getCommitmentFeerate(params.remoteNodeId, params.channelType, capacity, None)
val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.channelType, capacity)
val remoteFeeratePerKw = localCommit.spec.commitTxFeerate +: changes.remoteChanges.all.collect { case f: UpdateFee => f.feeratePerKw }
remoteFeeratePerKw.find(feerate => feeConf.feerateToleranceFor(params.remoteNodeId).isFeeDiffTooHigh(params.channelType, localFeeratePerKw, feerate)) match {
case Some(feerate) => return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = feerate))
@ -551,8 +551,8 @@ case class Commitment(fundingTxIndex: Long,
Right(())
}
def canReceiveFee(targetFeerate: FeeratePerKw, params: ChannelParams, changes: CommitmentChanges, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = {
val localFeeratePerKw = feeConf.getCommitmentFeerate(params.remoteNodeId, params.channelType, capacity, None)
def canReceiveFee(targetFeerate: FeeratePerKw, params: ChannelParams, changes: CommitmentChanges, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Unit] = {
val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.channelType, capacity)
if (feeConf.feerateToleranceFor(params.remoteNodeId).isFeeDiffTooHigh(params.channelType, localFeeratePerKw, targetFeerate) && hasPendingOrProposedHtlcs(changes)) {
return Left(FeerateTooDifferent(params.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = targetFeerate))
} else {
@ -809,7 +809,7 @@ case class Commitments(params: 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, feeConf: OnChainFeeConf): Either[ChannelException, (Commitments, UpdateAddHtlc)] = {
def sendAdd(cmd: CMD_ADD_HTLC, currentHeight: BlockHeight, channelConf: ChannelConf, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): 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)
@ -833,12 +833,12 @@ case class Commitments(params: 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
active.map(_.canSendAdd(add.amountMsat, params, changes1, feeConf))
active.map(_.canSendAdd(add.amountMsat, params, changes1, feerates, feeConf))
.collectFirst { case Left(f) => Left(f) }
.getOrElse(Right(copy(changes = changes1, originChannels = originChannels1), add))
}
def receiveAdd(add: UpdateAddHtlc, feeConf: OnChainFeeConf): Either[ChannelException, Commitments] = {
def receiveAdd(add: UpdateAddHtlc, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Commitments] = {
if (add.id != changes.remoteNextHtlcId) {
return Left(UnexpectedHtlcId(channelId, expected = changes.remoteNextHtlcId, actual = add.id))
}
@ -851,7 +851,7 @@ case class Commitments(params: 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, params, changes1, feeConf))
active.map(_.canReceiveAdd(add.amountMsat, params, changes1, feerates, feeConf))
.collectFirst { case Left(f) => Left(f) }
.getOrElse(Right(copy(changes = changes1)))
}
@ -942,24 +942,29 @@ case class Commitments(params: ChannelParams,
val changes1 = changes.copy(localChanges = changes.localChanges.copy(proposed = changes.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee))
active.map(_.canSendFee(cmd.feeratePerKw, params, changes1, feeConf))
.collectFirst { case Left(f) => Left(f) }
.getOrElse(Right(copy(changes = changes1), fee))
.getOrElse {
Metrics.LocalFeeratePerByte.withTag(Tags.CommitmentFormat, params.channelType.commitmentFormat.toString).record(FeeratePerByte(cmd.feeratePerKw).feerate.toLong)
Right(copy(changes = changes1), fee)
}
}
}
def receiveFee(fee: UpdateFee, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, Commitments] = {
def receiveFee(fee: UpdateFee, feerates: FeeratesPerKw, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, Commitments] = {
if (params.localParams.isInitiator) {
Left(NonInitiatorCannotSendUpdateFee(channelId))
} else if (fee.feeratePerKw < FeeratePerKw.MinimumFeeratePerKw) {
Left(FeerateTooSmall(channelId, remoteFeeratePerKw = fee.feeratePerKw))
} else {
Metrics.RemoteFeeratePerKw.withoutTags().record(fee.feeratePerKw.toLong)
val localFeeratePerKw = feeConf.getCommitmentFeerate(params.remoteNodeId, params.channelType, active.head.capacity, None)
val localFeeratePerKw = feeConf.getCommitmentFeerate(feerates, params.remoteNodeId, params.channelType, active.head.capacity)
log.info("remote feeratePerKw={}, local feeratePerKw={}, ratio={}", fee.feeratePerKw, localFeeratePerKw, fee.feeratePerKw.toLong.toDouble / localFeeratePerKw.toLong)
// update_fee replace each other, so we can remove previous ones
val changes1 = changes.copy(remoteChanges = changes.remoteChanges.copy(proposed = changes.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee))
active.map(_.canReceiveFee(fee.feeratePerKw, params, changes1, feeConf))
active.map(_.canReceiveFee(fee.feeratePerKw, params, changes1, feerates, feeConf))
.collectFirst { case Left(f) => Left(f) }
.getOrElse(Right(copy(changes = changes1)))
.getOrElse {
Metrics.RemoteFeeratePerByte.withTag(Tags.CommitmentFormat, params.channelType.commitmentFormat.toString).record(FeeratePerByte(fee.feeratePerKw).feerate.toLong)
Right(copy(changes = changes1))
}
}
}

View file

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256}
import fr.acinq.bitcoin.scalacompat.Script._
import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratePerKw, OnChainFeeConf}
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerKw, FeeratesPerKw, OnChainFeeConf}
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL
import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager
@ -117,7 +117,7 @@ object Helpers {
}
// 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(remoteNodeId, channelType, open.fundingSatoshis, None)
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelType, open.fundingSatoshis)
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType, 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
@ -156,7 +156,7 @@ object Helpers {
if (open.dustLimit > nodeParams.channelConf.maxRemoteDustLimit) return Left(DustLimitTooLarge(open.temporaryChannelId, open.dustLimit, nodeParams.channelConf.maxRemoteDustLimit))
// 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(remoteNodeId, channelType, open.fundingAmount, None)
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelType, open.fundingAmount)
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelType, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate))
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
@ -624,8 +624,8 @@ object Helpers {
feerates.computeFees(closingWeight)
}
def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): ClosingFees = {
val requestedFeerate = feeEstimator.getFeeratePerKw(feeTargets.mutualCloseBlockTarget)
def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf)(implicit log: LoggingAdapter): ClosingFees = {
val requestedFeerate = onChainFeeConf.getClosingFeerate(feerates)
val preferredFeerate = commitment.params.commitmentFormat match {
case DefaultCommitmentFormat =>
// we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
@ -634,16 +634,16 @@ object Helpers {
}
// NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can
// always use CPFP to speed up confirmation if necessary.
val closingFeerates = ClosingFeerates(preferredFeerate, preferredFeerate.min(feeEstimator.getFeeratePerKw(1008)), preferredFeerate * 2)
val closingFeerates = ClosingFeerates(preferredFeerate, preferredFeerate.min(ConfirmationPriority.Slow.getFeerate(feerates)), preferredFeerate * 2)
firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates)
}
def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2
def makeFirstClosingTx(keyManager: ChannelKeyManager, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets, closingFeerates_opt: Option[ClosingFeerates])(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = {
def makeFirstClosingTx(keyManager: ChannelKeyManager, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, closingFeerates_opt: Option[ClosingFeerates])(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = {
val closingFees = closingFeerates_opt match {
case Some(closingFeerates) => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, closingFeerates)
case None => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, feeEstimator, feeTargets)
case None => firstClosingFee(commitment, localScriptPubkey, remoteScriptPubkey, feerates, onChainFeeConf)
}
makeClosingTx(keyManager, commitment, localScriptPubkey, remoteScriptPubkey, closingFees)
}
@ -726,14 +726,14 @@ object Helpers {
* @param commitment our commitment data, which includes payment preimages
* @return a list of transactions (one per output of the commit tx that we can claim)
*/
def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, currentBlockHeight: BlockHeight, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): LocalCommitPublished = {
def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): LocalCommitPublished = {
require(commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx")
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index.toInt)
val localRevocationPubkey = Generators.revocationPubKey(commitment.remoteParams.revocationBasepoint, localPerCommitmentPoint)
val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
val feeratePerKwDelayed = onChainFeeConf.feeEstimator.getFeeratePerKw(onChainFeeConf.feeTargets.claimMainBlockTarget)
val feeratePerKwDelayed = onChainFeeConf.getClosingFeerate(feerates)
// first we will claim our main output as soon as the delay is over
val mainDelayedTx = withTxGenerationLog("local-main-delayed") {
@ -748,7 +748,7 @@ object Helpers {
val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs
val claimAnchorTxs: List[ClaimAnchorOutputTx] = if (spendAnchors) {
// If we don't have pending HTLCs, we don't have funds at risk, so we can aim for a slower confirmation.
val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmBefore).minOption.getOrElse(currentBlockHeight + onChainFeeConf.feeTargets.commitmentWithoutHtlcsBlockTarget)
val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmationTarget).minByOption(_.confirmBefore).getOrElse(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing))
List(
withTxGenerationLog("local-anchor") {
Transactions.makeClaimLocalAnchorOutputTx(tx, localFundingPubKey, confirmCommitBefore)
@ -820,9 +820,9 @@ object Helpers {
* NB: with anchor outputs, it's possible to have transactions that spend *many* HTLC outputs at once, but we're not
* doing that because it introduces a lot of subtle edge cases.
*/
def claimHtlcDelayedOutput(localCommitPublished: LocalCommitPublished, keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (LocalCommitPublished, Option[HtlcDelayedTx]) = {
def claimHtlcDelayedOutput(localCommitPublished: LocalCommitPublished, keyManager: ChannelKeyManager, commitment: FullCommitment, tx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (LocalCommitPublished, Option[HtlcDelayedTx]) = {
if (isHtlcSuccess(tx, localCommitPublished) || isHtlcTimeout(tx, localCommitPublished)) {
val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
val feeratePerKwDelayed = onChainFeeConf.getClosingFeerate(feerates)
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index.toInt)
val localRevocationPubkey = Generators.revocationPubKey(commitment.remoteParams.revocationBasepoint, localPerCommitmentPoint)
@ -852,15 +852,15 @@ object Helpers {
* @param tx the remote commitment transaction that has just been published
* @return a list of transactions (one per output of the commit tx that we can claim)
*/
def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, tx: Transaction, currentBlockHeight: BlockHeight, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RemoteCommitPublished = {
def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, tx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RemoteCommitPublished = {
require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx")
val htlcTxs: Map[OutPoint, Option[ClaimHtlcTx]] = claimHtlcOutputs(keyManager, commitment, remoteCommit, onChainFeeConf.feeEstimator, finalScriptPubKey)
val htlcTxs: Map[OutPoint, Option[ClaimHtlcTx]] = claimHtlcOutputs(keyManager, commitment, remoteCommit, feerates, onChainFeeConf, finalScriptPubKey)
val spendAnchors = htlcTxs.nonEmpty || onChainFeeConf.spendAnchorWithoutHtlcs
val claimAnchorTxs: List[ClaimAnchorOutputTx] = if (spendAnchors) {
// If we don't have pending HTLCs, we don't have funds at risk, so we can aim for a slower confirmation.
val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmBefore).minOption.getOrElse(currentBlockHeight + onChainFeeConf.feeTargets.commitmentWithoutHtlcsBlockTarget)
// If we don't have pending HTLCs, we don't have funds at risk, so we use the normal closing priority.
val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmationTarget).minByOption(_.confirmBefore).getOrElse(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing))
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
List(
withTxGenerationLog("local-anchor") {
@ -876,7 +876,7 @@ object Helpers {
RemoteCommitPublished(
commitTx = tx,
claimMainOutputTx = claimMainOutput(keyManager, commitment.params, remoteCommit.remotePerCommitmentPoint, tx, onChainFeeConf.feeEstimator, onChainFeeConf.feeTargets, finalScriptPubKey),
claimMainOutputTx = claimMainOutput(keyManager, commitment.params, remoteCommit.remotePerCommitmentPoint, tx, feerates, onChainFeeConf, finalScriptPubKey),
claimHtlcTxs = htlcTxs,
claimAnchorTxs = claimAnchorTxs,
irrevocablySpent = Map.empty
@ -890,7 +890,7 @@ object Helpers {
* @param tx the remote commitment transaction that has just been published
* @return an optional [[ClaimRemoteCommitMainOutputTx]] transaction claiming our main output
*/
def claimMainOutput(keyManager: ChannelKeyManager, params: ChannelParams, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = {
def claimMainOutput(keyManager: ChannelKeyManager, params: ChannelParams, remotePerCommitmentPoint: PublicKey, tx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Option[ClaimRemoteCommitMainOutputTx] = {
if (params.channelFeatures.paysDirectlyToWallet) {
// the commitment tx sends funds directly to our wallet, no claim tx needed
None
@ -898,7 +898,7 @@ object Helpers {
val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig)
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localPaymentPoint = keyManager.paymentPoint(channelKeyPath).publicKey
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
val feeratePerKwMain = onChainFeeConf.getClosingFeerate(feerates)
params.commitmentFormat match {
case DefaultCommitmentFormat => withTxGenerationLog("remote-main") {
@ -920,7 +920,7 @@ object Helpers {
/**
* Claim our htlc outputs only
*/
def claimHtlcOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, feeEstimator: FeeEstimator, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Map[OutPoint, Option[ClaimHtlcTx]] = {
def claimHtlcOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Map[OutPoint, Option[ClaimHtlcTx]] = {
val (remoteCommitTx, _) = Commitment.makeRemoteTxs(keyManager, commitment.params.channelConfig, commitment.params.channelFeatures, remoteCommit.index, commitment.localParams, commitment.remoteParams, commitment.fundingTxIndex, commitment.remoteFundingPubKey, commitment.commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
require(remoteCommitTx.tx.txid == remoteCommit.txid, "txid mismatch, cannot recompute the current remote commit tx")
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
@ -932,7 +932,7 @@ object Helpers {
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint)
val outputs = makeCommitTxOutputs(!commitment.localParams.isInitiator, commitment.remoteParams.dustLimit, remoteRevocationPubkey, commitment.localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, commitment.remoteFundingPubKey, localFundingPubkey, remoteCommit.spec, commitment.params.commitmentFormat)
// we need to use a rather high fee for htlc-claim because we compete with the counterparty
val feeratePerKwHtlc = feeEstimator.getFeeratePerKw(target = 2)
val feeratePerKwHtlc = feerates.fast
// those are the preimages to existing received htlcs
val hash2Preimage: Map[ByteVector32, ByteVector32] = commitment.changes.localChanges.all.collect { case u: UpdateFulfillHtlc => u.paymentPreimage }.map(r => Crypto.sha256(r) -> r).toMap
@ -1013,7 +1013,7 @@ object Helpers {
* When a revoked commitment transaction spending the funding tx is detected, we build a set of transactions that
* will punish our peer by stealing all their funds.
*/
def claimCommitTxOutputs(keyManager: ChannelKeyManager, params: ChannelParams, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RevokedCommitPublished = {
def claimCommitTxOutputs(keyManager: ChannelKeyManager, params: ChannelParams, commitTx: Transaction, commitmentNumber: Long, remotePerCommitmentSecret: PrivateKey, db: ChannelsDb, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): RevokedCommitPublished = {
import params._
log.warning("a revoked commit has been published with commitmentNumber={}", commitmentNumber)
@ -1026,9 +1026,9 @@ object Helpers {
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
val feerateMain = onChainFeeConf.getClosingFeerate(feerates)
// we need to use a high fee here for punishment txs because after a delay they can be spent by the counterparty
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2)
val feeratePenalty = feerates.fast
// first we will claim our main output right away
val mainTx = channelFeatures match {
@ -1037,13 +1037,13 @@ object Helpers {
None
case ct => ct.commitmentFormat match {
case DefaultCommitmentFormat => withTxGenerationLog("remote-main") {
Transactions.makeClaimP2WPKHOutputTx(commitTx, localParams.dustLimit, localPaymentPubkey, finalScriptPubKey, feeratePerKwMain).map(claimMain => {
Transactions.makeClaimP2WPKHOutputTx(commitTx, localParams.dustLimit, localPaymentPubkey, finalScriptPubKey, feerateMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, localPaymentPubkey, sig)
})
}
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain).map(claimMain => {
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, sig)
})
@ -1053,7 +1053,7 @@ object Helpers {
// then we punish them by stealing their main output
val mainPenaltyTx = withTxGenerationLog("main-penalty") {
Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, finalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty).map(txinfo => {
Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, finalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePenalty).map(txinfo => {
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(txinfo, sig)
})
@ -1073,7 +1073,7 @@ object Helpers {
val htlcPenaltyTxs = commitTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if htlcsRedeemScripts.contains(txOut.publicKeyScript) =>
val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript)
withTxGenerationLog("htlc-penalty") {
Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemScript, localParams.dustLimit, finalScriptPubKey, feeratePerKwPenalty).map(htlcPenalty => {
Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemScript, localParams.dustLimit, finalScriptPubKey, feeratePenalty).map(htlcPenalty => {
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey)
})
@ -1103,7 +1103,7 @@ object Helpers {
* NB: when anchor outputs is used, htlc transactions can be aggregated in a single transaction if they share the same
* lockTime (thanks to the use of sighash_single | sighash_anyonecanpay), so we may need to claim multiple outputs.
*/
def claimHtlcTxOutputs(keyManager: ChannelKeyManager, params: ChannelParams, remotePerCommitmentSecrets: ShaChain, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feeEstimator: FeeEstimator, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, Seq[ClaimHtlcDelayedOutputPenaltyTx]) = {
def claimHtlcTxOutputs(keyManager: ChannelKeyManager, params: ChannelParams, remotePerCommitmentSecrets: ShaChain, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feerates: FeeratesPerKw, onChainFeeConf: OnChainFeeConf, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): (RevokedCommitPublished, Seq[ClaimHtlcDelayedOutputPenaltyTx]) = {
val isHtlcTx = htlcTx.txIn.map(_.outPoint.txid).contains(revokedCommitPublished.commitTx.txid) &&
htlcTx.txIn.map(_.witness).collect(Scripts.extractPreimageFromHtlcSuccess.orElse(Scripts.extractPaymentHashFromHtlcTimeout)).nonEmpty
if (isHtlcTx) {
@ -1125,7 +1125,7 @@ object Helpers {
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
// we need to use a high fee here for punishment txs because after a delay they can be spent by the counterparty
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 1)
val feeratePerKwPenalty = feerates.fastest
val penaltyTxs = Transactions.makeClaimHtlcDelayedOutputPenaltyTxs(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, finalScriptPubKey, feeratePerKwPenalty).flatMap(claimHtlcDelayedOutputPenaltyTx => {
withTxGenerationLog("htlc-delayed-penalty") {

View file

@ -29,8 +29,8 @@ object Monitoring {
val HtlcsInFlightGlobal = Kamon.gauge("channels.htlc-in-flight-global", "Global HTLCs in flight across all channels")
val HtlcValueInFlight = Kamon.histogram("channels.htlc-value-in-flight", "Per-channel HTLC value in flight")
val HtlcValueInFlightGlobal = Kamon.gauge("channels.htlc-value-in-flight-global", "Global HTLC value in flight across all channels")
val LocalFeeratePerKw = Kamon.gauge("channels.local-feerate-per-kw")
val RemoteFeeratePerKw = Kamon.histogram("channels.remote-feerate-per-kw")
val LocalFeeratePerByte = Kamon.histogram("channels.local-feerate-per-byte")
val RemoteFeeratePerByte = Kamon.histogram("channels.remote-feerate-per-byte")
val ProcessMessage = Kamon.timer("channels.messages-processed")
def recordHtlcsInFlight(remoteSpec: CommitmentSpec, previousRemoteSpec: CommitmentSpec): Unit = {
@ -55,6 +55,7 @@ object Monitoring {
val Fatal = "fatal"
val Origin = "origin"
val State = "state"
val CommitmentFormat = "commitment-format"
object Events {
val Created = "created"
@ -71,7 +72,6 @@ object Monitoring {
val Incoming = "incoming"
val Outgoing = "outgoing"
}
}
}

View file

@ -384,7 +384,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
handleAddHtlcCommandError(c, error, Some(d.channelUpdate))
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) =>
d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.onChainFeeConf) match {
d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match {
case Right((commitments1, add)) =>
if (c.commit) self ! CMD_SIGN()
context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1))
@ -393,7 +393,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
}
case Event(add: UpdateAddHtlc, d: DATA_NORMAL) =>
d.commitments.receiveAdd(add, nodeParams.onChainFeeConf) match {
d.commitments.receiveAdd(add, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(add))
}
@ -468,7 +468,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
}
case Event(fee: UpdateFee, d: DATA_NORMAL) =>
d.commitments.receiveFee(fee, nodeParams.onChainFeeConf) match {
d.commitments.receiveFee(fee, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(fee))
}
@ -674,7 +674,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
// there are no pending signed changes, let's go directly to NEGOTIATING
if (d.commitments.params.localParams.isInitiator) {
// we are the channel initiator, need to initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, localShutdown.scriptPubKey, remoteShutdownScript, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, d.closingFeerates)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, localShutdown.scriptPubKey, remoteShutdownScript, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.closingFeerates)
goto(NEGOTIATING) using DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending sendList :+ closingSigned
} else {
// we are not the channel initiator, will wait for their closing_signed
@ -785,7 +785,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case SpliceStatus.NoSplice =>
if (d.commitments.isIdle && d.commitments.params.remoteParams.initFeatures.hasFeature(SplicePrototype)) {
val parentCommitment = d.commitments.latest.commitment
val targetFeerate = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)
val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates)
val fundingContribution = InteractiveTxFunder.computeSpliceContribution(
isInitiator = true,
sharedInput = Multisig2of2Input(parentCommitment),
@ -829,7 +829,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (!d.commitments.isIdle) {
log.info("rejecting splice request: channel not idle")
stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceRequest(d.channelId).getMessage)
} else if (msg.feerate < nodeParams.onChainFeeConf.feeEstimator.getMempoolMinFeeratePerKw()) {
} else if (msg.feerate < nodeParams.currentFeerates.minimum) {
log.info("rejecting splice request: feerate too low")
stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceRequest(d.channelId).getMessage)
} else {
@ -1138,7 +1138,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
}
case Event(fee: UpdateFee, d: DATA_SHUTDOWN) =>
d.commitments.receiveFee(fee, nodeParams.onChainFeeConf) match {
d.commitments.receiveFee(fee, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(fee))
}
@ -1184,7 +1184,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (commitments1.hasNoPendingHtlcsOrFeeUpdate) {
if (d.commitments.params.localParams.isInitiator) {
// we are the channel initiator, need to initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, closingFeerates)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, closingFeerates)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending revocation :: closingSigned :: Nil
} else {
// we are not the channel initiator, will wait for their closing_signed
@ -1226,7 +1226,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
log.debug("switching to NEGOTIATING spec:\n{}", commitments1.latest.specs2String)
if (d.commitments.params.localParams.isInitiator) {
// we are the channel initiator, need to initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, closingFeerates)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, closingFeerates)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending closingSigned
} else {
// we are not the channel initiator, will wait for their closing_signed
@ -1299,7 +1299,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.params.localParams.isInitiator =>
// if we are not the channel initiator and they proposed a fee range, we pick a value in that range and they should accept it without further negotiation
// we don't care much about the closing fee since they're paying it (not us) and we can use CPFP if we want to speed up confirmation
val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf)
if (maxFee < localClosingFees.min) {
log.warning("their highest closing fee is below our minimum fee: {} < {}", maxFee, localClosingFees.min)
stay() sending Warning(d.channelId, s"closing fee range must not be below ${localClosingFees.min}")
@ -1326,7 +1326,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis)
val (closingTx, closingSigned) = {
// if we are not the channel initiator and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee
val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf)
val nextPreferredFee = Closing.MutualClose.nextClosingFee(lastLocalClosingFee_opt.getOrElse(localClosingFees.preferred), remoteClosingFee)
Closing.MutualClose.makeClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, localClosingFees.copy(preferred = nextPreferredFee))
}
@ -1357,7 +1357,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
handleCommandError(ClosingAlreadyInProgress(d.channelId), c)
} else {
log.info("updating our closing feerates: {}", feerates)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, Some(feerates))
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, Some(feerates))
val closingTxProposed1 = d.closingTxProposed match {
case previousNegotiations :+ currentNegotiation => previousNegotiations :+ (currentNegotiation :+ ClosingTxProposed(closingTx, closingSigned))
case previousNegotiations => previousNegotiations :+ List(ClosingTxProposed(closingTx, closingSigned))
@ -1383,8 +1383,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
log.info("got valid settlement for htlc={}, recalculating htlc transactions", c.id)
val commitment = commitments1.latest
val localCommitPublished1 = d.localCommitPublished.map(localCommitPublished => localCommitPublished.copy(htlcTxs = Closing.LocalClose.claimHtlcOutputs(keyManager, commitment)))
val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.remoteCommit, nodeParams.onChainFeeConf.feeEstimator, d.finalScriptPubKey)))
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.nextRemoteCommit_opt.get.commit, nodeParams.onChainFeeConf.feeEstimator, d.finalScriptPubKey)))
val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.remoteCommit, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey)))
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.nextRemoteCommit_opt.get.commit, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey)))
def republish(): Unit = {
localCommitPublished1.foreach(lcp => doPublish(lcp, commitment))
@ -1531,7 +1531,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
}
}
val revokedCommitPublished1 = d.revokedCommitPublished.map { rev =>
val (rev1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, rev, tx, nodeParams.onChainFeeConf.feeEstimator, d.finalScriptPubKey)
val (rev1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, rev, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey)
penaltyTxs.foreach(claimTx => txPublisher ! PublishFinalTx(claimTx, claimTx.fee, None))
penaltyTxs.foreach(claimTx => blockchain ! WatchOutputSpent(self, tx.txid, claimTx.input.outPoint.index.toInt, hints = Set(claimTx.tx.txid)))
rev1
@ -1545,7 +1545,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
val d1 = d.copy(
localCommitPublished = d.localCommitPublished.map(localCommitPublished => {
// If the tx is one of our HTLC txs, we now publish a 3rd-stage claim-htlc-tx that claims its output.
val (localCommitPublished1, claimHtlcTx_opt) = Closing.LocalClose.claimHtlcDelayedOutput(localCommitPublished, keyManager, d.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, d.finalScriptPubKey)
val (localCommitPublished1, claimHtlcTx_opt) = Closing.LocalClose.claimHtlcDelayedOutput(localCommitPublished, keyManager, d.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey)
claimHtlcTx_opt.foreach(claimHtlcTx => {
txPublisher ! PublishFinalTx(claimHtlcTx, claimHtlcTx.fee, None)
blockchain ! WatchTxConfirmed(self, claimHtlcTx.tx.txid, nodeParams.channelConf.minDepthBlocks)
@ -1901,7 +1901,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (d.commitments.params.localParams.isInitiator && !shutdownInProgress) {
// TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw?
val currentFeeratePerKw = d.commitments.latest.localCommit.spec.commitTxFeerate
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.params.channelType, d.commitments.latest.capacity, None)
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.channelType, d.commitments.latest.capacity)
if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) {
self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true)
}
@ -1931,7 +1931,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
// note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them
if (d.commitments.params.localParams.isInitiator) {
// we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, None)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, None)
val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx, closingSigned))
goto(NEGOTIATING) using d.copy(closingTxProposed = closingTxProposed1) storing() sending d.localShutdown :: closingSigned :: Nil
} else {
@ -2316,7 +2316,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = {
// TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw?
val commitments = d.commitments.latest
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.params.channelType, commitments.capacity, Some(c))
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.channelType, commitments.capacity)
val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate
val shouldUpdateFee = d.commitments.params.localParams.isInitiator && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)
val shouldClose = !d.commitments.params.localParams.isInitiator &&
@ -2342,7 +2342,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def handleCurrentFeerateDisconnected(c: CurrentFeerates, d: ChannelDataWithCommitments) = {
// TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw?
val commitments = d.commitments.latest
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, d.commitments.params.channelType, commitments.capacity, Some(c))
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.channelType, commitments.capacity)
val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate
// if the network fees are too high we risk to not be able to confirm our current commitment
val shouldClose = networkFeeratePerKw > currentFeeratePerKw &&

View file

@ -197,7 +197,7 @@ trait ErrorHandlers extends CommonHandlers {
val commitment = d.commitments.latest
log.error(s"force-closing with fundingIndex=${commitment.fundingTxIndex}")
val commitTx = commitment.fullySignedLocalCommitTx(keyManager).tx
val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey)
val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, negotiating.closingTxProposed.flatten.map(_.unsignedTx), localCommitPublished = Some(localCommitPublished))
@ -241,7 +241,7 @@ trait ErrorHandlers extends CommonHandlers {
require(commitTx.txid == commitments.remoteCommit.txid, "txid mismatch")
val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d)
context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput, commitTx, d.commitments.params.localParams.isInitiator), "remote-commit"))
val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitments, commitments.remoteCommit, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey)
val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitments, commitments.remoteCommit, commitTx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = negotiating.closingTxProposed.flatten.map(_.unsignedTx), remoteCommitPublished = Some(remoteCommitPublished))
@ -259,7 +259,7 @@ trait ErrorHandlers extends CommonHandlers {
val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d)
context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput, commitTx, d.commitments.params.localParams.isInitiator), "next-remote-commit"))
val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitment, remoteCommit, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf, finalScriptPubKey)
val remoteCommitPublished = Closing.RemoteClose.claimCommitTxOutputs(keyManager, commitment, remoteCommit, commitTx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, mutualCloseProposed = negotiating.closingTxProposed.flatten.map(_.unsignedTx), nextRemoteCommitPublished = Some(remoteCommitPublished))
@ -293,7 +293,7 @@ trait ErrorHandlers extends CommonHandlers {
val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d)
Closing.RevokedClose.getRemotePerCommitmentSecret(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, tx) match {
case Some((commitmentNumber, remotePerCommitmentSecret)) =>
val revokedCommitPublished = Closing.RevokedClose.claimCommitTxOutputs(keyManager, d.commitments.params, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, finalScriptPubKey)
val revokedCommitPublished = Closing.RevokedClose.claimCommitTxOutputs(keyManager, d.commitments.params, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey)
log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx")
context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, tx, Closing.commitTxFee(commitment.commitInput, tx, d.commitments.params.localParams.isInitiator), "revoked-commit"))
val exc = FundingTxSpent(d.channelId, tx.txid)
@ -312,7 +312,7 @@ trait ErrorHandlers extends CommonHandlers {
val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint
val remoteCommitPublished = RemoteCommitPublished(
commitTx = tx,
claimMainOutputTx = Closing.RemoteClose.claimMainOutput(keyManager, d.commitments.params, remotePerCommitmentPoint, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, finalScriptPubKey),
claimMainOutputTx = Closing.RemoteClose.claimMainOutput(keyManager, d.commitments.params, remotePerCommitmentPoint, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey),
claimHtlcTxs = Map.empty,
claimAnchorTxs = List.empty,
irrevocablySpent = Map.empty)

View file

@ -21,7 +21,7 @@ import akka.actor.typed.{ActorRef, Behavior}
import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Transaction}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratePerKw}
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.channel.publish.ReplaceableTxFunder.FundedTx
import fr.acinq.eclair.channel.publish.ReplaceableTxPrePublisher.{ClaimLocalAnchorWithWitnessData, ReplaceableTxWithWitnessData}
import fr.acinq.eclair.channel.publish.TxPublisher.TxPublishContext
@ -46,7 +46,7 @@ object ReplaceableTxPublisher {
// @formatter:off
sealed trait Command
case class Publish(replyTo: ActorRef[TxPublisher.PublishTxResult], cmd: TxPublisher.PublishReplaceableTx) extends Command
case class UpdateConfirmationTarget(confirmBefore: BlockHeight) extends Command
case class UpdateConfirmationTarget(confirmationTarget: ConfirmationTarget) extends Command
case object Stop extends Command
private case class WrappedPreconditionsResult(result: ReplaceableTxPrePublisher.PreconditionsResult) extends Command
@ -75,28 +75,31 @@ object ReplaceableTxPublisher {
}
}
def getFeerate(feeEstimator: FeeEstimator, confirmBefore: BlockHeight, currentBlockHeight: BlockHeight, hasEnoughSafeUtxos: Boolean): FeeratePerKw = {
val remainingBlocks = confirmBefore - currentBlockHeight
if (hasEnoughSafeUtxos) {
val blockTarget = remainingBlocks match {
// If our target is still very far in the future, no need to rush
case t if t >= 144 => 144
case t if t >= 72 => 72
case t if t >= 36 => 36
// However, if we get closer to the target, we start being more aggressive
case t if t >= 18 => 12
case t if t >= 12 => 6
case t if t >= 2 => 2
case _ => 1
}
feeEstimator.getFeeratePerKw(blockTarget)
} else {
// We don't have many safe utxos so we want the transaction to confirm quickly.
if (remainingBlocks <= 1) {
feeEstimator.getFeeratePerKw(1)
} else {
feeEstimator.getFeeratePerKw(2)
}
def getFeerate(feerates: FeeratesPerKw, confirmationTarget: ConfirmationTarget, currentBlockHeight: BlockHeight, hasEnoughSafeUtxos: Boolean): FeeratePerKw = {
confirmationTarget match {
case ConfirmationTarget.Absolute(confirmBefore) =>
// If we have an absolute block height target, we take into account what the current height is and adjust the feerate
val remainingBlocks = confirmBefore - currentBlockHeight
if (hasEnoughSafeUtxos) {
remainingBlocks match {
// If our target is still very far in the future, no need to rush
case t if t >= 36 => feerates.slow
// However, if we get closer to the target, we start being more aggressive
case t if t >= 12 => feerates.medium
case t if t >= 2 => feerates.fast
case _ => feerates.fastest
}
} else {
// We don't have many safe utxos so we want the transaction to confirm quickly.
if (remainingBlocks <= 1) {
feerates.fastest
} else {
feerates.fast
}
}
case ConfirmationTarget.Priority(priority) =>
// If we have a priority target, then the current block height doesn't matter
priority.getFeerate(feerates)
}
}
@ -115,7 +118,8 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
private val log = context.log
private var confirmBefore: BlockHeight = cmd.txInfo.confirmBefore
/** The confirmation target may be updated in some corner cases (e.g. for a htlc if we learn a payment preimage). */
private var confirmationTarget: ConfirmationTarget = cmd.txInfo.confirmationTarget
def checkPreconditions(): Behavior[Command] = {
val prePublisher = context.spawn(ReplaceableTxPrePublisher(nodeParams, bitcoinClient, txPublishContext), "pre-publisher")
@ -127,7 +131,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case ReplaceableTxPrePublisher.PreconditionsFailed(reason) => sendResult(TxPublisher.TxRejected(txPublishContext.id, cmd, reason), None)
}
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop => Behaviors.stopped
}
@ -143,7 +147,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
Behaviors.receiveMessagePartial {
case TimeLocksOk => chooseFeerate(txWithWitnessData)
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop => Behaviors.stopped
}
@ -151,16 +155,16 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
}
def chooseFeerate(txWithWitnessData: ReplaceableTxWithWitnessData): Behavior[Command] = {
context.pipeToSelf(hasEnoughSafeUtxos(nodeParams.onChainFeeConf.feeTargets.safeUtxosThreshold)) {
context.pipeToSelf(hasEnoughSafeUtxos(nodeParams.onChainFeeConf.safeUtxosThreshold)) {
case Success(isSafe) => CheckUtxosResult(isSafe, nodeParams.currentBlockHeight)
case Failure(_) => CheckUtxosResult(isSafe = false, nodeParams.currentBlockHeight) // if we can't check our utxos, we assume the worst
}
Behaviors.receiveMessagePartial {
case CheckUtxosResult(isSafe, currentBlockHeight) =>
val targetFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, currentBlockHeight, isSafe)
val targetFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe)
fund(txWithWitnessData, targetFeerate)
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop => Behaviors.stopped
}
@ -173,14 +177,17 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case WrappedFundingResult(result) =>
result match {
case ReplaceableTxFunder.TransactionReady(tx) =>
log.debug("publishing {} with confirmation target in {} blocks", cmd.desc, confirmBefore - nodeParams.currentBlockHeight)
confirmationTarget match {
case ConfirmationTarget.Absolute(confirmBefore) => log.debug("publishing {} with confirmation target in {} blocks", cmd.desc, confirmBefore - nodeParams.currentBlockHeight)
case ConfirmationTarget.Priority(priority) => log.debug("publishing {} with priority {}", cmd.desc, priority)
}
val txMonitor = context.spawn(MempoolTxMonitor(nodeParams, bitcoinClient, txPublishContext), s"mempool-tx-monitor-${tx.signedTx.txid}")
txMonitor ! MempoolTxMonitor.Publish(context.messageAdapter[MempoolTxMonitor.TxResult](WrappedTxResult), tx.signedTx, cmd.input, cmd.desc, tx.fee)
wait(tx)
case ReplaceableTxFunder.FundingFailed(reason) => sendResult(TxPublisher.TxRejected(txPublishContext.id, cmd, reason), None)
}
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop =>
// We can't stop right now, the child actor is currently funding the transaction and will send its result soon.
@ -203,7 +210,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case _ => true
}
if (shouldRbf) {
context.pipeToSelf(hasEnoughSafeUtxos(nodeParams.onChainFeeConf.feeTargets.safeUtxosThreshold)) {
context.pipeToSelf(hasEnoughSafeUtxos(nodeParams.onChainFeeConf.safeUtxosThreshold)) {
case Success(isSafe) => CheckUtxosResult(isSafe, currentBlockHeight)
case Failure(_) => CheckUtxosResult(isSafe = false, currentBlockHeight) // if we can't check our utxos, we assume the worst
}
@ -216,23 +223,34 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case CheckUtxosResult(isSafe, currentBlockHeight) =>
// We make sure we increase the fees by at least 20% as we get closer to the confirmation target.
val bumpRatio = 1.2
val currentFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, currentBlockHeight, isSafe)
val targetFeerate_opt = if (confirmBefore <= currentBlockHeight + 6) {
log.debug("{} confirmation target is close (in {} blocks): bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate.max(tx.feerate * bumpRatio))
} else if (tx.feerate * bumpRatio <= currentFeerate) {
log.debug("{} confirmation target is in {} blocks: bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate)
} else {
log.debug("{} confirmation target is in {} blocks: no need to bump fees", cmd.desc, confirmBefore - currentBlockHeight)
None
val currentFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe)
val targetFeerate_opt = confirmationTarget match {
case ConfirmationTarget.Absolute(confirmBefore) =>
if (confirmBefore <= currentBlockHeight + 6) {
log.debug("{} confirmation target is close (in {} blocks): bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate.max(tx.feerate * bumpRatio))
} else if (tx.feerate * bumpRatio <= currentFeerate) {
log.debug("{} confirmation target is in {} blocks: bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate)
} else {
log.debug("{} confirmation target is in {} blocks: no need to bump fees", cmd.desc, confirmBefore - currentBlockHeight)
None
}
case ConfirmationTarget.Priority(priority) =>
if (tx.feerate * bumpRatio <= currentFeerate) {
log.debug("{} priority {}: bumping fees", cmd.desc, priority)
Some(currentFeerate)
} else {
log.debug("{} priority {}: no need to bump fees", cmd.desc, priority)
None
}
}
// We avoid a herd effect whenever we fee bump transactions.
targetFeerate_opt.foreach(targetFeerate => timers.startSingleTimer(BumpFeeKey, BumpFee(targetFeerate), (1 + Random.nextLong(nodeParams.channelConf.maxTxPublishRetryDelay.toMillis)).millis))
Behaviors.same
case BumpFee(targetFeerate) => fundReplacement(targetFeerate, tx)
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop => unlockAndStop(cmd.input, Seq(tx.signedTx))
}
@ -257,7 +275,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
timers.startSingleTimer(txResult, 1 second)
Behaviors.same
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop =>
// We can't stop right away, because the child actor may need to unlock utxos first.
@ -298,7 +316,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
Behaviors.same
}
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop =>
// We don't know yet which transaction won, so we try abandoning both and unlocking their utxos.
@ -330,7 +348,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
timers.startSingleTimer(txResult, 1 second)
Behaviors.same
case UpdateConfirmationTarget(target) =>
confirmBefore = target
confirmationTarget = target
Behaviors.same
case Stop =>
// We don't stop right away, because we're cleaning up the failed transaction.

View file

@ -24,6 +24,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Satoshi, Transactio
import fr.acinq.eclair.blockchain.CurrentBlockHeight
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.FullCommitment
import fr.acinq.eclair.transactions.Transactions.{ReplaceableTransactionWithInputInfo, TransactionWithInputInfo}
import fr.acinq.eclair.{BlockHeight, Logs, NodeParams}
@ -156,7 +157,7 @@ object TxPublisher {
def cmd: PublishTx
}
case class FinalAttempt(id: UUID, cmd: PublishFinalTx, actor: ActorRef[FinalTxPublisher.Command]) extends PublishAttempt
case class ReplaceableAttempt(id: UUID, cmd: PublishReplaceableTx, confirmBefore: BlockHeight, actor: ActorRef[ReplaceableTxPublisher.Command]) extends PublishAttempt
case class ReplaceableAttempt(id: UUID, cmd: PublishReplaceableTx, confirmationTarget: ConfirmationTarget, actor: ActorRef[ReplaceableTxPublisher.Command]) extends PublishAttempt
// @formatter:on
/**
@ -220,23 +221,33 @@ private class TxPublisher(nodeParams: NodeParams, factory: TxPublisher.ChildFact
}
case cmd: PublishReplaceableTx =>
val proposedConfirmationTarget = cmd.txInfo.confirmBefore
val proposedConfirmationTarget = cmd.txInfo.confirmationTarget
val attempts = pending.getOrElse(cmd.input, PublishAttempts.empty)
attempts.replaceableAttempt_opt match {
case Some(currentAttempt) =>
if (currentAttempt.cmd.txInfo.tx.txOut.headOption.map(_.publicKeyScript) != cmd.txInfo.tx.txOut.headOption.map(_.publicKeyScript)) {
log.error("replaceable {} sends to a different address than the previous attempt, this should not happen: proposed={}, previous={}", currentAttempt.cmd.desc, cmd.txInfo, currentAttempt.cmd.txInfo)
}
val currentConfirmationTarget = currentAttempt.confirmBefore
if (currentConfirmationTarget <= proposedConfirmationTarget) {
log.debug("not publishing replaceable {} spending {}:{} with confirmation target={}, publishing is already in progress with confirmation target={}", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget)
Behaviors.same
} else {
val currentConfirmationTarget = currentAttempt.confirmationTarget
def updateConfirmationTarget() = {
log.info("replaceable {} spending {}:{} has new confirmation target={} (previous={})", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget)
currentAttempt.actor ! ReplaceableTxPublisher.UpdateConfirmationTarget(proposedConfirmationTarget)
val attempts2 = attempts.copy(replaceableAttempt_opt = Some(currentAttempt.copy(confirmBefore = proposedConfirmationTarget)))
val attempts2 = attempts.copy(replaceableAttempt_opt = Some(currentAttempt.copy(confirmationTarget = proposedConfirmationTarget)))
run(pending + (cmd.input -> attempts2), retryNextBlock, channelContext)
}
(currentConfirmationTarget, proposedConfirmationTarget) match {
case (ConfirmationTarget.Absolute(currentConfirmBefore), ConfirmationTarget.Absolute(proposedConfirmBefore)) if proposedConfirmBefore < currentConfirmBefore =>
// The proposed block target is closer than what it was
updateConfirmationTarget()
case (_: ConfirmationTarget.Priority, ConfirmationTarget.Absolute(proposedConfirmBefore)) =>
// Switch from relative priority mode to absolute blockheight mode
updateConfirmationTarget()
case _ =>
log.debug("not publishing replaceable {} spending {}:{} with confirmation target={}, publishing is already in progress with confirmation target={}", cmd.desc, cmd.input.txid, cmd.input.index, proposedConfirmationTarget, currentConfirmationTarget)
Behaviors.same
}
case None =>
val publishId = UUID.randomUUID()
log.info("publishing replaceable {} spending {}:{} with id={} ({} other attempts)", cmd.desc, cmd.input.txid, cmd.input.index, publishId, attempts.count)

View file

@ -164,8 +164,8 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnchainP
} else {
randomBytes32()
}
val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget))
val commitTxFeerate = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, channelType, c.fundingAmount, None)
val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates))
val commitTxFeerate = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelType, c.fundingAmount)
log.info(s"requesting a new channel with type=$channelType fundingAmount=${c.fundingAmount} dualFunded=$dualFunded pushAmount=${c.pushAmount_opt} fundingFeerate=$fundingTxFeerate temporaryChannelId=$temporaryChannelId localParams=$localParams")
channel ! INPUT_INIT_CHANNEL_INITIATOR(temporaryChannelId, c.fundingAmount, dualFunded, commitTxFeerate, fundingTxFeerate, c.pushAmount_opt, requireConfirmedInputs, localParams, d.peerConnection, d.remoteInit, c.channelFlags_opt.getOrElse(nodeParams.channelConf.channelFlags), channelConfig, channelType, c.channelOrigin, replyTo)
stay() using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel))

View file

@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.balance.CheckBalance.{CorrectedOnChainBalance, GlobalBalance, OffChainBalance}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{ShaChain, Sphinx}
import fr.acinq.eclair.db.FailureType.FailureType
@ -232,26 +232,26 @@ object TransactionWithInputInfoSerializer extends MinimalSerializer({
JField("tx", JString(x.tx.toString())),
JField("paymentHash", JString(x.paymentHash.toString())),
JField("htlcId", JLong(x.htlcId)),
JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong))
JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong))
))
case x: HtlcTimeoutTx => JObject(List(
JField("txid", JString(x.tx.txid.toHex)),
JField("tx", JString(x.tx.toString())),
JField("htlcId", JLong(x.htlcId)),
JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong))
JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong))
))
case x: ClaimHtlcSuccessTx => JObject(List(
JField("txid", JString(x.tx.txid.toHex)),
JField("tx", JString(x.tx.toString())),
JField("paymentHash", JString(x.paymentHash.toString())),
JField("htlcId", JLong(x.htlcId)),
JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong))
JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong))
))
case x: ClaimHtlcTx => JObject(List(
JField("txid", JString(x.tx.txid.toHex)),
JField("tx", JString(x.tx.toString())),
JField("htlcId", JLong(x.htlcId)),
JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong))
JField("confirmBeforeBlock", JLong(x.confirmationTarget.confirmBefore.toLong))
))
case x: ClosingTx =>
val txFields = List(
@ -271,7 +271,11 @@ object TransactionWithInputInfoSerializer extends MinimalSerializer({
case x: ReplaceableTransactionWithInputInfo => JObject(List(
JField("txid", JString(x.tx.txid.toHex)),
JField("tx", JString(x.tx.toString())),
JField("confirmBeforeBlock", JLong(x.confirmBefore.toLong))
x.confirmationTarget match {
case ConfirmationTarget.Absolute(confirmBefore) => JField("confirmBeforeBlock", JLong(confirmBefore.toLong))
case ConfirmationTarget.Priority(priority) => JField("confirmPriority", JString(priority.toString))
}
))
case x: TransactionWithInputInfo => JObject(List(
JField("txid", JString(x.tx.txid.toHex)),

View file

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, ripemd160}
import fr.acinq.bitcoin.scalacompat.Script._
import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.transactions.CommitmentOutput._
import fr.acinq.eclair.transactions.Scripts._
import fr.acinq.eclair.wire.protocol.UpdateAddHtlc
@ -121,7 +121,7 @@ object Transactions {
}
sealed trait ReplaceableTransactionWithInputInfo extends TransactionWithInputInfo {
/** Block before which the transaction must be confirmed. */
def confirmBefore: BlockHeight
def confirmationTarget: ConfirmationTarget
}
case class SpliceTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "splice-tx" }
@ -148,16 +148,20 @@ object Transactions {
case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
}
}
override def confirmationTarget: ConfirmationTarget.Absolute
}
case class HtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmBefore: BlockHeight) extends HtlcTx { override def desc: String = "htlc-success" }
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends HtlcTx { override def desc: String = "htlc-timeout" }
case class HtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-success" }
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-timeout" }
case class HtlcDelayedTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed" }
sealed trait ClaimHtlcTx extends ReplaceableTransactionWithInputInfo { def htlcId: Long }
case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmBefore: BlockHeight) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" }
sealed trait ClaimHtlcTx extends ReplaceableTransactionWithInputInfo {
def htlcId: Long
override def confirmationTarget: ConfirmationTarget.Absolute
}
case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" }
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" }
sealed trait ClaimAnchorOutputTx extends TransactionWithInputInfo
case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmBefore: BlockHeight) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" }
case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" }
case class ClaimRemoteAnchorOutputTx(input: InputInfo, tx: Transaction) extends ClaimAnchorOutputTx { override def desc: String = "remote-anchor" }
sealed trait ClaimRemoteCommitMainOutputTx extends TransactionWithInputInfo
case class ClaimP2WPKHOutputTx(input: InputInfo, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main" }
@ -476,7 +480,7 @@ object Transactions {
txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil,
lockTime = htlc.cltvExpiry.toLong
)
Right(HtlcTimeoutTx(input, tx, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)))
Right(HtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
}
}
@ -503,7 +507,7 @@ object Transactions {
txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil,
lockTime = 0
)
Right(HtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)))
Right(HtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
}
}
@ -550,14 +554,14 @@ object Transactions {
txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil,
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
lockTime = 0)
val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)), PlaceHolderSig, ByteVector32.Zeroes).tx.weight()
val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes).tx.weight()
val fee = weight2fee(feeratePerKw, weight)
val amount = input.txOut.amount - fee
if (amount < localDustLimit) {
Left(AmountBelowDustLimit)
} else {
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)))
Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
}
case None => Left(OutputNotFound)
}
@ -585,14 +589,14 @@ object Transactions {
txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil,
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
lockTime = htlc.cltvExpiry.toLong)
val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)), PlaceHolderSig).tx.weight()
val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig).tx.weight()
val fee = weight2fee(feeratePerKw, weight)
val amount = input.txOut.amount - fee
if (amount < localDustLimit) {
Left(AmountBelowDustLimit)
} else {
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, BlockHeight(htlc.cltvExpiry.toLong)))
Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
}
case None => Left(OutputNotFound)
}
@ -705,8 +709,8 @@ object Transactions {
}
}
def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmBefore: BlockHeight): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = {
makeClaimAnchorOutputTx(commitTx, localFundingPubkey).map { case (input, tx) => ClaimLocalAnchorOutputTx(input, tx, confirmBefore) }
def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmationTarget: ConfirmationTarget): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = {
makeClaimAnchorOutputTx(commitTx, localFundingPubkey).map { case (input, tx) => ClaimLocalAnchorOutputTx(input, tx, confirmationTarget) }
}
def makeClaimRemoteAnchorOutputTx(commitTx: Transaction, remoteFundingPubkey: PublicKey): Either[TxGenerationSkipped, ClaimRemoteAnchorOutputTx] = {

View file

@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version0
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
@ -126,6 +127,8 @@ private[channel] object ChannelCodecs0 {
("txOut" | txOutCodec) ::
("redeemScript" | varsizebinarydata)).as[InputInfo].decodeOnly
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
// We can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a
// downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing.
// We can also safely set confirmBefore = 0: we will simply use a high feerate to make these transactions confirm
@ -133,10 +136,10 @@ private[channel] object ChannelCodecs0 {
// complexity and real world impact.
val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16)
.typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx])
.typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx])
.typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx])
.typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx])

View file

@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version0
import com.softwaremill.quicklens._
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OP_CHECKMULTISIG, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.CommitmentSpec
@ -58,8 +59,8 @@ private[channel] object ChannelTypes0 {
// the channel will put a watch at start-up which will make us fetch the spending transaction.
val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
val claimMainDelayedOutputTxNew = claimMainDelayedOutputTx.map(tx => ClaimLocalDelayedOutputTx(getPartialInputInfo(commitTx, tx), tx))
val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, BlockHeight(0)))
val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0)))
val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, ConfirmationTarget.Absolute(BlockHeight(0))))
val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0))))
val htlcTxsNew = (htlcSuccessTxsNew ++ htlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap
val claimHtlcDelayedTxsNew = claimHtlcDelayedTxs.map(tx => {
val htlcTx = htlcTxs.find(_.txid == tx.txIn.head.outPoint.txid)
@ -78,8 +79,8 @@ private[channel] object ChannelTypes0 {
// the channel will put a watch at start-up which will make us fetch the spending transaction.
val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) }
val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx))
val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0)))
val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, BlockHeight(0)))
val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0))))
val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0))))
val claimHtlcTxsNew = (claimHtlcSuccessTxsNew ++ claimHtlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap
channel.RemoteCommitPublished(commitTx, claimMainOutputTxNew, claimHtlcTxsNew, Nil, irrevocablySpentNew)
}

View file

@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version1
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
@ -101,14 +102,16 @@ private[channel] object ChannelCodecs1 {
("txOut" | txOutCodec) ::
("redeemScript" | lengthDelimited(bytes))).as[InputInfo]
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
// NB: we can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a
// downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing.
val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16)
.typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx])
.typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTargetBefore" | defaultConfirmationTarget)).as[HtlcSuccessTx])
.typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx])
.typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx])
.typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx])
.typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx])
.typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx])
.typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx])

View file

@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version2
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.LocalFundingStatus.SingleFundedUnconfirmedFundingTx
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
@ -110,39 +111,24 @@ private[channel] object ChannelCodecs2 {
("amount" | satoshi) ::
("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo]
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx]
val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx]
val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx]
val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]
val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]
val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]
val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx]
val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]
val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]
val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget])).as[ClaimLocalAnchorOutputTx]
val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx]
val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]
val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16)
.typecase(0x01, commitTxCodec)
.typecase(0x02, htlcSuccessTxCodec)
.typecase(0x03, htlcTimeoutTxCodec)
.typecase(0x04, claimHtlcSuccessTxCodec)
.typecase(0x05, claimHtlcTimeoutTxCodec)
.typecase(0x06, claimP2WPKHOutputTxCodec)
.typecase(0x07, claimLocalDelayedOutputTxCodec)
.typecase(0x08, mainPenaltyTxCodec)
.typecase(0x09, htlcPenaltyTxCodec)
.typecase(0x10, closingTxCodec)
.typecase(0x11, claimLocalAnchorOutputTxCodec)
.typecase(0x12, claimRemoteAnchorOutputTxCodec)
.typecase(0x13, claimRemoteDelayedOutputTxCodec)
.typecase(0x14, claimHtlcDelayedOutputPenaltyTxCodec)
.typecase(0x15, htlcDelayedTxCodec)
val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8)
.typecase(0x01, claimP2WPKHOutputTxCodec)
.typecase(0x02, claimRemoteDelayedOutputTxCodec)

View file

@ -19,6 +19,7 @@ package fr.acinq.eclair.wire.internal.channel.version3
import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt}
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.LocalFundingStatus._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
@ -122,25 +123,28 @@ private[channel] object ChannelCodecs3 {
("amount" | satoshi) ::
("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo]
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
private val blockHeightConfirmationTarget: Codec[ConfirmationTarget.Absolute] = blockHeight.map(ConfirmationTarget.Absolute).decodeOnly
val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcTimeoutTx]
private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx]
private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx]
private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]
private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx]
val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx]
private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcTimeoutTx]
private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcSuccessTx]
private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx]
private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx]
private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx]
private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]
val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]
val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]
val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx]
val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]
val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]
val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | blockHeight)).as[ClaimLocalAnchorOutputTx]
private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx]
private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx]
val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx]
val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]

View file

@ -3,6 +3,8 @@ package fr.acinq.eclair.wire.internal.channel.version4
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut}
import fr.acinq.eclair.blockchain.fee
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget}
import fr.acinq.eclair.channel.LocalFundingStatus._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit
@ -116,26 +118,38 @@ private[channel] object ChannelCodecs4 {
("amount" | satoshi) ::
("scriptPubKey" | lengthDelimited(bytes))).as[OutputInfo]
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
private val blockHeightConfirmationTarget: Codec[ConfirmationTarget.Absolute] = blockHeight.xmap(ConfirmationTarget.Absolute, _.confirmBefore)
private val confirmationPriority: Codec[ConfirmationPriority] = discriminated[ConfirmationPriority].by(uint8)
.typecase(0x01, provide(ConfirmationPriority.Slow))
.typecase(0x02, provide(ConfirmationPriority.Medium))
.typecase(0x03, provide(ConfirmationPriority.Fast))
private val priorityConfirmationTarget: Codec[ConfirmationTarget.Priority] = confirmationPriority.xmap(ConfirmationTarget.Priority, _.priority)
private val confirmationTarget: Codec[ConfirmationTarget] = discriminated[ConfirmationTarget].by(uint8)
.typecase(0x00, blockHeightConfirmationTarget)
.typecase(0x01, priorityConfirmationTarget)
val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[HtlcTimeoutTx]
private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcSuccessTx]
private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[HtlcTimeoutTx]
val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx]
val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx]
private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]
private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx]
val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx]
private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[LegacyClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | blockHeight)).as[ClaimHtlcTimeoutTx]
private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcSuccessTx]
private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimHtlcTimeoutTx]
private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]
val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx]
val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx]
private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx]
private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]
val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]
val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]
val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx]
val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]
val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]
val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | blockHeight)).as[ClaimLocalAnchorOutputTx]
private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmBefore" | provide(BlockHeight(0)))).as[ClaimLocalAnchorOutputTx]
val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx]
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | confirmationTarget)).as[ClaimLocalAnchorOutputTx]
private val claimLocalAnchorOutputTxBlockHeightConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx]
private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx]
private val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx]
val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]
val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8)
@ -144,7 +158,8 @@ private[channel] object ChannelCodecs4 {
val claimAnchorOutputTxCodec: Codec[ClaimAnchorOutputTx] = discriminated[ClaimAnchorOutputTx].by(uint8)
// Important: order matters!
.typecase(0x11, claimLocalAnchorOutputTxCodec)
.typecase(0x12, claimLocalAnchorOutputTxCodec)
.typecase(0x11, claimLocalAnchorOutputTxBlockHeightConfirmCodec)
.typecase(0x01, claimLocalAnchorOutputTxNoConfirmCodec)
.typecase(0x02, claimRemoteAnchorOutputTxCodec)

View file

@ -21,13 +21,14 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, SatoshiLong}
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features._
import fr.acinq.eclair.blockchain.fee.{DustTolerance, FeeratePerByte, FeeratePerKw, FeerateTolerance}
import fr.acinq.eclair.TestConstants.feeratePerKw
import fr.acinq.eclair.blockchain.fee.{DustTolerance, FeeratePerByte, FeeratePerKw, FeerateTolerance, FeeratesPerKw}
import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager}
import org.scalatest.funsuite.AnyFunSuite
import scodec.bits.{ByteVector, HexStringSyntax}
import java.util.UUID
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import scala.jdk.CollectionConverters._
import scala.util.Try
@ -37,11 +38,11 @@ class StartupSpec extends AnyFunSuite {
def makeNodeParamsWithDefaults(conf: Config): NodeParams = {
val blockCount = new AtomicLong(0)
val feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw))
val nodeKeyManager = new LocalNodeKeyManager(randomBytes32(), chainHash = Block.TestnetGenesisBlock.hash)
val channelKeyManager = new LocalChannelKeyManager(randomBytes32(), chainHash = Block.TestnetGenesisBlock.hash)
val feeEstimator = new TestFeeEstimator()
val db = TestDatabases.inMemoryDb()
NodeParams.makeNodeParams(conf, UUID.fromString("01234567-0123-4567-89ab-0123456789ab"), nodeKeyManager, channelKeyManager, None, db, blockCount, feeEstimator)
NodeParams.makeNodeParams(conf, UUID.fromString("01234567-0123-4567-89ab-0123456789ab"), nodeKeyManager, channelKeyManager, None, db, blockCount, feerates)
}
test("check configuration") {

View file

@ -35,7 +35,7 @@ import org.scalatest.Tag
import scodec.bits.{ByteVector, HexStringSyntax}
import java.util.UUID
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import scala.concurrent.duration._
/**
@ -65,7 +65,7 @@ object TestConstants {
// @formatter:on
}
val blockchainWatchdogSources = Seq(
private val blockchainWatchdogSources = Seq(
"bitcoinheaders.net",
"blockcypher.com",
"blockstream.info",
@ -82,6 +82,7 @@ object TestConstants {
nodeKeyManager,
channelKeyManager,
blockHeight = new AtomicLong(defaultBlockHeight),
feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)),
alias = "alice",
color = Color(1, 2, 3),
publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil,
@ -133,8 +134,8 @@ object TestConstants {
remoteRbfLimits = RemoteRbfLimits(5, 0)
),
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 36, 12, 18, 0),
feeEstimator = new TestFeeEstimator,
feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium),
safeUtxosThreshold = 0,
spendAnchorWithoutHtlcs = true,
closeOnOfflineMismatch = true,
updateFeeMinDiffRatio = 0.1,
@ -243,6 +244,7 @@ object TestConstants {
nodeKeyManager,
channelKeyManager,
blockHeight = new AtomicLong(defaultBlockHeight),
feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)),
alias = "bob",
color = Color(4, 5, 6),
publicAddresses = NodeAddress.fromParts("localhost", 9732).get :: Nil,
@ -291,8 +293,8 @@ object TestConstants {
remoteRbfLimits = RemoteRbfLimits(5, 0)
),
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 36, 12, 18, 0),
feeEstimator = new TestFeeEstimator,
feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium),
safeUtxosThreshold = 0,
spendAnchorWithoutHtlcs = true,
closeOnOfflineMismatch = true,
updateFeeMinDiffRatio = 0.1,

View file

@ -1,47 +0,0 @@
/*
* Copyright 2019 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.acinq.eclair
import fr.acinq.eclair.TestConstants.feeratePerKw
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratePerKB, FeeratePerKw, FeeratesPerKw}
class TestFeeEstimator(initialFeerate: FeeratePerKw = feeratePerKw) extends FeeEstimator {
private var currentFeerates = FeeratesPerKw.single(initialFeerate)
// @formatter:off
override def getFeeratePerKb(target: Int): FeeratePerKB = FeeratePerKB(currentFeerates.feePerBlock(target))
override def getFeeratePerKw(target: Int): FeeratePerKw = currentFeerates.feePerBlock(target)
override def getMempoolMinFeeratePerKw(): FeeratePerKw = currentFeerates.mempoolMinFee
// @formatter:on
def setFeerate(target: Int, feerate: FeeratePerKw): Unit = {
target match {
case 1 => currentFeerates = currentFeerates.copy(block_1 = feerate)
case 2 => currentFeerates = currentFeerates.copy(blocks_2 = feerate)
case t if t <= 6 => currentFeerates = currentFeerates.copy(blocks_6 = feerate)
case t if t <= 12 => currentFeerates = currentFeerates.copy(blocks_12 = feerate)
case t if t <= 36 => currentFeerates = currentFeerates.copy(blocks_36 = feerate)
case t if t <= 72 => currentFeerates = currentFeerates.copy(blocks_72 = feerate)
case t if t <= 144 => currentFeerates = currentFeerates.copy(blocks_144 = feerate)
case _ => currentFeerates = currentFeerates.copy(blocks_1008 = feerate)
}
}
def setFeerate(feeratesPerKw: FeeratesPerKw): Unit = {
currentFeerates = feeratesPerKw
}
}

View file

@ -74,26 +74,22 @@ class BitcoinCoreFeeProviderSpec extends TestKitBaseClass with BitcoindService w
)
val ref = FeeratesPerKB(
mempoolMinFee = FeeratePerKB(300 sat),
block_1 = fees(1),
blocks_2 = fees(2),
blocks_6 = fees(6),
blocks_12 = fees(12),
blocks_36 = fees(36),
blocks_72 = fees(72),
blocks_144 = fees(144),
blocks_1008 = fees(1008)
minimum = FeeratePerKB(300 sat),
fastest = fees(1),
fast = fees(2),
medium = fees(12),
slow = fees(1008)
)
val mockBitcoinClient = createMockBitcoinClient(fees, ref.mempoolMinFee)
val mockBitcoinClient = createMockBitcoinClient(fees, ref.minimum)
val mockProvider = BitcoinCoreFeeProvider(mockBitcoinClient, FeeratesPerKB(FeeratePerKB(1 sat), FeeratePerKB(1 sat), FeeratePerKB(2 sat), FeeratePerKB(3 sat), FeeratePerKB(4 sat), FeeratePerKB(5 sat), FeeratePerKB(6 sat), FeeratePerKB(7 sat), FeeratePerKB(8 sat)))
val mockProvider = BitcoinCoreFeeProvider(mockBitcoinClient, FeeratesPerKB(minimum = FeeratePerKB(1 sat), slow = FeeratePerKB(2 sat), medium = FeeratePerKB(3 sat), fast = FeeratePerKB(4 sat), fastest = FeeratePerKB(5 sat)))
mockProvider.getFeerates.pipeTo(sender.ref)
assert(sender.expectMsgType[FeeratesPerKB] == ref)
}
test("get mempool minimum fee") {
val regtestProvider = BitcoinCoreFeeProvider(bitcoinrpcclient, FeeratesPerKB(FeeratePerKB(1 sat), FeeratePerKB(1 sat), FeeratePerKB(2 sat), FeeratePerKB(3 sat), FeeratePerKB(4 sat), FeeratePerKB(5 sat), FeeratePerKB(6 sat), FeeratePerKB(7 sat), FeeratePerKB(8 sat)))
val regtestProvider = BitcoinCoreFeeProvider(bitcoinrpcclient, FeeratesPerKB(minimum = FeeratePerKB(1 sat), slow = FeeratePerKB(2 sat), medium = FeeratePerKB(3 sat), fast = FeeratePerKB(4 sat), fastest = FeeratePerKB(5 sat)))
val sender = TestProbe()
regtestProvider.mempoolMinFee().pipeTo(sender.ref)
val mempoolMinFee = sender.expectMsgType[FeeratePerKB]

View file

@ -42,7 +42,7 @@ class FallbackFeeProviderSpec extends AnyFunSuite {
def dummyFeerate = FeeratePerKB(1000.sat + Random.nextInt(10000).sat)
def dummyFeerates = FeeratesPerKB(dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate)
def dummyFeerates = FeeratesPerKB(dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate, dummyFeerate)
def await[T](f: Future[T]): T = Await.result(f, 3 seconds)
@ -71,11 +71,12 @@ class FallbackFeeProviderSpec extends AnyFunSuite {
}
test("ensure minimum feerate") {
val constantFeeProvider = ConstantFeeProvider(FeeratesPerKB(FeeratePerKB(64000 sat), FeeratePerKB(32000 sat), FeeratePerKB(16000 sat), FeeratePerKB(8000 sat), FeeratePerKB(4000 sat), FeeratePerKB(2000 sat), FeeratePerKB(1500 sat), FeeratePerKB(1000 sat), FeeratePerKB(1000 sat)))
val constantFeerates = FeeratesPerKB(minimum = FeeratePerKB(1000 sat), slow = FeeratePerKB(8_000 sat), medium = FeeratePerKB(16_000 sat), fast = FeeratePerKB(32_000 sat), fastest = FeeratePerKB(64_000 sat))
val constantFeeProvider = ConstantFeeProvider(constantFeerates)
val minFeeratePerByte = FeeratePerByte(2 sat)
val minFeeratePerKB = FeeratePerKB(minFeeratePerByte)
val fallbackFeeProvider = new FallbackFeeProvider(constantFeeProvider :: Nil, minFeeratePerByte)
assert(await(fallbackFeeProvider.getFeerates) == FeeratesPerKB(FeeratePerKB(64000 sat), FeeratePerKB(32000 sat), FeeratePerKB(16000 sat), FeeratePerKB(8000 sat), FeeratePerKB(4000 sat), minFeeratePerKB, minFeeratePerKB, minFeeratePerKB, minFeeratePerKB))
assert(await(fallbackFeeProvider.getFeerates) == constantFeerates.copy(minimum = minFeeratePerKB))
}
}

View file

@ -1,130 +0,0 @@
/*
* Copyright 2021 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.acinq.eclair.blockchain.fee
import fr.acinq.bitcoin.scalacompat.SatoshiLong
import fr.acinq.eclair.blockchain.CurrentFeerates
import fr.acinq.eclair.channel.ChannelTypes
import fr.acinq.eclair.{TestFeeEstimator, randomKey}
import org.scalatest.funsuite.AnyFunSuite
class FeeEstimatorSpec extends AnyFunSuite {
val defaultFeerateTolerance = FeerateTolerance(0.5, 2.0, FeeratePerKw(2500 sat), DustTolerance(15000 sat, closeOnUpdateFeeOverflow = false))
test("should update fee when diff ratio exceeded") {
val feeConf = OnChainFeeConf(FeeTargets(1, 1, 1, 1, 1, 1), new TestFeeEstimator(), spendAnchorWithoutHtlcs = true, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1000 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(900 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1100 sat)))
assert(feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(899 sat)))
assert(feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1101 sat)))
}
test("get commitment feerate") {
val feeEstimator = new TestFeeEstimator()
val channelType = ChannelTypes.Standard()
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1, 1), feeEstimator, spendAnchorWithoutHtlcs = true, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = FeeratePerKw(5000 sat)))
assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelType, 100000 sat, None) == FeeratePerKw(5000 sat))
val currentFeerates = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = FeeratePerKw(4000 sat)))
assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelType, 100000 sat, Some(currentFeerates)) == FeeratePerKw(4000 sat))
}
test("get commitment feerate (anchor outputs)") {
val feeEstimator = new TestFeeEstimator()
val defaultNodeId = randomKey().publicKey
val defaultMaxCommitFeerate = defaultFeerateTolerance.anchorOutputMaxCommitFeerate
val overrideNodeId = randomKey().publicKey
val overrideMaxCommitFeerate = defaultMaxCommitFeerate * 2
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1, 1), feeEstimator, spendAnchorWithoutHtlcs = true, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map(overrideNodeId -> defaultFeerateTolerance.copy(anchorOutputMaxCommitFeerate = overrideMaxCommitFeerate)))
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, None) == defaultMaxCommitFeerate / 2)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, None) == defaultMaxCommitFeerate / 2)
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 2, mempoolMinFee = FeeratePerKw(250 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, None) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, None) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, None) == overrideMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, None) == overrideMaxCommitFeerate)
val currentFeerates1 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, Some(currentFeerates1)) == defaultMaxCommitFeerate / 2)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, Some(currentFeerates1)) == defaultMaxCommitFeerate / 2)
val currentFeerates2 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 1.5, mempoolMinFee = FeeratePerKw(250 sat)))
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, Some(currentFeerates2)) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, Some(currentFeerates2)) == defaultMaxCommitFeerate)
val highFeerates = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(mempoolMinFee = FeeratePerKw(10000 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25)
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(mempoolMinFee = FeeratePerKw(10000 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25)
}
test("fee difference too high") {
val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat), DustTolerance(25000 sat, closeOnUpdateFeeOverflow = false))
val channelType = ChannelTypes.Standard()
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(channelType, 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)),
(FeeratePerKw(500 sat), FeeratePerKw(2500 sat)),
(FeeratePerKw(500 sat), FeeratePerKw(10000 sat)),
(FeeratePerKw(500 sat), FeeratePerKw(10001 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(10000 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(10001 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1250 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1249 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1000 sat)),
(FeeratePerKw(1000 sat), FeeratePerKw(500 sat)),
(FeeratePerKw(1000 sat), FeeratePerKw(499 sat)),
)
testCases.foreach { case (networkFeerate, proposedFeerate) =>
assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputs(), networkFeerate, proposedFeerate))
assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), networkFeerate, proposedFeerate))
}
}
}

View file

@ -0,0 +1,129 @@
/*
* Copyright 2021 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.acinq.eclair.blockchain.fee
import fr.acinq.bitcoin.scalacompat.SatoshiLong
import fr.acinq.eclair.channel.ChannelTypes
import fr.acinq.eclair.{TestConstants, randomKey}
import org.scalatest.funsuite.AnyFunSuite
import java.util.concurrent.atomic.AtomicReference
class OnChainFeeConfSpec extends AnyFunSuite {
private val defaultFeeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium)
private val defaultFeerateTolerance = FeerateTolerance(0.5, 2.0, FeeratePerKw(2500 sat), DustTolerance(15000 sat, closeOnUpdateFeeOverflow = false))
test("should update fee when diff ratio exceeded") {
val feeConf = OnChainFeeConf(defaultFeeTargets, safeUtxosThreshold = 0, spendAnchorWithoutHtlcs = true, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1000 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(900 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1100 sat)))
assert(feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(899 sat)))
assert(feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1101 sat)))
}
test("get commitment feerate") {
val channelType = ChannelTypes.Standard()
val feeConf = OnChainFeeConf(defaultFeeTargets, safeUtxosThreshold = 0, spendAnchorWithoutHtlcs = true, 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, channelType, 100000 sat) == FeeratePerKw(5000 sat))
val feerates2 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = FeeratePerKw(4000 sat))
assert(feeConf.getCommitmentFeerate(feerates2, randomKey().publicKey, channelType, 100000 sat) == FeeratePerKw(4000 sat))
}
test("get commitment feerate (anchor outputs)") {
val defaultNodeId = randomKey().publicKey
val defaultMaxCommitFeerate = defaultFeerateTolerance.anchorOutputMaxCommitFeerate
val overrideNodeId = randomKey().publicKey
val overrideMaxCommitFeerate = defaultMaxCommitFeerate * 2
val feeConf = OnChainFeeConf(defaultFeeTargets, safeUtxosThreshold = 0, spendAnchorWithoutHtlcs = true, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map(overrideNodeId -> defaultFeerateTolerance.copy(anchorOutputMaxCommitFeerate = overrideMaxCommitFeerate)))
val feerates1 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = defaultMaxCommitFeerate / 2, minimum = FeeratePerKw(250 sat))
assert(feeConf.getCommitmentFeerate(feerates1, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == defaultMaxCommitFeerate / 2)
assert(feeConf.getCommitmentFeerate(feerates1, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == defaultMaxCommitFeerate / 2)
val feerates2 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = defaultMaxCommitFeerate * 2, minimum = FeeratePerKw(250 sat))
assert(feeConf.getCommitmentFeerate(feerates2, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(feerates2, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(feerates2, overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == overrideMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(feerates2, overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == overrideMaxCommitFeerate)
val feerates3 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = defaultMaxCommitFeerate / 2, minimum = FeeratePerKw(250 sat))
assert(feeConf.getCommitmentFeerate(feerates3, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == defaultMaxCommitFeerate / 2)
assert(feeConf.getCommitmentFeerate(feerates3, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == defaultMaxCommitFeerate / 2)
val feerates4 = FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(fast = defaultMaxCommitFeerate * 1.5, minimum = FeeratePerKw(250 sat))
assert(feeConf.getCommitmentFeerate(feerates4, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == defaultMaxCommitFeerate)
assert(feeConf.getCommitmentFeerate(feerates4, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == defaultMaxCommitFeerate)
val feerates5 = FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(minimum = FeeratePerKw(10000 sat))
assert(feeConf.getCommitmentFeerate(feerates5, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates5, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates5, overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates5, overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
val feerates6 = FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(minimum = FeeratePerKw(10000 sat))
assert(feeConf.getCommitmentFeerate(feerates6, defaultNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates6, defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates6, overrideNodeId, ChannelTypes.AnchorOutputs(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
assert(feeConf.getCommitmentFeerate(feerates6, overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), 100000 sat) == FeeratePerKw(10000 sat) * 1.25)
}
test("fee difference too high") {
val tolerance = FeerateTolerance(ratioLow = 0.5, ratioHigh = 4.0, anchorOutputMaxCommitFeerate = FeeratePerKw(2500 sat), DustTolerance(25000 sat, closeOnUpdateFeeOverflow = false))
val channelType = ChannelTypes.Standard()
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(channelType, 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)),
(FeeratePerKw(500 sat), FeeratePerKw(2500 sat)),
(FeeratePerKw(500 sat), FeeratePerKw(10000 sat)),
(FeeratePerKw(500 sat), FeeratePerKw(10001 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(10000 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(10001 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1250 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1249 sat)),
(FeeratePerKw(2500 sat), FeeratePerKw(1000 sat)),
(FeeratePerKw(1000 sat), FeeratePerKw(500 sat)),
(FeeratePerKw(1000 sat), FeeratePerKw(499 sat)),
)
testCases.foreach { case (networkFeerate, proposedFeerate) =>
assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputs(), networkFeerate, proposedFeerate))
assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(), networkFeerate, proposedFeerate))
}
}
}

View file

@ -27,11 +27,11 @@ class SmoothFeeProviderSpec extends AnyFunSuite {
test("smooth fee rates") {
val rates = Array(
FeeratesPerKB(FeeratePerKB(50 sat), FeeratePerKB(100 sat), FeeratePerKB(200 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(650 sat), FeeratePerKB(700 sat)),
FeeratesPerKB(FeeratePerKB(60 sat), FeeratePerKB(200 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(700 sat), FeeratePerKB(750 sat), FeeratePerKB(800 sat)),
FeeratesPerKB(FeeratePerKB(70 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(700 sat), FeeratePerKB(800 sat), FeeratePerKB(850 sat), FeeratePerKB(900 sat)),
FeeratesPerKB(FeeratePerKB(70 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(700 sat), FeeratePerKB(800 sat), FeeratePerKB(850 sat), FeeratePerKB(900 sat)),
FeeratesPerKB(FeeratePerKB(70 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(700 sat), FeeratePerKB(800 sat), FeeratePerKB(850 sat), FeeratePerKB(900 sat))
FeeratesPerKB(minimum = FeeratePerKB(50 sat), fastest = FeeratePerKB(700 sat), fast = FeeratePerKB(650 sat), medium = FeeratePerKB(500 sat), slow = FeeratePerKB(400 sat)),
FeeratesPerKB(minimum = FeeratePerKB(60 sat), fastest = FeeratePerKB(800 sat), fast = FeeratePerKB(750 sat), medium = FeeratePerKB(600 sat), slow = FeeratePerKB(500 sat)),
FeeratesPerKB(minimum = FeeratePerKB(70 sat), fastest = FeeratePerKB(900 sat), fast = FeeratePerKB(850 sat), medium = FeeratePerKB(700 sat), slow = FeeratePerKB(600 sat)),
FeeratesPerKB(minimum = FeeratePerKB(70 sat), fastest = FeeratePerKB(900 sat), fast = FeeratePerKB(850 sat), medium = FeeratePerKB(700 sat), slow = FeeratePerKB(600 sat)),
FeeratesPerKB(minimum = FeeratePerKB(70 sat), fastest = FeeratePerKB(900 sat), fast = FeeratePerKB(850 sat), medium = FeeratePerKB(700 sat), slow = FeeratePerKB(600 sat))
)
val provider: FeeProvider = new FeeProvider {
var index = 0
@ -56,7 +56,7 @@ class SmoothFeeProviderSpec extends AnyFunSuite {
assert(rate1 == rates(0))
assert(rate2 == SmoothFeeProvider.smooth(Seq(rates(0), rates(1))))
assert(rate3 == SmoothFeeProvider.smooth(Seq(rates(0), rates(1), rates(2))))
assert(rate3 == FeeratesPerKB(FeeratePerKB(60 sat), FeeratePerKB(200 sat), FeeratePerKB(300 sat), FeeratePerKB(400 sat), FeeratePerKB(500 sat), FeeratePerKB(600 sat), FeeratePerKB(700 sat), FeeratePerKB(750 sat), FeeratePerKB(800 sat)))
assert(rate3 == FeeratesPerKB(minimum = FeeratePerKB(60 sat), fastest = FeeratePerKB(800 sat), fast = FeeratePerKB(750 sat), medium = FeeratePerKB(600 sat), slow = FeeratePerKB(500 sat)))
assert(rate4 == SmoothFeeProvider.smooth(Seq(rates(1), rates(2), rates(3))))
assert(rate5 == rates(4)) // since the last 3 values are the same
}

View file

@ -118,7 +118,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) {
case (current, tx) =>
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
Closing.updateLocalCommitPublished(current1, tx)
}
assert(!lcp3.isDone)
@ -141,7 +141,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) {
case (current, tx) =>
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
Closing.updateLocalCommitPublished(current1, tx)
}
assert(!lcp3.isDone)
@ -160,7 +160,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
assert(lcp5.claimHtlcDelayedTxs.length == 3)
val newHtlcSuccessTx = lcp5.htlcTxs(remainingHtlcOutpoint).get.tx
val (lcp6, Some(newClaimHtlcDelayedTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp5, nodeParams.channelKeyManager, aliceClosing.commitments.latest, newHtlcSuccessTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (lcp6, Some(newClaimHtlcDelayedTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp5, nodeParams.channelKeyManager, aliceClosing.commitments.latest, newHtlcSuccessTx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
assert(lcp6.claimHtlcDelayedTxs.length == 4)
val lcp7 = Closing.updateLocalCommitPublished(lcp6, newHtlcSuccessTx)
@ -177,7 +177,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val remoteHtlcSuccess = rcp.claimHtlcTxs.values.collectFirst { case Some(tx: ClaimHtlcSuccessTx) => tx }.get
val lcp3 = (htlcSuccessTxs.map(_.tx) ++ Seq(remoteHtlcSuccess.tx)).foldLeft(lcp) {
case (current, tx) =>
val (current1, _) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (current1, _) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
Closing.updateLocalCommitPublished(current1, tx)
}
assert(lcp3.claimHtlcDelayedTxs.length == 1)
@ -188,7 +188,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val remainingHtlcTimeoutTxs = htlcTimeoutTxs.filter(_.input.outPoint != remoteHtlcSuccess.input.outPoint)
assert(remainingHtlcTimeoutTxs.length == 1)
val (lcp5, Some(remainingClaimHtlcTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp4, nodeParams.channelKeyManager, aliceClosing.commitments.latest, remainingHtlcTimeoutTxs.head.tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (lcp5, Some(remainingClaimHtlcTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp4, nodeParams.channelKeyManager, aliceClosing.commitments.latest, remainingHtlcTimeoutTxs.head.tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
assert(lcp5.claimHtlcDelayedTxs.length == 2)
val lcp6 = (remainingHtlcTimeoutTxs.map(_.tx) ++ Seq(remainingClaimHtlcTx.tx)).foldLeft(lcp5) {
@ -207,7 +207,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val lcp3 = htlcTimeoutTxs.map(_.tx).foldLeft(lcp) {
case (current, tx) =>
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
Closing.updateLocalCommitPublished(current1, tx)
}
assert(!lcp3.isDone)
@ -233,7 +233,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) {
case (current, tx) =>
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, aliceClosing.finalScriptPubKey)
val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, aliceClosing.finalScriptPubKey)
Closing.updateLocalCommitPublished(current1, tx)
}

View file

@ -30,6 +30,7 @@ import fr.acinq.eclair.wire.protocol.{IncorrectOrUnknownPaymentDetails, UpdateAd
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.duration._
import scala.util.Random
@ -39,9 +40,10 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging
val feeConfNoMismatch = OnChainFeeConf(
FeeTargets(6, 2, 12, 2, 6, 1),
new TestFeeEstimator(),
private val feerates = FeeratesPerKw.single(TestConstants.feeratePerKw)
private val feeConfNoMismatch = OnChainFeeConf(
feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium),
safeUtxosThreshold = 0,
spendAnchorWithoutHtlcs = true,
closeOnOfflineMismatch = false,
1.0,
@ -79,11 +81,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.onChainFeeConf)
val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, 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.onChainFeeConf)
val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc1.availableBalanceForSend == b)
assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee)
@ -164,11 +166,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.onChainFeeConf)
val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, 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.onChainFeeConf)
val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc1.availableBalanceForSend == b)
assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee)
@ -252,29 +254,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.onChainFeeConf)
val Right((ac1, add1)) = ac0.sendAdd(cmdAdd1, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, 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.onChainFeeConf)
val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, 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.onChainFeeConf)
val Right((bc1, add3)) = bc0.sendAdd(cmdAdd3, currentBlockHeight, bob.underlyingActor.nodeParams.channelConf, bob.underlyingActor.nodeParams.currentFeerates, 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.onChainFeeConf)
val Right(bc2) = bc1.receiveAdd(add1, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc2.availableBalanceForSend == b - p3)
assert(bc2.availableBalanceForReceive == a - p1 - htlcOutputFee)
val Right(bc3) = bc2.receiveAdd(add2, bob.underlyingActor.nodeParams.onChainFeeConf)
val Right(bc3) = bc2.receiveAdd(add2, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc3.availableBalanceForSend == b - p3)
assert(bc3.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee)
val Right(ac3) = ac2.receiveAdd(add3, alice.underlyingActor.nodeParams.onChainFeeConf)
val Right(ac3) = ac2.receiveAdd(add3, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf)
assert(ac3.availableBalanceForSend == a - p1 - htlcOutputFee - p2 - htlcOutputFee)
assert(ac3.availableBalanceForReceive == b - p3)
@ -383,7 +385,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, feeConfNoMismatch)
val Right((c1, _)) = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch)
assert(c1.availableBalanceForSend == 0.msat)
// We should be able to handle a fee increase.
@ -391,14 +393,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, feeConfNoMismatch)
val Left(_: InsufficientFunds) = c2.sendAdd(cmdAdd1, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, 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, feeConfNoMismatch)
val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch)
assert(result.isRight, result)
}
}
@ -407,7 +409,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)
c.receiveAdd(add, feeConfNoMismatch)
c.receiveAdd(add, feerates, feeConfNoMismatch)
}
}
@ -428,14 +430,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, feeConfNoMismatch) match {
c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, 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, feeConfNoMismatch)
val result = c.sendAdd(cmdAdd, f.currentBlockHeight, TestConstants.Alice.nodeParams.channelConf, feerates, feeConfNoMismatch)
assert(result.isRight, s"$t -> $result")
}
}
@ -458,14 +460,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)
c.receiveAdd(add, feeConfNoMismatch) match {
c.receiveAdd(add, feerates, feeConfNoMismatch) 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)
c.receiveAdd(add, feeConfNoMismatch) match {
c.receiveAdd(add, feerates, feeConfNoMismatch) match {
case Right(_) => ()
case Left(e) => fail(s"$t -> $e")
}

View file

@ -17,7 +17,7 @@
package fr.acinq.eclair.channel.publish
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel.publish.ReplaceableTxFunder.AdjustPreviousTxOutputResult.{AddWalletInputs, TxOutputAdjusted}
import fr.acinq.eclair.channel.publish.ReplaceableTxFunder._
@ -48,7 +48,7 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike {
val anchorTx = ClaimLocalAnchorOutputTx(
InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head, anchorScript),
Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Nil, 0),
BlockHeight(0)
ConfirmationTarget.Absolute(BlockHeight(0))
)
(CommitTx(commitInput, commitTx), anchorTx)
}
@ -69,13 +69,13 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike {
Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0),
paymentHash,
17,
BlockHeight(0)
ConfirmationTarget.Absolute(BlockHeight(0))
), PlaceHolderSig, preimage)
val htlcTimeout = HtlcTimeoutWithWitnessData(HtlcTimeoutTx(
InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last, htlcTimeoutScript),
Transaction(2, Seq(TxIn(OutPoint(commitTx, 1), ByteVector.empty, 0)), Seq(TxOut(4000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0),
12,
BlockHeight(0)
ConfirmationTarget.Absolute(BlockHeight(0))
), PlaceHolderSig)
(htlcSuccess, htlcTimeout)
}
@ -90,13 +90,13 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike {
Transaction(2, Seq(TxIn(OutPoint(ByteVector32.Zeroes, 3), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0),
paymentHash,
5,
BlockHeight(0)
ConfirmationTarget.Absolute(BlockHeight(0))
), preimage)
val claimHtlcTimeout = ClaimHtlcTimeoutWithWitnessData(ClaimHtlcTimeoutTx(
InputInfo(OutPoint(ByteVector32.Zeroes, 7), TxOut(5000 sat, Script.pay2wsh(htlcTimeoutScript)), htlcTimeoutScript),
Transaction(2, Seq(TxIn(OutPoint(ByteVector32.Zeroes, 7), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0),
7,
BlockHeight(0)
ConfirmationTarget.Absolute(BlockHeight(0))
))
(claimHtlcSuccess, claimHtlcTimeout)
}

View file

@ -28,7 +28,7 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.MempoolTx
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinCoreClient, BitcoinJsonRPCClient}
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, OnchainPubkeyCache}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
@ -39,7 +39,7 @@ import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsT
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.{CommitSig, RevokeAndAck}
import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, NotificationsLogger, TestConstants, TestFeeEstimator, TestKitBaseClass, randomKey}
import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, NotificationsLogger, TestConstants, TestKitBaseClass, randomKey}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuiteLike
@ -82,14 +82,21 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
/** Set uniform feerate for all block targets. */
def setFeerate(feerate: FeeratePerKw): Unit = {
alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(feerate))
bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(feerate))
alice.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate))
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate))
}
/** Set feerate for a specific block target. */
def setFeerate(feerate: FeeratePerKw, blockTarget: Int): Unit = {
alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(blockTarget, feerate)
bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(blockTarget, feerate)
def updateFeerates(currentFeerates: FeeratesPerKw): FeeratesPerKw = blockTarget match {
case 1 => currentFeerates.copy(fastest = feerate)
case 2 => currentFeerates.copy(fast = feerate)
case t if t <= 12 => currentFeerates.copy(medium = feerate)
case _ => currentFeerates.copy(slow = feerate)
}
alice.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates))
bob.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates))
}
def getMempool(): Seq[Transaction] = {
@ -171,7 +178,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val publishAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(publishAnchor.txInfo.input.outPoint.txid == commitTx.tx.txid)
assert(publishAnchor.txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
val anchorTx = publishAnchor.txInfo.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmBefore = overrideCommitTarget)
val anchorTx = publishAnchor.txInfo.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideCommitTarget))
(publishCommitTx, publishAnchor.copy(txInfo = anchorTx))
}
@ -540,7 +547,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(10.4 millibtc, 5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30)
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 40)
wallet.publishTransaction(commitTx.tx).pipeTo(probe.ref)
probe.expectMsg(commitTx.tx.txid)
@ -579,7 +586,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30)
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 40)
wallet.publishTransaction(commitTx.tx).pipeTo(probe.ref)
probe.expectMsg(commitTx.tx.txid)
@ -613,7 +620,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30)
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 40)
wallet.publishTransaction(commitTx.tx).pipeTo(probe.ref)
probe.expectMsg(commitTx.tx.txid)
@ -644,7 +651,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30)
val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 40)
wallet.publishTransaction(commitTx.tx).pipeTo(probe.ref)
probe.expectMsg(commitTx.tx.txid)
@ -668,7 +675,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
// The confirmation target has changed (probably because we learnt a payment preimage).
// We should now use the high feerate, which corresponds to that new target.
publisher ! UpdateConfirmationTarget(aliceBlockHeight() + 15)
publisher ! UpdateConfirmationTarget(ConfirmationTarget.Absolute(aliceBlockHeight() + 15))
system.eventStream.publish(CurrentBlockHeight(aliceBlockHeight()))
val anchorTxId2 = listener.expectMsgType[TransactionPublished].tx.txid
awaitAssert(assert(!isInMempool(mempoolAnchorTx1.txid)), interval = 200 millis, max = 30 seconds)
@ -859,10 +866,10 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
alice2blockchain.expectMsgType[PublishFinalTx] // claim main output
val htlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(htlcSuccess.txInfo.isInstanceOf[HtlcSuccessTx])
val htlcSuccessTx = htlcSuccess.txInfo.asInstanceOf[HtlcSuccessTx].copy(confirmBefore = overrideHtlcTarget)
val htlcSuccessTx = htlcSuccess.txInfo.asInstanceOf[HtlcSuccessTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
val htlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(htlcTimeout.txInfo.isInstanceOf[HtlcTimeoutTx])
val htlcTimeoutTx = htlcTimeout.txInfo.asInstanceOf[HtlcTimeoutTx].copy(confirmBefore = overrideHtlcTarget)
val htlcTimeoutTx = htlcTimeout.txInfo.asInstanceOf[HtlcTimeoutTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx
alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output
@ -1208,7 +1215,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val (commitTx, htlcSuccess, _) = closeChannelWithHtlcs(f, aliceBlockHeight() + 144)
// The HTLC confirmation target is far away, but we have less safe utxos than the configured threshold.
// We will target a 1-block confirmation to get a safe utxo back as soon as possible.
val highSafeThresholdParams = alice.underlyingActor.nodeParams.modify(_.onChainFeeConf.feeTargets.safeUtxosThreshold).setTo(10)
val highSafeThresholdParams = alice.underlyingActor.nodeParams.modify(_.onChainFeeConf.safeUtxosThreshold).setTo(10)
setFeerate(FeeratePerKw(2500 sat))
val targetFeerate = FeeratePerKw(5000 sat)
setFeerate(targetFeerate, blockTarget = 2)
@ -1364,10 +1371,10 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
alice2blockchain.expectMsgType[PublishFinalTx] // claim main output
val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx])
val claimHtlcSuccessTx = claimHtlcSuccess.txInfo.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmBefore = overrideHtlcTarget)
val claimHtlcSuccessTx = claimHtlcSuccess.txInfo.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
val claimHtlcTimeout = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(claimHtlcTimeout.txInfo.isInstanceOf[ClaimHtlcTimeoutTx])
val claimHtlcTimeoutTx = claimHtlcTimeout.txInfo.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmBefore = overrideHtlcTarget)
val claimHtlcTimeoutTx = claimHtlcTimeout.txInfo.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx
alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output
@ -1431,7 +1438,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._
val currentFeerate = alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(2)
val currentFeerate = alice.underlyingActor.nodeParams.currentFeerates.fast
val (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) = remoteCloseChannelWithHtlcs(f, aliceBlockHeight() + 50, nextCommit = false)
val claimHtlcSuccessTx = testPublishClaimHtlcSuccess(f, remoteCommitTx, claimHtlcSuccess, currentFeerate)
assert(claimHtlcSuccess.txInfo.fee > 0.sat)

View file

@ -22,6 +22,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorSystemOps, TypedActorRefOp
import akka.testkit.TestProbe
import fr.acinq.bitcoin.scalacompat.{OutPoint, SatoshiLong, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.CurrentBlockHeight
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel.publish
import fr.acinq.eclair.channel.publish.TxPublisher.TxRejectedReason._
import fr.acinq.eclair.channel.publish.TxPublisher._
@ -101,7 +102,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
test("publish replaceable tx") { f =>
import f._
val confirmBefore = nodeParams.currentBlockHeight + 12
val confirmBefore = ConfirmationTarget.Absolute(nodeParams.currentBlockHeight + 12)
val input = OutPoint(randomBytes32(), 3)
val cmd = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore), null)
txPublisher ! cmd
@ -115,7 +116,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val confirmBefore = nodeParams.currentBlockHeight + 12
val input = OutPoint(randomBytes32(), 3)
val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore)
val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ConfirmationTarget.Absolute(confirmBefore))
val cmd = PublishReplaceableTx(anchorTx, null)
txPublisher ! cmd
val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor
@ -125,19 +126,19 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
txPublisher ! PublishReplaceableTx(anchorTx, null)
child.expectNoMessage(100 millis)
factory.expectNoMessage(100 millis)
val cmdHigherTarget = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore + 1))
val cmdHigherTarget = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore + 1)))
txPublisher ! cmdHigherTarget
child.expectNoMessage(100 millis)
factory.expectNoMessage(100 millis)
// But we update the confirmation target when it is more aggressive than previous attempts:
val cmdLowerTarget = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore - 6))
val cmdLowerTarget = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore - 6)))
txPublisher ! cmdLowerTarget
child.expectMsg(ReplaceableTxPublisher.UpdateConfirmationTarget(confirmBefore - 6))
child.expectMsg(ReplaceableTxPublisher.UpdateConfirmationTarget(ConfirmationTarget.Absolute(confirmBefore - 6)))
factory.expectNoMessage(100 millis)
// And we update our internal threshold accordingly:
val cmdInBetween = cmd.copy(txInfo = anchorTx.copy(confirmBefore = confirmBefore - 3))
val cmdInBetween = cmd.copy(txInfo = anchorTx.copy(confirmationTarget = ConfirmationTarget.Absolute(confirmBefore - 3)))
txPublisher ! cmdInBetween
child.expectNoMessage(100 millis)
factory.expectNoMessage(100 millis)
@ -159,7 +160,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned].actor
attempt2.expectMsgType[FinalTxPublisher.Publish]
val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), nodeParams.currentBlockHeight), null)
val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null)
txPublisher ! cmd3
val attempt3 = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor
attempt3.expectMsgType[ReplaceableTxPublisher.Publish]
@ -181,7 +182,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val attempt1 = factory.expectMsgType[FinalTxPublisherSpawned]
attempt1.actor.expectMsgType[FinalTxPublisher.Publish]
val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), nodeParams.currentBlockHeight), null)
val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null)
txPublisher ! cmd2
val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned]
attempt2.actor.expectMsgType[ReplaceableTxPublisher.Publish]
@ -221,7 +222,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val target = nodeParams.currentBlockHeight + 12
val input = OutPoint(randomBytes32(), 7)
val paymentHash = randomBytes32()
val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, target), null)
val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(target)), null)
txPublisher ! cmd
val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned]
attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish]
@ -285,7 +286,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val input = OutPoint(randomBytes32(), 7)
val paymentHash = randomBytes32()
val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, nodeParams.currentBlockHeight), null)
val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null)
txPublisher ! cmd
val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned]
attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish]
@ -356,7 +357,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
}
{
// Only replaceable attempts.
val attempt = ReplaceableAttempt(UUID.randomUUID(), null, BlockHeight(0), null)
val attempt = ReplaceableAttempt(UUID.randomUUID(), null, ConfirmationTarget.Absolute(BlockHeight(0)), null)
val attempts = PublishAttempts(Nil, Some(attempt))
assert(!attempts.isEmpty)
assert(attempts.count == 1)
@ -365,7 +366,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
}
{
// Mix of final and replaceable attempts with the same id.
val attempt1 = ReplaceableAttempt(UUID.randomUUID(), null, BlockHeight(0), null)
val attempt1 = ReplaceableAttempt(UUID.randomUUID(), null, ConfirmationTarget.Absolute(BlockHeight(0)), null)
val attempt2 = FinalAttempt(attempt1.id, null, null)
val attempt3 = FinalAttempt(UUID.randomUUID(), null, null)
val attempts = PublishAttempts(Seq(attempt2), Some(attempt1)).add(attempt3)

View file

@ -26,9 +26,9 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, SatoshiLong, Script,
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeTargets, FeeratePerKw}
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.{DummyOnChainWallet, OnChainWallet, OnchainPubkeyCache, SingleKeyOnChainWallet}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.{ChannelData, ChannelState, _}
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher
import fr.acinq.eclair.channel.publish.TxPublisher.PublishReplaceableTx
@ -111,13 +111,6 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
def currentBlockHeight: BlockHeight = alice.underlyingActor.nodeParams.currentBlockHeight
}
implicit class ChannelWithTestFeeConf(a: TestFSMRef[ChannelState, ChannelData, Channel]) {
// @formatter:off
def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets
// @formatter:on
}
implicit val system: ActorSystem
val systemA: ActorSystem = ActorSystem("system-alice")
val systemB: ActorSystem = ActorSystem("system-bob")
@ -621,4 +614,13 @@ object ChannelStateTestsBase {
override def spawnTxPublisher(context: ActorContext, remoteNodeId: PublicKey): akka.actor.typed.ActorRef[TxPublisher.Command] = txPublisher.ref
}
}
implicit class PimpTestFSM(private val channel: TestFSMRef[ChannelState, ChannelData, Channel]) {
val nodeParams: NodeParams = channel.underlyingActor.nodeParams
def setFeerates(feerates: FeeratesPerKw): Unit = channel.underlyingActor.nodeParams.setFeerates(feerates)
def setFeerate(feerate: FeeratePerKw): Unit = setFeerates(FeeratesPerKw.single(feerate))
}
}

View file

@ -29,7 +29,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.FullySignedSharedTransaction
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx, SetChannelId}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.FakeTxPublisherFactory
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.{FakeTxPublisherFactory, PimpTestFSM}
import fr.acinq.eclair.channel.states.ChannelStateTestsTags.{AnchorOutputsZeroFeeHtlcTxs, NoMaxHtlcValueInFlight, ZeroConf}
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.testutils.PimpTestProbe.convert
@ -169,7 +169,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
initiateSplice(f, spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
val fundingTx1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
val feerate = TestConstants.Alice.nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(TestConstants.Alice.nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)
val feerate = alice.nodeParams.onChainFeeConf.getFundingFeerate(alice.nodeParams.currentFeerates)
val expectedMiningFee = Transactions.weight2fee(feerate, fundingTx1.weight())
val actualMiningFee = 1_400_000.sat - alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.capacity
// fee computation is approximate

View file

@ -26,13 +26,13 @@ import fr.acinq.eclair.Features.StaticRemoteKey
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerByte, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates}
import fr.acinq.eclair.channel.RealScidStatus.Final
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel._
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.io.Peer
@ -527,7 +527,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(20000 sat)))
bob.setFeerate(FeeratePerKw(20000 sat))
bob ! CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(20000 sat)))
bob2alice.expectNoMessage(100 millis) // we don't close because the commitment doesn't contain any HTLC
@ -540,7 +540,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
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.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob.setFeerate(FeeratePerKw(11000 sat))
bob ! CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob2alice.expectNoMessage(100 millis)
bob ! add
@ -772,8 +772,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(Alice.nodeParams.channelConf.dustLimit > Bob.nodeParams.channelConf.dustLimit)
// and a low feerate to avoid messing with dust exposure limits
val currentFeerate = FeeratePerKw(2500 sat)
alice.feeEstimator.setFeerate(FeeratesPerKw.single(currentFeerate))
bob.feeEstimator.setFeerate(FeeratesPerKw.single(currentFeerate))
alice.setFeerate(currentFeerate)
bob.setFeerate(currentFeerate)
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.
@ -2013,8 +2013,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
// We start with a low feerate.
val initialFeerate = FeeratePerKw(500 sat)
alice.feeEstimator.setFeerate(FeeratesPerKw.single(initialFeerate))
bob.feeEstimator.setFeerate(FeeratesPerKw.single(initialFeerate))
alice.setFeerate(initialFeerate)
bob.setFeerate(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)
@ -2045,8 +2045,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// 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.feeEstimator.setFeerate(FeeratesPerKw.single(updatedFeerate))
bob.feeEstimator.setFeerate(FeeratesPerKw.single(updatedFeerate))
alice.setFeerate(updatedFeerate)
bob.setFeerate(updatedFeerate)
alice ! cmd
if (higherDustLimit == aliceCommitments.params.localParams.dustLimit) {
sender.expectMsg(RES_FAILURE(cmd, LocalDustHtlcExposureTooHigh(channelId(alice), 25000 sat, 29600000 msat)))
@ -2134,7 +2134,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100000000 sat))
// we first update the feerates so that we don't trigger a 'fee too different' error
bob.feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw))
bob.setFeerate(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)
@ -2214,7 +2214,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments
val tx = bobCommitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
val expectedFeeratePerKw = bob.feeEstimator.getFeeratePerKw(bob.feeTargets.commitmentBlockTarget)
val expectedFeeratePerKw = bob.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.remoteNodeId, bobCommitments.params.channelType, bobCommitments.latest.capacity)
assert(bobCommitments.latest.localCommit.spec.commitTxFeerate == expectedFeeratePerKw)
bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat))
val error = bob2alice.expectMsgType[Error]
@ -2241,7 +2241,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
// A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-closes:
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(20000 sat)))
bob.setFeerate(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)
@ -2270,7 +2270,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// A large feerate increase would make these HTLCs overflow Bob's dust exposure, so he force-close:
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(20000 sat)))
bob.setFeerate(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)
@ -2283,8 +2283,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe()
// We start with a low feerate.
val initialFeerate = FeeratePerKw(500 sat)
alice.feeEstimator.setFeerate(FeeratesPerKw.single(initialFeerate))
bob.feeEstimator.setFeerate(FeeratesPerKw.single(initialFeerate))
alice.setFeerate(initialFeerate)
bob.setFeerate(initialFeerate)
updateFee(initialFeerate, alice, bob, alice2bob, bob2alice)
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val aliceCommitments = initialState.commitments
@ -2316,7 +2316,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// A feerate increase makes these HTLCs become dust in one of the commitments but not the other.
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
bob.feeEstimator.setFeerate(FeeratesPerKw.single(updatedFeerate))
bob.setFeerate(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.
@ -2876,26 +2876,32 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f =>
import f._
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val event = CurrentFeerates(FeeratesPerKw(FeeratePerKw(50 sat), FeeratePerKw(100 sat), FeeratePerKw(200 sat), FeeratePerKw(600 sat), FeeratePerKw(1200 sat), FeeratePerKw(3600 sat), FeeratePerKw(7200 sat), FeeratePerKw(14400 sat), FeeratePerKw(100800 sat)))
val event = CurrentFeerates(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)))
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.channelType, initialState.commitments.latest.capacity)))
}
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)
alice ! CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
val event1 = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event1.feeratesPerKw)
alice ! event1
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2))
alice2bob.expectMsgType[CommitSig]
// The configured maximum feerate is bypassed if it's below the propagation threshold.
alice ! CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = TestConstants.anchorOutputsFeeratePerKw))
val event2 = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = TestConstants.anchorOutputsFeeratePerKw))
alice.setFeerates(event2.feeratesPerKw)
alice ! event2
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw * 1.25))
}
test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f =>
import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectNoMessage(500 millis)
}
@ -2904,13 +2910,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
alice ! CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectNoMessage(500 millis)
}
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f =>
import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectNoMessage(500 millis)
}
@ -2921,8 +2930,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
addHtlc(10000000 msat, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(14000 sat)))
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(14000 sat)))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectMsgType[Error]
bob2blockchain.expectMsgType[PublishTx] // commit tx
@ -2935,8 +2944,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
// We start with a feerate lower than the 10 sat/byte threshold.
alice.feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2))
bob.feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2))
alice.setFeerate(TestConstants.anchorOutputsFeeratePerKw / 2)
bob.setFeerate(TestConstants.anchorOutputsFeeratePerKw / 2)
alice ! CMD_UPDATE_FEE(TestConstants.anchorOutputsFeeratePerKw / 2)
alice2bob.expectMsgType[UpdateFee]
alice2bob.forward(bob)
@ -2945,8 +2954,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw / 2)
// The network fees spike, but Bob doesn't close the channel because we're using anchor outputs.
bob.feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2))
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectNoMessage(250 millis)
assert(bob.stateName == NORMAL)
@ -2955,8 +2964,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, without HTLCs)") { f =>
import f._
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1000 sat)))
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(1000 sat)))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC
@ -3012,8 +3021,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(amountClaimed == 814880.sat)
// alice sets the confirmation targets to the HTLC expiry
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcSuccessTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlcb1.id -> htlcb1.cltvExpiry.toLong))
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong))
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcSuccessTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlcb1.id -> htlcb1.cltvExpiry.blockHeight))
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight))
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid)
@ -3031,7 +3040,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(getClaimHtlcTimeoutTxs(rcp).length == 2)
// assert the feerate of the claim main is what we expect
val expectedFeeRate = alice.feeEstimator.getFeeratePerKw(alice.feeTargets.claimMainBlockTarget)
val expectedFeeRate = alice.underlyingActor.nodeParams.onChainFeeConf.getClosingFeerate(alice.underlyingActor.nodeParams.currentFeerates)
val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight)
val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount).sum - claimMain.txOut.map(_.amount).sum
assert(claimFee == expectedFee)
@ -3103,7 +3112,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(amountClaimed == 822310.sat)
// alice sets the confirmation targets to the HTLC expiry
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmBefore.toLong) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong))
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight))
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid) // claim-main
@ -3378,15 +3387,15 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(alice.stateName == CLOSING)
val localAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(localAnchor.txInfo.confirmBefore.toLong == htlca1.cltvExpiry.toLong) // the target is set to match the first htlc that expires
assert(localAnchor.txInfo.confirmationTarget == ConfirmationTarget.Absolute(htlca1.cltvExpiry.blockHeight)) // the target is set to match the first htlc that expires
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx]
// alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val htlcConfirmationTargets = Seq(
alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 1
alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 2
alice2blockchain.expectMsgType[PublishReplaceableTx], // htlc 3
).map(p => p.txInfo.asInstanceOf[HtlcTx].htlcId -> p.txInfo.confirmBefore.toLong).toMap
assert(htlcConfirmationTargets == Map(htlcb1.id -> htlcb1.cltvExpiry.toLong, htlca1.id -> htlca1.cltvExpiry.toLong, htlca2.id -> htlca2.cltvExpiry.toLong))
).map(p => p.txInfo.asInstanceOf[HtlcTx].htlcId -> p.txInfo.asInstanceOf[HtlcTx].confirmationTarget.confirmBefore).toMap
assert(htlcConfirmationTargets == Map(htlcb1.id -> htlcb1.cltvExpiry.blockHeight, htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight))
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.tx.txid)
@ -3422,7 +3431,6 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(alice.stateName == CLOSING)
val currentBlockHeight = alice.underlyingActor.nodeParams.currentBlockHeight
val blockTargets = alice.underlyingActor.nodeParams.onChainFeeConf.feeTargets
if (commitFeeBumpDisabled) {
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx]
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === aliceCommitTx.txid)
@ -3430,8 +3438,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice2blockchain.expectNoMessage(1 second)
} else {
val localAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx]
// When there are no pending HTLCs, there is no rush to get the commit tx confirmed
assert(localAnchor.txInfo.confirmBefore === currentBlockHeight + blockTargets.commitmentWithoutHtlcsBlockTarget)
// When there are no pending HTLCs, there is no absolute deadline to get the commit tx confirmed, we use priority
assert(localAnchor.txInfo.confirmationTarget == ConfirmationTarget.Priority(ConfirmationPriority.Medium))
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx]
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === aliceCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === claimMain.tx.txid)

View file

@ -30,9 +30,10 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM
import fr.acinq.eclair.transactions.Transactions.HtlcSuccessTx
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestFeeEstimator, TestKitBaseClass, TestUtils, randomBytes32}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, TestUtils, randomBytes32}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@ -609,10 +610,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1
// to ensure the network's feerate is 10% above our threshold).
val networkFeeratePerKw = currentFeeratePerKw * (1.1 / alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Bob.nodeParams.nodeId).ratioLow)
val networkFeerate = FeeratesPerKw.single(networkFeeratePerKw)
val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw)
// alice is funder
alice ! CurrentFeerates(networkFeerate)
alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates)
if (shouldClose) {
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid)
} else {
@ -635,10 +637,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1
// to ensure the network's feerate is 10% above our threshold).
val networkFeeratePerKw = currentFeeratePerKw * (1.1 / alice.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Bob.nodeParams.nodeId).ratioLow)
val networkFeerate = FeeratesPerKw.single(networkFeeratePerKw)
val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw)
// this time Alice will ignore feerate changes for the offline channel
alice ! CurrentFeerates(networkFeerate)
alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates)
alice2blockchain.expectNoMessage()
alice2bob.expectNoMessage()
}
@ -651,11 +654,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val localFeeratePerKw = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate
val networkFeeratePerKw = localFeeratePerKw * 2
val networkFeerate = FeeratesPerKw.single(networkFeeratePerKw)
val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw)
// Alice ignores feerate changes while offline
alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(networkFeerate)
alice ! CurrentFeerates(networkFeerate)
alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates)
alice2blockchain.expectNoMessage()
alice2bob.expectNoMessage()
@ -718,10 +721,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// we receive a feerate update that makes our current feerate too low compared to the network's (we multiply by 1.1
// to ensure the network's feerate is 10% above our threshold).
val networkFeeratePerKw = currentFeeratePerKw * (1.1 / bob.underlyingActor.nodeParams.onChainFeeConf.feerateToleranceFor(Alice.nodeParams.nodeId).ratioLow)
val networkFeerate = FeeratesPerKw.single(networkFeeratePerKw)
val networkFeerates = FeeratesPerKw.single(networkFeeratePerKw)
// bob is fundee
bob ! CurrentFeerates(networkFeerate)
bob.setFeerates(networkFeerates)
bob ! CurrentFeerates(networkFeerates)
if (shouldClose) {
assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == bobCommitTx.txid)
} else {

View file

@ -26,6 +26,7 @@ import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream
import fr.acinq.eclair.payment._
@ -560,7 +561,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
val fee = UpdateFee(ByteVector32.Zeroes, FeeratePerKw(100000000 sat))
// we first update the feerates so that we don't trigger a 'fee too different' error
bob.feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw))
bob.setFeerate(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)
@ -626,22 +627,26 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f =>
import f._
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
val event = CurrentFeerates(FeeratesPerKw(FeeratePerKw(50 sat), FeeratePerKw(100 sat), FeeratePerKw(200 sat), FeeratePerKw(600 sat), FeeratePerKw(1200 sat), FeeratePerKw(3600 sat), FeeratePerKw(7200 sat), FeeratePerKw(14400 sat), FeeratePerKw(100800 sat)))
val event = CurrentFeerates(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(alice.feeTargets.commitmentBlockTarget)))
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.channelType, initialState.commitments.latest.capacity)))
}
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)
alice ! CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2))
}
test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f =>
import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectNoMessage(500 millis)
}
@ -650,13 +655,16 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
import f._
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
alice ! CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw)
alice ! event
alice2bob.expectNoMessage(500 millis)
}
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f =>
import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectNoMessage(500 millis)
}
@ -664,6 +672,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f =>
import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(1000 sat)))
bob.setFeerates(event.feeratesPerKw)
bob ! event
bob2alice.expectMsgType[Error]
bob2blockchain.expectMsgType[PublishTx] // commit tx

View file

@ -24,12 +24,13 @@ import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
import fr.acinq.eclair.wire.protocol.ClosingSignedTlv.FeeRange
import fr.acinq.eclair.wire.protocol.{ClosingSigned, Error, Shutdown, TlvStream, Warning}
import fr.acinq.eclair.{CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestFeeEstimator, TestKitBaseClass, randomBytes32}
import fr.acinq.eclair.{CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@ -85,9 +86,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
assert(bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.params.localParams.upfrontShutdownScript_opt.forall(_ == bobShutdown.scriptPubKey))
}
def setFeerate(feeEstimator: TestFeeEstimator, feerate: FeeratePerKw, minFeerate: FeeratePerKw = FeeratePerKw(250 sat)): Unit = {
feeEstimator.setFeerate(FeeratesPerKw.single(feerate).copy(mempoolMinFee = minFeerate, blocks_1008 = minFeerate))
}
def buildFeerates(feerate: FeeratePerKw, minFeerate: FeeratePerKw = FeeratePerKw(250 sat)): FeeratesPerKw =
FeeratesPerKw.single(feerate).copy(minimum = minFeerate, slow = minFeerate)
test("recv CMD_ADD_HTLC") { f =>
import f._
@ -105,10 +105,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
import f._
// alice and bob see different on-chain feerates
alice.feeEstimator.setFeerate(FeeratesPerKw(FeeratePerKw(250 sat), FeeratePerKw(10000 sat), FeeratePerKw(8000 sat), FeeratePerKw(7500 sat), FeeratePerKw(5000 sat), FeeratePerKw(2000 sat), FeeratePerKw(2000 sat), FeeratePerKw(2000 sat), FeeratePerKw(2000 sat)))
bob.feeEstimator.setFeerate(FeeratesPerKw(FeeratePerKw(250 sat), FeeratePerKw(15000 sat), FeeratePerKw(12500 sat), FeeratePerKw(10000 sat), FeeratePerKw(7500 sat), FeeratePerKw(3000 sat), FeeratePerKw(3000 sat), FeeratePerKw(3000 sat), FeeratePerKw(3000 sat)))
assert(alice.feeTargets.mutualCloseBlockTarget == 12)
assert(bob.feeTargets.mutualCloseBlockTarget == 12)
alice.setFeerates(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(8000 sat), medium = FeeratePerKw(5000 sat), slow = FeeratePerKw(2000 sat)))
bob.setFeerates(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(15_000 sat), fast = FeeratePerKw(12_500 sat), medium = FeeratePerKw(7500 sat), slow = FeeratePerKw(3000 sat)))
if (bobInitiates) {
bobClose(f)
@ -176,8 +174,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (theirMinCloseFee > ourCloseFee)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(10000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(2500 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(2500 sat)))
aliceClose(f)
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
alice2bob.forward(bob)
@ -187,8 +185,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (theirMaxCloseFee < ourCloseFee)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(5000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(20000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(5_000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(20_000 sat)))
aliceClose(f)
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
alice2bob.forward(bob)
@ -200,8 +198,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
import f._
// alice and bob see the same on-chain feerates
setFeerate(alice.feeEstimator, FeeratePerKw(5000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(5000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(5000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(5000 sat)))
if (bobInitiates) {
bobClose(f)
@ -243,8 +241,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("override on-chain fee estimator (funder)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(10000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
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]
@ -266,8 +264,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("override on-chain fee estimator (fundee)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(10000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
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]
@ -285,8 +283,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (nothing at stake)", Tag(ChannelStateTestsTags.NoPushAmount)) { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(5000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(5_000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
bobClose(f)
val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
alice2bob.forward(bob)
@ -311,7 +309,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (other side ignores our fee range, funder)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(1000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(1000 sat)))
aliceClose(f)
val aliceClosing1 = alice2bob.expectMsgType[ClosingSigned]
val Some(FeeRange(_, maxFee)) = aliceClosing1.feeRange_opt
@ -350,7 +348,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (other side ignores our fee range, fundee)") { f =>
import f._
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)))
bob.setFeerate(FeeratePerKw(10_000 sat))
bobClose(f)
// alice starts with a very low proposal
val (aliceClosing1, _) = makeLegacyClosingSigned(f, 500 sat)
@ -389,7 +387,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (other side ignores our fee range, max iterations reached)") { f =>
import f._
alice.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1000 sat)))
alice.setFeerate(FeeratePerKw(1000 sat))
aliceClose(f)
for (_ <- 1 to Channel.MAX_NEGOTIATION_ITERATIONS) {
val aliceClosing = alice2bob.expectMsgType[ClosingSigned]
@ -405,8 +403,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv ClosingSigned (fee too low, fundee)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(250 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat), minFeerate = FeeratePerKw(750 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(250 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat), minFeerate = FeeratePerKw(750 sat)))
bobClose(f)
val aliceClosing = alice2bob.expectMsgType[ClosingSigned]
assert(aliceClosing.feeRange_opt.get.max < 500.sat)
@ -465,8 +463,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv WatchFundingSpentTriggered (an older mutual close)") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(1000 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(1000 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat)))
aliceClose(f)
val aliceClosing1 = alice2bob.expectMsgType[ClosingSigned]
alice2bob.forward(bob, aliceClosing1)
@ -489,7 +487,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv WatchFundingSpentTriggered (self mutual close)") { f =>
import f._
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)))
bob.setFeerate(FeeratePerKw(10_000 sat))
bobClose(f)
// alice starts with a very low proposal
val (aliceClosing1, _) = makeLegacyClosingSigned(f, 500 sat)
@ -517,8 +515,8 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
test("recv CMD_CLOSE with updated feerates") { f =>
import f._
setFeerate(alice.feeEstimator, FeeratePerKw(250 sat))
setFeerate(bob.feeEstimator, FeeratePerKw(10000 sat), minFeerate = FeeratePerKw(750 sat))
alice.setFeerates(buildFeerates(FeeratePerKw(250 sat)))
bob.setFeerates(buildFeerates(FeeratePerKw(10_000 sat), minFeerate = FeeratePerKw(750 sat)))
bobClose(f)
val aliceClosing1 = alice2bob.expectMsgType[ClosingSigned]
alice2bob.send(bob, aliceClosing1)

View file

@ -22,7 +22,7 @@ import fr.acinq.bitcoin.ScriptFlags
import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITCOIN_FUNDING_TIMEOUT}
@ -288,7 +288,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._
val sender = TestProbe()
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures)
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(mempoolMinFee = FeeratePerKw(250 sat), blocks_1008 = FeeratePerKw(250 sat)))
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(minimum = FeeratePerKw(250 sat), slow = FeeratePerKw(250 sat)))
// alice initiates a closing with a low fee
alice ! CMD_CLOSE(sender.ref, None, Some(ClosingFeerates(FeeratePerKw(500 sat), FeeratePerKw(250 sat), FeeratePerKw(1000 sat))))
alice2bob.expectMsgType[Shutdown]
@ -814,9 +814,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx)
val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get).head.tx
Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(publishHtlcSuccessTx.txInfo.tx == claimHtlcSuccessTx)
assert(publishHtlcSuccessTx.txInfo.confirmBefore.toLong == htlc1.cltvExpiry.toLong)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx]
assert(publishHtlcSuccessTx.tx == claimHtlcSuccessTx)
assert(publishHtlcSuccessTx.confirmationTarget == ConfirmationTarget.Absolute(htlc1.cltvExpiry.blockHeight))
// Alice resets watches on all relevant transactions.
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
@ -857,9 +857,9 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// we should re-publish unconfirmed transactions
closingState.claimMainOutputTx.foreach(claimMain => assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == claimMain.tx))
val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(publishHtlcTimeoutTx.txInfo.tx == htlcTimeoutTx.tx)
assert(publishHtlcTimeoutTx.txInfo.confirmBefore.toLong == htlca.cltvExpiry.toLong)
val publishClaimHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcTimeoutTx]
assert(publishClaimHtlcTimeoutTx.tx == htlcTimeoutTx.tx)
assert(publishClaimHtlcTimeoutTx.confirmationTarget == ConfirmationTarget.Absolute(htlca.cltvExpiry.blockHeight))
closingState.claimMainOutputTx.foreach(claimMain => assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.tx.txid))
assert(alice2blockchain.expectMsgType[WatchOutputSpent].outputIndex == htlcTimeoutTx.input.outPoint.index)
}
@ -989,12 +989,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx)
val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get).head.tx
Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(publishHtlcSuccessTx.txInfo.tx == claimHtlcSuccessTx)
assert(publishHtlcSuccessTx.txInfo.confirmBefore.toLong == htlc1.cltvExpiry.toLong)
val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(publishHtlcTimeoutTx.txInfo.tx == claimHtlcTimeoutTx)
assert(publishHtlcTimeoutTx.txInfo.confirmBefore.toLong == htlc2.cltvExpiry.toLong)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx]
assert(publishHtlcSuccessTx.tx == claimHtlcSuccessTx)
assert(publishHtlcSuccessTx.confirmationTarget == ConfirmationTarget.Absolute(htlc1.cltvExpiry.blockHeight))
val publishHtlcTimeoutTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcTimeoutTx]
assert(publishHtlcTimeoutTx.tx == claimHtlcTimeoutTx)
assert(publishHtlcTimeoutTx.confirmationTarget == ConfirmationTarget.Absolute(htlc2.cltvExpiry.blockHeight))
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == closingState.claimMainOutputTx.get.tx.txid)

View file

@ -13,7 +13,7 @@ import fr.acinq.eclair.ShortChannelId.txIndex
import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{WatchFundingConfirmed, WatchFundingConfirmedTriggered, WatchFundingDeeplyBuried, WatchFundingDeeplyBuriedTriggered}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.crypto.TransportHandler
@ -30,13 +30,13 @@ import fr.acinq.eclair.payment.relay.{ChannelRelayer, PostRestartHtlcCleaner, Re
import fr.acinq.eclair.payment.send.PaymentInitiator
import fr.acinq.eclair.router.Router
import fr.acinq.eclair.wire.protocol.IPAddress
import fr.acinq.eclair.{BlockHeight, MilliSatoshi, NodeParams, RealShortChannelId, SubscriptionsComplete, TestBitcoinCoreClient, TestDatabases, TestFeeEstimator}
import fr.acinq.eclair.{BlockHeight, MilliSatoshi, NodeParams, RealShortChannelId, SubscriptionsComplete, TestBitcoinCoreClient, TestDatabases}
import org.scalatest.concurrent.{Eventually, IntegrationPatience}
import org.scalatest.{Assertions, EitherValues}
import java.net.InetAddress
import java.util.UUID
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import scala.concurrent.duration.DurationInt
import scala.util.Random
@ -73,7 +73,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat
torAddress_opt = None,
database = TestDatabases.inMemoryDb(),
blockHeight = new AtomicLong(400_000),
feeEstimator = new TestFeeEstimator(FeeratePerKw(253 sat))
feerates = new AtomicReference(FeeratesPerKw.single(FeeratePerKw(253 sat)))
).modify(_.alias).setTo(alias)
.modify(_.chainHash).setTo(Block.RegtestGenesisBlock.hash)
.modify(_.routerConf.routerBroadcastInterval).setTo(1 second)

View file

@ -20,17 +20,16 @@ import akka.actor.typed.scaladsl.adapter.actorRefAdapter
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestKit, TestProbe}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.TestConstants.{Alice, Bob, feeratePerKw}
import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
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.FakeTxPublisherFactory
import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler}
import fr.acinq.eclair.wire.protocol.Init
import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestFeeEstimator, TestKitBaseClass, TestUtils}
import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestKitBaseClass, TestUtils}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.matchers.should.Matchers
import org.scalatest.{BeforeAndAfterAll, Outcome}
@ -64,9 +63,8 @@ class RustyTestsSpec extends TestKitBaseClass with Matchers with FixtureAnyFunSu
// we just bypass the relayer for this test
val relayer = paymentHandler
val wallet = new DummyOnChainWallet()
val feeEstimator = new TestFeeEstimator()
val aliceNodeParams = Alice.nodeParams.copy(blockHeight = blockHeight, onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator))
val bobNodeParams = Bob.nodeParams.copy(blockHeight = blockHeight, onChainFeeConf = Bob.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator))
val aliceNodeParams = Alice.nodeParams.copy(blockHeight = blockHeight)
val bobNodeParams = Bob.nodeParams.copy(blockHeight = blockHeight)
val channelConfig = ChannelConfig.standard
val channelType = ChannelTypes.Standard()
val alice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(aliceNodeParams, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, relayer, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref)
@ -74,8 +72,7 @@ class RustyTestsSpec extends TestKitBaseClass with Matchers with FixtureAnyFunSu
val aliceInit = Init(Alice.channelParams.initFeatures)
val bobInit = Init(Bob.channelParams.initFeatures)
// alice and bob will both have 1 000 000 sat
feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)))
alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, 2000000 sat, dualFunded = false, feeEstimator.getFeeratePerKw(target = 2), feeEstimator.getFeeratePerKw(target = 6), Some(1000000000 msat), requireConfirmedInputs = false, Alice.channelParams, pipe, bobInit, ChannelFlags.Private, channelConfig, channelType, replyTo = system.deadLetters)
alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, 2000000 sat, dualFunded = false, commitTxFeerate = feeratePerKw, fundingTxFeerate = feeratePerKw, Some(1000000000 msat), requireConfirmedInputs = false, Alice.channelParams, pipe, bobInit, ChannelFlags.Private, channelConfig, channelType, replyTo = system.deadLetters)
alice2blockchain.expectMsgType[TxPublisher.SetChannelId]
bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, Bob.channelParams, pipe, aliceInit, channelConfig, channelType)
bob2blockchain.expectMsgType[TxPublisher.SetChannelId]

View file

@ -465,15 +465,14 @@ class PeerSpec extends FixtureSpec {
assert(peer.stateData.channels.isEmpty)
// We ensure the current network feerate is higher than the default anchor output feerate.
val feeEstimator = nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
nodeParams.setFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None))
val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR]
assert(init.channelType == ChannelTypes.AnchorOutputs())
assert(!init.dualFunded)
assert(init.fundingAmount == 15000.sat)
assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
assert(init.fundingTxFeerate == feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget))
assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates))
}
test("use correct on-chain fee rates when spawning a channel (anchor outputs zero fee htlc)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
@ -484,15 +483,14 @@ class PeerSpec extends FixtureSpec {
assert(peer.stateData.channels.isEmpty)
// We ensure the current network feerate is higher than the default anchor output feerate.
val feeEstimator = nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = FeeratePerKw(250 sat)))
nodeParams.setFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None))
val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR]
assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx())
assert(!init.dualFunded)
assert(init.fundingAmount == 15000.sat)
assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
assert(init.fundingTxFeerate == feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget))
assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates))
}
test("use correct final script if option_static_remotekey is negotiated", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>

View file

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, Btc, ByteVector32, ByteVector64, Det
import fr.acinq.eclair._
import fr.acinq.eclair.balance.CheckBalance
import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, GlobalBalance, MainAndHtlcBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
@ -356,7 +356,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat
val dummyInputInfo = InputInfo(OutPoint(ByteVector32.Zeroes, 0), TxOut(Satoshi(0), Nil), Nil)
val htlcSuccessTx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800")
val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, BlockHeight(1105))
val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, ConfirmationTarget.Absolute(BlockHeight(1105)))
val htlcSuccessJson =
s"""{
| "txid": "${htlcSuccessTx.txid.toHex}",
@ -369,7 +369,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat
assertJsonEquals(JsonSerializers.serialization.write(htlcSuccessTxInfo)(JsonSerializers.formats), htlcSuccessJson)
val claimHtlcTimeoutTx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000")
val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, claimHtlcTimeoutTx, 2, BlockHeight(144))
val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, claimHtlcTimeoutTx, 2, ConfirmationTarget.Absolute(BlockHeight(144)))
val claimHtlcTimeoutJson =
s"""{
| "txid": "${claimHtlcTimeoutTx.txid.toHex}",

View file

@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, ripemd160, sha256}
import fr.acinq.bitcoin.scalacompat.Script.{pay2wpkh, pay2wsh, write}
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, Crypto, MilliBtc, MilliBtcDouble, OutPoint, Protocol, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, millibtc2satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.transactions.CommitmentOutput.{InHtlc, OutHtlc}
import fr.acinq.eclair.transactions.Scripts.{anchor, htlcOffered, htlcReceived, toLocalDelayed}
@ -198,9 +198,9 @@ class TransactionsSpec extends AnyFunSuite with Logging {
// first we create a fake commitTx tx, containing only the output that will be spent by the ClaimAnchorOutputTx
val pubKeyScript = write(pay2wsh(anchor(localFundingPriv.publicKey)))
val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(anchorAmount, pubKeyScript) :: Nil, lockTime = 0)
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, BlockHeight(1105))
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(1105)))
assert(claimAnchorOutputTx.tx.txOut.isEmpty)
assert(claimAnchorOutputTx.confirmBefore == BlockHeight(1105))
assert(claimAnchorOutputTx.confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(1105)))
// we will always add at least one input and one output to be able to set our desired feerate
// we use dummy signatures to compute the weight
val p2wpkhWitness = ScriptWitness(Seq(Scripts.der(PlaceHolderSig), PlaceHolderPubKey.value))
@ -304,7 +304,7 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val htlcTxs = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(DefaultCommitmentFormat), outputs, DefaultCommitmentFormat)
assert(htlcTxs.length == 4)
val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap
val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap
assert(confirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 295, 3 -> 300))
val htlcSuccessTxs = htlcTxs.collect { case tx: HtlcSuccessTx => tx }
val htlcTimeoutTxs = htlcTxs.collect { case tx: HtlcTimeoutTx => tx }
@ -532,7 +532,7 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val htlcTxs = makeHtlcTxs(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(UnsafeLegacyAnchorOutputsCommitmentFormat), outputs, UnsafeLegacyAnchorOutputsCommitmentFormat)
assert(htlcTxs.length == 5)
val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap
val confirmationTargets = htlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap
assert(confirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 310, 3 -> 295, 4 -> 300))
val htlcSuccessTxs = htlcTxs.collect { case tx: HtlcSuccessTx => tx }
val htlcTimeoutTxs = htlcTxs.collect { case tx: HtlcTimeoutTx => tx }
@ -545,7 +545,7 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val zeroFeeCommitTx = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsInitiator = true, zeroFeeOutputs)
val zeroFeeHtlcTxs = makeHtlcTxs(zeroFeeCommitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat), zeroFeeOutputs, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)
assert(zeroFeeHtlcTxs.length == 7)
val zeroFeeConfirmationTargets = zeroFeeHtlcTxs.map(tx => tx.htlcId -> tx.confirmBefore.toLong).toMap
val zeroFeeConfirmationTargets = zeroFeeHtlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap
assert(zeroFeeConfirmationTargets == Map(0 -> 300, 1 -> 310, 2 -> 310, 3 -> 295, 4 -> 300, 7 -> 300, 8 -> 302))
val zeroFeeHtlcSuccessTxs = zeroFeeHtlcTxs.collect { case tx: HtlcSuccessTx => tx }
val zeroFeeHtlcTimeoutTxs = zeroFeeHtlcTxs.collect { case tx: HtlcTimeoutTx => tx }
@ -580,7 +580,7 @@ class TransactionsSpec extends AnyFunSuite with Logging {
}
{
// local spends local anchor
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, BlockHeight(0))
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)))
assert(checkSpendable(claimAnchorOutputTx).isFailure)
val localSig = sign(claimAnchorOutputTx, localFundingPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat)
val signedTx = addSigs(claimAnchorOutputTx, localSig)
@ -588,7 +588,7 @@ class TransactionsSpec extends AnyFunSuite with Logging {
}
{
// remote spends remote anchor
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, BlockHeight(0))
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)))
assert(checkSpendable(claimAnchorOutputTx).isFailure)
val localSig = sign(claimAnchorOutputTx, remoteFundingPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat)
val signedTx = addSigs(claimAnchorOutputTx, localSig)

View file

@ -15,7 +15,7 @@
*/
package fr.acinq.eclair.wire.internal.channel.version3
import fr.acinq.bitcoin.scalacompat.ByteVector32
import fr.acinq.eclair.blockchain.fee.ConfirmationTarget
import fr.acinq.eclair.channel._
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs._
@ -75,19 +75,6 @@ class ChannelCodecs3Spec extends AnyFunSuite {
assert(claimHtlcSuccessTx1.isInstanceOf[LegacyClaimHtlcSuccessTx])
claimHtlcSuccessTx1.asInstanceOf[LegacyClaimHtlcSuccessTx]
}
// We can encode data that contains a payment hash.
val claimHtlcSuccess = ClaimHtlcSuccessTx(legacyClaimHtlcSuccess.input, legacyClaimHtlcSuccess.tx, ByteVector32(hex"0101010101010101010101010101010101010101010101010101010101010101"), legacyClaimHtlcSuccess.htlcId, BlockHeight(0))
val txWithInputInfoCodecBin = txWithInputInfoCodec.encode(claimHtlcSuccess).require.bytes
assert(txWithInputInfoCodecBin !== oldTxWithInputInfoCodecBin)
val claimHtlcTxBin = claimHtlcTxCodec.encode(claimHtlcSuccess).require.bytes
assert(claimHtlcTxBin !== oldClaimHtlcTxBin)
// And decode new data that contains a payment hash.
val decoded1 = txWithInputInfoCodec.decode(txWithInputInfoCodecBin.bits).require.value
assert(claimHtlcSuccess == decoded1)
val decoded2 = claimHtlcTxCodec.decode(claimHtlcTxBin.bits).require.value
assert(claimHtlcSuccess == decoded2)
}
test("backwards compatibility with transactions missing a confirmation target") {
@ -95,46 +82,31 @@ class ChannelCodecs3Spec extends AnyFunSuite {
val oldAnchorTxBin = hex"0011 24bd0be30e31c748c7afdde7d2c527d711fadf88500971a4a1136bca375dba07b8000000002b4a0100000000000022002036c067df8952dbcd5db347e7c152ca3fa4514f2072d27867837b1c2d319a7e01282103cc89f1459b5201cda08e08c6fb7b1968c54e8172c555896da27c6fdc10522ceeac736460b268330200000001bd0be30e31c748c7afdde7d2c527d711fadf88500971a4a1136bca375dba07b80000000000000000000000000000"
val oldAnchorTx = txWithInputInfoCodec.decode(oldAnchorTxBin.bits).require.value
assert(oldAnchorTx.isInstanceOf[ClaimLocalAnchorOutputTx])
assert(oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].confirmBefore == BlockHeight(0))
val anchorTx = oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].copy(confirmBefore = BlockHeight(1105))
val anchorTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(anchorTx).require).require.value
assert(anchorTx == anchorTx2)
assert(oldAnchorTx.asInstanceOf[ClaimLocalAnchorOutputTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0)))
}
{
val oldHtlcSuccessTxBin = hex"0002 24f5580de0577271dce09d2de26e19ec58bf2373b0171473291a8f8be9b04fb289000000002bb0ad010000000000220020462cf8912ffc5f27764c109bed188950500011a2837ff8b9c8f9a39cffa395a58b76a91406b0950d9feded82239b3e6c9082308900f389de8763ac672102d65aa07658a7214ff129f91a1a22ade2ea4d1b07cc14b2f85a2842c34240836f7c8201208763a914c461c897e2165c7e44e14850dfcfd68f99127aed88527c21033c8d41cfbe1511a909b63fed68e75a29c3ce30418c39bbb8294c8b36c6a6c16a52ae677503101b06b175ac6868fd01a002000000000101f5580de0577271dce09d2de26e19ec58bf2373b0171473291a8f8be9b04fb289000000000000000000013a920100000000002200208742b16c9fd4e74854dcd84322dd1de06f7993fe627fd2ca0be4b996a936d56b050047304402201b4527c8f420852550af00bbd9149db9b31adcb7e1f127766e75e1e01746df0302202a57bb1e274ed7d3e8dbe5f205de721a23092c1e2ce2135f4750f18f6c0b51b001483045022100b6df309c8e5746a077b1f7c2f299528e164946bd514a5049475af7f5665805da0220392ae877112a3c52f74d190b354b4f5c020da9c1a71a7a08ced0a5363e795a27012017ea8f5afde8f708258d5669e1bbd454e82ddca8c6c480ec5302b4b1e8051d3d8b76a91406b0950d9feded82239b3e6c9082308900f389de8763ac672102d65aa07658a7214ff129f91a1a22ade2ea4d1b07cc14b2f85a2842c34240836f7c8201208763a914c461c897e2165c7e44e14850dfcfd68f99127aed88527c21033c8d41cfbe1511a909b63fed68e75a29c3ce30418c39bbb8294c8b36c6a6c16a52ae677503101b06b175ac686800000000dc7002a387673f17ebaf08545ccec712a9b6914813cdb83b4270932294f20f660000000000000000"
val oldHtlcSuccessTx = txWithInputInfoCodec.decode(oldHtlcSuccessTxBin.bits).require.value
assert(oldHtlcSuccessTx.isInstanceOf[HtlcSuccessTx])
assert(oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].confirmBefore == BlockHeight(0))
val htlcSuccessTx = oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].copy(confirmBefore = BlockHeight(1105))
val htlcSuccessTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(htlcSuccessTx).require).require.value
assert(htlcSuccessTx == htlcSuccessTx2)
assert(oldHtlcSuccessTx.asInstanceOf[HtlcSuccessTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0)))
}
{
val oldHtlcTimeoutTxBin = hex"0003 248f0619a4b2a351977b3e5b0ddd700482e1d697d40deea2dd7356df99345d51d0000000002b50c300000000000022002005ff644937d7f5f32ec194424f551371e8d4bcf2cda3e1096cdd2fe88687fc408576a914c98707b6420ef3454f3bd10d663adcc04452baea8763ac672102fb20469287c8ade948011bd001440d74633d5e1a98574e4783dd38b76509d8f67c820120876475527c210279d3a1c2086a0968404a0160c8f8c6f88c0ce7184022bb7406f98fdb503ea51452ae67a9140550b2b1621e788d795fe3ae308e7dec06a6a1e088ac6868fd0179020000000001018f0619a4b2a351977b3e5b0ddd700482e1d697d40deea2dd7356df99345d51d0000000000000000000016aa9000000000000220020c980a1573ce6dc6a1bb8a1d60ecffdf2f0a7aa983c49786a2ab1ba8f0cd74a76050047304402200d2b631fb0e5a7f406f3de2b36fe3c0ab3337fe98f114dadf38de8f5548e87eb02203ad7c385c7cf62ac17ec15329dee071ee2c479aceb34a14d700dfeba80008574014730440220653b4ff490b03de06a9053aa7ab0b30e85c484c76900c586db65f7308f38ad86022019ac12ffb127e4a17a01dc6db730d3eccea837d2dc85a0275bdea8b393a2f11d01008576a914c98707b6420ef3454f3bd10d663adcc04452baea8763ac672102fb20469287c8ade948011bd001440d74633d5e1a98574e4783dd38b76509d8f67c820120876475527c210279d3a1c2086a0968404a0160c8f8c6f88c0ce7184022bb7406f98fdb503ea51452ae67a9140550b2b1621e788d795fe3ae308e7dec06a6a1e088ac6868101b06000000000000000003"
val oldHtlcTimeoutTx = txWithInputInfoCodec.decode(oldHtlcTimeoutTxBin.bits).require.value
assert(oldHtlcTimeoutTx.isInstanceOf[HtlcTimeoutTx])
assert(oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].confirmBefore == BlockHeight(0))
val htlcTimeoutTx = oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].copy(confirmBefore = BlockHeight(1105))
val htlcTimeoutTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(htlcTimeoutTx).require).require.value
assert(htlcTimeoutTx == htlcTimeoutTx2)
assert(oldHtlcTimeoutTx.asInstanceOf[HtlcTimeoutTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0)))
}
{
val oldClaimHtlcSuccessTxBin = hex"0016 24e75b5236d1cdd482a6f540d5f08b9aa27b74a9ecae6e2622a67110b3ee1b3d89000000002bb0ad01000000000022002063e22369052a2bad9eb124737742690b8d1aba7693869d041da16443e2973e638576a91494957f4639ebc6f8a30e126552aff8429174dfb18763ac672102e1aa04ff55771238012edb958e6e0525af0415a01d52dd8d5f69fb391e586adc7c820120876475527c2102384e785d34b3b1fe35d3c093a750b234fbc79d8316c149e7845929f628a5baa052ae67a9145e3be49c9ace2d9eab11dc5fc29e40cd4148262e88ac6868fd014502000000000101e75b5236d1cdd482a6f540d5f08b9aa27b74a9ecae6e2622a67110b3ee1b3d890000000000000000000162970100000000001600140262586eef1a2c8f47ebc139a2733123d09e315603483045022100898f6b51361f044c8c54468dcb1b9decd75edcc1b69b62e584059b512eb075900220675f8b23aa402bb7d5bfdefbac4074088f82bd34d09b34c651c0dff48e6967930120efb38d645311af59c028cd8a9bf8ee21ff9e7c1a1cff1a0398a0315280247ac38576a91494957f4639ebc6f8a30e126552aff8429174dfb18763ac672102e1aa04ff55771238012edb958e6e0525af0415a01d52dd8d5f69fb391e586adc7c820120876475527c2102384e785d34b3b1fe35d3c093a750b234fbc79d8316c149e7845929f628a5baa052ae67a9145e3be49c9ace2d9eab11dc5fc29e40cd4148262e88ac686800000000ad02394dd18774f6f403f783afb518b6c69a89d531f718b29868ddca3e7905020000000000000000"
val oldClaimHtlcSuccessTx = txWithInputInfoCodec.decode(oldClaimHtlcSuccessTxBin.bits).require.value
assert(oldClaimHtlcSuccessTx.isInstanceOf[ClaimHtlcSuccessTx])
assert(oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].confirmBefore == BlockHeight(0))
val claimHtlcSuccessTx = oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmBefore = BlockHeight(1105))
val claimHtlcSuccessTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(claimHtlcSuccessTx).require).require.value
assert(claimHtlcSuccessTx == claimHtlcSuccessTx2)
assert(oldClaimHtlcSuccessTx.asInstanceOf[ClaimHtlcSuccessTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0)))
}
{
val oldClaimHtlcTimeoutTxBin = hex"0005 24df6aa4cd4e8877e4b3a363d6180951036d6f18ef214dabd50a6c05a60077d4d8000000002b50c30000000000002200205db892d76fb358ed508ec09c77b5a184cd9de3aa3e74a025a5c5d3f7adc221f78b76a914e0c241e0656088953f84475cbe5c70ded12e05b58763ac6721028876f3e23b21e07f889f10fc2aa0875b96021359a06d0b7f52a79fc284f6b2837c8201208763a9144ae5c96e8b7495fd35a5ca5681aa7c8f4ab6bc9b88527c2102b4d398ee7a42e87012de5a76832d9ebfa8532ef267490a7f3fe75b2c2e18cc9152ae677503981a06b175ac6868fd012b02000000000101df6aa4cd4e8877e4b3a363d6180951036d6f18ef214dabd50a6c05a60077d4d80000000000000000000106ae0000000000001600142b8e221121004b248f6551a0c6fb8ce219f4997e03483045022100ecdb8179f92c097594844756ac2bd7948f1c44ae74b54dfda86971800e59bf980220125edbe563f24cde15fa1fa25f4eac7cb938a3ace6f9329eb487938005c7621501008b76a914e0c241e0656088953f84475cbe5c70ded12e05b58763ac6721028876f3e23b21e07f889f10fc2aa0875b96021359a06d0b7f52a79fc284f6b2837c8201208763a9144ae5c96e8b7495fd35a5ca5681aa7c8f4ab6bc9b88527c2102b4d398ee7a42e87012de5a76832d9ebfa8532ef267490a7f3fe75b2c2e18cc9152ae677503981a06b175ac6868981a06000000000000000003"
val oldClaimHtlcTimeoutTx = txWithInputInfoCodec.decode(oldClaimHtlcTimeoutTxBin.bits).require.value
assert(oldClaimHtlcTimeoutTx.isInstanceOf[ClaimHtlcTimeoutTx])
assert(oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].confirmBefore == BlockHeight(0))
val claimHtlcTimeoutTx = oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmBefore = BlockHeight(1105))
val claimHtlcTimeoutTx2 = txWithInputInfoCodec.decode(txWithInputInfoCodec.encode(claimHtlcTimeoutTx).require).require.value
assert(claimHtlcTimeoutTx == claimHtlcTimeoutTx2)
assert(oldClaimHtlcTimeoutTx.asInstanceOf[ClaimHtlcTimeoutTx].confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(0)))
}
}