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:
parent
194f5dd2b8
commit
da98e19540
52 changed files with 807 additions and 826 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)))
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue