1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-19 01:43:22 +01:00

Add recommended_feerates optional message (#2860)

We send to our peers an optional message that tells them the feerates
we'd like to use for funding channels. This lets them know which values
are acceptable to us, in case we reject their funding requests.

This is using an odd type and will be automatically ignored by existing
nodes who don't support that feature.

Co-authored-by: Pierre-Marie Padiou <pm47@users.noreply.github.com>
This commit is contained in:
Bastien Teinturier 2024-09-24 16:22:24 +02:00 committed by GitHub
parent cfdb0885f8
commit db8290f80e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 241 additions and 118 deletions

View File

@ -381,9 +381,9 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
override def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[TxId] = { override def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[TxId] = {
val feeRate = confirmationTargetOrFeerate match { val feeRate = confirmationTargetOrFeerate match {
case Left(blocks) => case Left(blocks) =>
if (blocks < 3) appKit.nodeParams.currentFeerates.fast if (blocks < 3) appKit.nodeParams.currentBitcoinCoreFeerates.fast
else if (blocks > 6) appKit.nodeParams.currentFeerates.slow else if (blocks > 6) appKit.nodeParams.currentBitcoinCoreFeerates.slow
else appKit.nodeParams.currentFeerates.medium else appKit.nodeParams.currentBitcoinCoreFeerates.medium
case Right(feeratePerByte) => FeeratePerKw(feeratePerByte) case Right(feeratePerByte) => FeeratePerKw(feeratePerByte)
} }
appKit.wallet match { appKit.wallet match {

View File

@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong} import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong}
import fr.acinq.eclair.Setup.Seeds import fr.acinq.eclair.Setup.Seeds
import fr.acinq.eclair.blockchain.fee._ import fr.acinq.eclair.blockchain.fee._
import fr.acinq.eclair.channel.ChannelFlags
import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy} import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy}
import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes}
import fr.acinq.eclair.crypto.Noise.KeyPair import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager} import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager}
import fr.acinq.eclair.db._ import fr.acinq.eclair.db._
@ -36,6 +36,7 @@ import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios}
import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf} import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf}
import fr.acinq.eclair.tor.Socks5ProxyParams import fr.acinq.eclair.tor.Socks5ProxyParams
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.wire.protocol._
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -57,7 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
onChainKeyManager_opt: Option[OnChainKeyManager], onChainKeyManager_opt: Option[OnChainKeyManager],
instanceId: UUID, // a unique instance ID regenerated after each restart instanceId: UUID, // a unique instance ID regenerated after each restart
private val blockHeight: AtomicLong, private val blockHeight: AtomicLong,
private val feerates: AtomicReference[FeeratesPerKw], private val bitcoinCoreFeerates: AtomicReference[FeeratesPerKw],
alias: String, alias: String,
color: Color, color: Color,
publicAddresses: List[NodeAddress], publicAddresses: List[NodeAddress],
@ -102,13 +103,34 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get) def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get)
def currentFeerates: FeeratesPerKw = feerates.get() def currentBitcoinCoreFeerates: FeeratesPerKw = bitcoinCoreFeerates.get()
/** Only to be used in tests. */ /** Only to be used in tests. */
def setFeerates(value: FeeratesPerKw): Unit = feerates.set(value) def setBitcoinCoreFeerates(value: FeeratesPerKw): Unit = bitcoinCoreFeerates.set(value)
/** Returns the features that should be used in our init message with the given peer. */ /** 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() def initFeaturesFor(nodeId: PublicKey): Features[InitFeature] = overrideInitFeatures.getOrElse(nodeId, features).initFeatures()
/** Returns the feerates we'd like our peer to use when funding channels. */
def recommendedFeerates(remoteNodeId: PublicKey, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): RecommendedFeerates = {
val feerateTolerance = onChainFeeConf.feerateToleranceFor(remoteNodeId)
val fundingFeerate = onChainFeeConf.getFundingFeerate(currentBitcoinCoreFeerates)
val fundingRange = RecommendedFeeratesTlv.FundingFeerateRange(
min = fundingFeerate * feerateTolerance.ratioLow,
max = fundingFeerate * feerateTolerance.ratioHigh,
)
// We use the most likely commitment format, even though there is no guarantee that this is the one that will be used.
val commitmentFormat = ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, announceChannel = false).commitmentFormat
val commitmentFeerate = onChainFeeConf.getCommitmentFeerate(currentBitcoinCoreFeerates, remoteNodeId, commitmentFormat, channelConf.minFundingPrivateSatoshis)
val commitmentRange = RecommendedFeeratesTlv.CommitmentFeerateRange(
min = commitmentFeerate * feerateTolerance.ratioLow,
max = commitmentFormat match {
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
case _: Transactions.AnchorOutputsCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
},
)
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))
}
} }
case class PaymentFinalExpiryConf(min: CltvExpiryDelta, max: CltvExpiryDelta) { case class PaymentFinalExpiryConf(min: CltvExpiryDelta, max: CltvExpiryDelta) {
@ -219,7 +241,7 @@ object NodeParams extends Logging {
def makeNodeParams(config: Config, instanceId: UUID, def makeNodeParams(config: Config, instanceId: UUID,
nodeKeyManager: NodeKeyManager, channelKeyManager: ChannelKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager], nodeKeyManager: NodeKeyManager, channelKeyManager: ChannelKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager],
torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, feerates: AtomicReference[FeeratesPerKw], torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, bitcoinCoreFeerates: AtomicReference[FeeratesPerKw],
pluginParams: Seq[PluginParams] = Nil): NodeParams = { pluginParams: Seq[PluginParams] = Nil): NodeParams = {
// check configuration for keys that have been renamed // check configuration for keys that have been renamed
val deprecatedKeyPaths = Map( val deprecatedKeyPaths = Map(
@ -513,7 +535,7 @@ object NodeParams extends Logging {
onChainKeyManager_opt = onChainKeyManager_opt, onChainKeyManager_opt = onChainKeyManager_opt,
instanceId = instanceId, instanceId = instanceId,
blockHeight = blockHeight, blockHeight = blockHeight,
feerates = feerates, bitcoinCoreFeerates = bitcoinCoreFeerates,
alias = nodeAlias, alias = nodeAlias,
color = Color(color(0), color(1), color(2)), color = Color(color(0), color(1), color(2)),
publicAddresses = addresses, publicAddresses = addresses,

View File

@ -255,7 +255,7 @@ class Setup(val datadir: File,
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.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.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) 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)) system.eventStream.publish(CurrentFeerates.BitcoinCore(feeratesPerKw.get))
logger.info(s"current feeratesPerKB=$feeratesPerKB feeratesPerKw=${feeratesPerKw.get}") logger.info(s"current feeratesPerKB=$feeratesPerKB feeratesPerKw=${feeratesPerKw.get}")
feeratesRetrieved.trySuccess(Done) feeratesRetrieved.trySuccess(Done)
case Failure(exception) => case Failure(exception) =>

View File

@ -32,4 +32,14 @@ case class NewTransaction(tx: Transaction) extends BlockchainEvent
case class CurrentBlockHeight(blockHeight: BlockHeight) extends BlockchainEvent case class CurrentBlockHeight(blockHeight: BlockHeight) extends BlockchainEvent
case class CurrentFeerates(feeratesPerKw: FeeratesPerKw) extends BlockchainEvent sealed trait CurrentFeerates extends BlockchainEvent {
val feeratesPerKw: FeeratesPerKw
}
object CurrentFeerates {
//@formatter:off
case class BitcoinCore(feeratesPerKw: FeeratesPerKw) extends CurrentFeerates
//@formatter:on
}

View File

@ -119,7 +119,7 @@ object Helpers {
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis) val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis)
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw)) if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, 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 // we don't check that the funder's amount for the initial commitment transaction is sufficient for full fee payment
@ -166,7 +166,7 @@ object Helpers {
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount) val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount)
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate)) if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate))
for { for {

View File

@ -223,7 +223,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
// the constant delay by which we delay processing of blocks (it will be smoothened among all channels) // the constant delay by which we delay processing of blocks (it will be smoothened among all channels)
private val blockProcessingDelay = Random.nextLong(nodeParams.channelConf.maxBlockProcessingDelay.toMillis + 1).millis private val blockProcessingDelay = Random.nextLong(nodeParams.channelConf.maxBlockProcessingDelay.toMillis + 1).millis
// this will be used to make sure the current commitment fee is up-to-date // this will be used to make sure the current commitment fee is up-to-date
context.system.eventStream.subscribe(self, classOf[CurrentFeerates]) context.system.eventStream.subscribe(self, classOf[CurrentFeerates.BitcoinCore])
/* /*
8888888 888b 888 8888888 88888888888 8888888 888b 888 8888888 88888888888
@ -435,7 +435,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
handleAddHtlcCommandError(c, error, Some(d.channelUpdate)) handleAddHtlcCommandError(c, error, Some(d.channelUpdate))
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) =>
d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match { d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match {
case Right((commitments1, add)) => case Right((commitments1, add)) =>
if (c.commit) self ! CMD_SIGN() if (c.commit) self ! CMD_SIGN()
context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1))
@ -444,7 +444,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} }
case Event(add: UpdateAddHtlc, d: DATA_NORMAL) => case Event(add: UpdateAddHtlc, d: DATA_NORMAL) =>
d.commitments.receiveAdd(add, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match { d.commitments.receiveAdd(add, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1) case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(add)) case Left(cause) => handleLocalError(cause, d, Some(add))
} }
@ -519,7 +519,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} }
case Event(fee: UpdateFee, d: DATA_NORMAL) => case Event(fee: UpdateFee, d: DATA_NORMAL) =>
d.commitments.receiveFee(fee, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match { d.commitments.receiveFee(fee, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1) case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(fee)) case Left(cause) => handleLocalError(cause, d, Some(fee))
} }
@ -735,7 +735,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
// there are no pending signed changes, let's go directly to NEGOTIATING // there are no pending signed changes, let's go directly to NEGOTIATING
if (d.commitments.params.localParams.paysClosingFees) { if (d.commitments.params.localParams.paysClosingFees) {
// we pay the closing fees, so we initiate the negotiation by sending the first closing_signed // we pay the closing fees, so we initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, localShutdown.scriptPubKey, remoteShutdownScript, nodeParams.currentFeerates, nodeParams.onChainFeeConf, d.closingFeerates) val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, localShutdown.scriptPubKey, remoteShutdownScript, nodeParams.currentBitcoinCoreFeerates, 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 goto(NEGOTIATING) using DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending sendList :+ closingSigned
} else { } else {
// we are not the channel initiator, will wait for their closing_signed // we are not the channel initiator, will wait for their closing_signed
@ -750,7 +750,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Event(ProcessCurrentBlockHeight(c), d: DATA_NORMAL) => handleNewBlock(c, d) case Event(ProcessCurrentBlockHeight(c), d: DATA_NORMAL) => handleNewBlock(c, d)
case Event(c: CurrentFeerates, d: DATA_NORMAL) => handleCurrentFeerate(c, d) case Event(c: CurrentFeerates.BitcoinCore, d: DATA_NORMAL) => handleCurrentFeerate(c, d)
case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty =>
val finalRealShortId = RealScidStatus.Final(RealShortChannelId(blockHeight, txIndex, d.commitments.latest.commitInput.outPoint.index.toInt)) val finalRealShortId = RealScidStatus.Final(RealShortChannelId(blockHeight, txIndex, d.commitments.latest.commitInput.outPoint.index.toInt))
@ -944,7 +944,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (!d.commitments.isQuiescent) { if (!d.commitments.isQuiescent) {
log.info("rejecting splice request: channel not quiescent") log.info("rejecting splice request: channel not quiescent")
stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceNotQuiescent(d.channelId).getMessage) stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceNotQuiescent(d.channelId).getMessage)
} else if (msg.feerate < nodeParams.currentFeerates.minimum) { } else if (msg.feerate < nodeParams.currentBitcoinCoreFeerates.minimum) {
log.info("rejecting splice request: feerate too low") log.info("rejecting splice request: feerate too low")
stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceRequest(d.channelId).getMessage) stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceRequest(d.channelId).getMessage)
} else { } else {
@ -1295,7 +1295,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} }
case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => case Event(fee: UpdateFee, d: DATA_SHUTDOWN) =>
d.commitments.receiveFee(fee, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match { d.commitments.receiveFee(fee, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match {
case Right(commitments1) => stay() using d.copy(commitments = commitments1) case Right(commitments1) => stay() using d.copy(commitments = commitments1)
case Left(cause) => handleLocalError(cause, d, Some(fee)) case Left(cause) => handleLocalError(cause, d, Some(fee))
} }
@ -1341,7 +1341,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (commitments1.hasNoPendingHtlcsOrFeeUpdate) { if (commitments1.hasNoPendingHtlcsOrFeeUpdate) {
if (d.commitments.params.localParams.paysClosingFees) { if (d.commitments.params.localParams.paysClosingFees) {
// we pay the closing fees, so we initiate the negotiation by sending the first closing_signed // we pay the closing fees, so we initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, closingFeerates) val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closingFeerates)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending revocation :: closingSigned :: Nil goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending revocation :: closingSigned :: Nil
} else { } else {
// we are not the channel initiator, will wait for their closing_signed // we are not the channel initiator, will wait for their closing_signed
@ -1383,7 +1383,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
log.debug("switching to NEGOTIATING spec:\n{}", commitments1.latest.specs2String) log.debug("switching to NEGOTIATING spec:\n{}", commitments1.latest.specs2String)
if (d.commitments.params.localParams.paysClosingFees) { if (d.commitments.params.localParams.paysClosingFees) {
// we pay the closing fees, so we initiate the negotiation by sending the first closing_signed // we pay the closing fees, so we initiate the negotiation by sending the first closing_signed
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, closingFeerates) val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, commitments1.latest, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, closingFeerates)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending closingSigned goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx, closingSigned))), bestUnpublishedClosingTx_opt = None) storing() sending closingSigned
} else { } else {
// we are not the channel initiator, will wait for their closing_signed // we are not the channel initiator, will wait for their closing_signed
@ -1402,7 +1402,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Event(ProcessCurrentBlockHeight(c), d: DATA_SHUTDOWN) => handleNewBlock(c, d) case Event(ProcessCurrentBlockHeight(c), d: DATA_SHUTDOWN) => handleNewBlock(c, d)
case Event(c: CurrentFeerates, d: DATA_SHUTDOWN) => handleCurrentFeerate(c, d) case Event(c: CurrentFeerates.BitcoinCore, d: DATA_SHUTDOWN) => handleCurrentFeerate(c, d)
case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) => case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) =>
c.feerates match { c.feerates match {
@ -1456,7 +1456,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.params.localParams.paysClosingFees => case Some(ClosingSignedTlv.FeeRange(minFee, maxFee)) if !d.commitments.params.localParams.paysClosingFees =>
// if we are not paying the closing fees and they proposed a fee range, we pick a value in that range and they should accept it without further negotiation // if we are not paying the closing fees and they proposed a fee range, we pick a value in that range and they should accept it without further negotiation
// we don't care much about the closing fee since they're paying it (not us) and we can use CPFP if we want to speed up confirmation // 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.currentFeerates, nodeParams.onChainFeeConf) val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf)
if (maxFee < localClosingFees.min) { if (maxFee < localClosingFees.min) {
log.warning("their highest closing fee is below our minimum fee: {} < {}", 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}") stay() sending Warning(d.channelId, s"closing fee range must not be below ${localClosingFees.min}")
@ -1483,7 +1483,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis) val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis)
val (closingTx, closingSigned) = { 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 // 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.currentFeerates, nodeParams.onChainFeeConf) val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf)
val nextPreferredFee = Closing.MutualClose.nextClosingFee(lastLocalClosingFee_opt.getOrElse(localClosingFees.preferred), remoteClosingFee) 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)) Closing.MutualClose.makeClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, localClosingFees.copy(preferred = nextPreferredFee))
} }
@ -1514,7 +1514,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
handleCommandError(ClosingAlreadyInProgress(d.channelId), c) handleCommandError(ClosingAlreadyInProgress(d.channelId), c)
} else { } else {
log.info("updating our closing feerates: {}", feerates) log.info("updating our closing feerates: {}", feerates)
val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentFeerates, nodeParams.onChainFeeConf, Some(feerates)) val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, Some(feerates))
val closingTxProposed1 = d.closingTxProposed match { val closingTxProposed1 = d.closingTxProposed match {
case previousNegotiations :+ currentNegotiation => previousNegotiations :+ (currentNegotiation :+ ClosingTxProposed(closingTx, closingSigned)) case previousNegotiations :+ currentNegotiation => previousNegotiations :+ (currentNegotiation :+ ClosingTxProposed(closingTx, closingSigned))
case previousNegotiations => previousNegotiations :+ List(ClosingTxProposed(closingTx, closingSigned)) case previousNegotiations => previousNegotiations :+ List(ClosingTxProposed(closingTx, closingSigned))
@ -1540,8 +1540,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
log.info("got valid settlement for htlc={}, recalculating htlc transactions", c.id) log.info("got valid settlement for htlc={}, recalculating htlc transactions", c.id)
val commitment = commitments1.latest val commitment = commitments1.latest
val localCommitPublished1 = d.localCommitPublished.map(localCommitPublished => localCommitPublished.copy(htlcTxs = Closing.LocalClose.claimHtlcOutputs(keyManager, commitment))) 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.currentFeerates, d.finalScriptPubKey))) val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.remoteCommit, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey)))
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.nextRemoteCommit_opt.get.commit, nodeParams.currentFeerates, d.finalScriptPubKey))) val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.nextRemoteCommit_opt.get.commit, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey)))
def republish(): Unit = { def republish(): Unit = {
localCommitPublished1.foreach(lcp => doPublish(lcp, commitment)) localCommitPublished1.foreach(lcp => doPublish(lcp, commitment))
@ -1716,7 +1716,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} }
} }
val revokedCommitPublished1 = d.revokedCommitPublished.map { rev => val revokedCommitPublished1 = d.revokedCommitPublished.map { rev =>
val (rev1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, rev, tx, nodeParams.currentFeerates, d.finalScriptPubKey) val (rev1, penaltyTxs) = Closing.RevokedClose.claimHtlcTxOutputs(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, rev, tx, nodeParams.currentBitcoinCoreFeerates, d.finalScriptPubKey)
penaltyTxs.foreach(claimTx => txPublisher ! PublishFinalTx(claimTx, claimTx.fee, None)) 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))) penaltyTxs.foreach(claimTx => blockchain ! WatchOutputSpent(self, tx.txid, claimTx.input.outPoint.index.toInt, hints = Set(claimTx.tx.txid)))
rev1 rev1
@ -1730,7 +1730,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
val d1 = d.copy( val d1 = d.copy(
localCommitPublished = d.localCommitPublished.map(localCommitPublished => { 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. // 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.currentFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey) val (localCommitPublished1, claimHtlcTx_opt) = Closing.LocalClose.claimHtlcDelayedOutput(localCommitPublished, keyManager, d.commitments.latest, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey)
claimHtlcTx_opt.foreach(claimHtlcTx => { claimHtlcTx_opt.foreach(claimHtlcTx => {
txPublisher ! PublishFinalTx(claimHtlcTx, claimHtlcTx.fee, None) txPublisher ! PublishFinalTx(claimHtlcTx, claimHtlcTx.fee, None)
blockchain ! WatchTxConfirmed(self, claimHtlcTx.tx.txid, nodeParams.channelConf.minDepthBlocks, Some(RelativeDelay(tx.txid, d.commitments.params.remoteParams.toSelfDelay.toInt.toLong))) blockchain ! WatchTxConfirmed(self, claimHtlcTx.tx.txid, nodeParams.channelConf.minDepthBlocks, Some(RelativeDelay(tx.txid, d.commitments.params.remoteParams.toSelfDelay.toInt.toLong)))
@ -1939,7 +1939,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d) case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d)
case Event(c: CurrentFeerates, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d) case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d)
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => handleAddDisconnected(c, d) case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => handleAddDisconnected(c, d)
@ -2133,7 +2133,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
if (d.commitments.params.localParams.paysCommitTxFees && !shutdownInProgress) { if (d.commitments.params.localParams.paysCommitTxFees && !shutdownInProgress) {
// TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw? // 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 currentFeeratePerKw = d.commitments.latest.localCommit.spec.commitTxFeerate
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.commitmentFormat, d.commitments.latest.capacity) val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.params.commitmentFormat, d.commitments.latest.capacity)
if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) { if (nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)) {
self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true)
} }
@ -2163,7 +2163,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 // 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.paysClosingFees) { if (d.commitments.params.localParams.paysClosingFees) {
// 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 // 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.currentFeerates, nodeParams.onChainFeeConf, None) val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, None)
val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx, closingSigned)) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx, closingSigned))
goto(NEGOTIATING) using d.copy(closingTxProposed = closingTxProposed1) storing() sending d.localShutdown :: closingSigned :: Nil goto(NEGOTIATING) using d.copy(closingTxProposed = closingTxProposed1) storing() sending d.localShutdown :: closingSigned :: Nil
} else { } else {
@ -2192,7 +2192,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d) case Event(ProcessCurrentBlockHeight(c), d: ChannelDataWithCommitments) => handleNewBlock(c, d)
case Event(c: CurrentFeerates, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d) case Event(c: CurrentFeerates.BitcoinCore, d: ChannelDataWithCommitments) => handleCurrentFeerateDisconnected(c, d)
case Event(getTxResponse: GetTxWithMetaResponse, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if getTxResponse.txid == d.commitments.latest.fundingTxId => handleGetFundingTx(getTxResponse, d.waitingSince, d.fundingTx_opt) case Event(getTxResponse: GetTxWithMetaResponse, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if getTxResponse.txid == d.commitments.latest.fundingTxId => handleGetFundingTx(getTxResponse, d.waitingSince, d.fundingTx_opt)
@ -2282,7 +2282,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case Event(ProcessCurrentBlockHeight(_), _) => stay() case Event(ProcessCurrentBlockHeight(_), _) => stay()
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream // we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
case Event(CurrentFeerates(_), _) => stay() case Event(_: CurrentFeerates.BitcoinCore, _) => stay()
// we only care about this event in NORMAL state // we only care about this event in NORMAL state
case Event(_: BroadcastChannelUpdate, _) => stay() case Event(_: BroadcastChannelUpdate, _) => stay()
@ -2558,7 +2558,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = { 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? // 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 commitments = d.commitments.latest
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.commitmentFormat, commitments.capacity) val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.params.commitmentFormat, commitments.capacity)
val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate
val shouldUpdateFee = d.commitments.params.localParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) val shouldUpdateFee = d.commitments.params.localParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw)
val shouldClose = !d.commitments.params.localParams.paysCommitTxFees && val shouldClose = !d.commitments.params.localParams.paysCommitTxFees &&
@ -2584,7 +2584,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def handleCurrentFeerateDisconnected(c: CurrentFeerates, d: ChannelDataWithCommitments) = { 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? // 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 commitments = d.commitments.latest
val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, d.commitments.params.commitmentFormat, commitments.capacity) val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, d.commitments.params.commitmentFormat, commitments.capacity)
val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate val currentFeeratePerKw = commitments.localCommit.spec.commitTxFeerate
// if the network fees are too high we risk to not be able to confirm our current commitment // if the network fees are too high we risk to not be able to confirm our current commitment
val shouldClose = networkFeeratePerKw > currentFeeratePerKw && val shouldClose = networkFeeratePerKw > currentFeeratePerKw &&
@ -2769,7 +2769,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def initiateSplice(cmd: CMD_SPLICE, d: DATA_NORMAL): Either[ChannelException, SpliceInit] = { private def initiateSplice(cmd: CMD_SPLICE, d: DATA_NORMAL): Either[ChannelException, SpliceInit] = {
if (d.commitments.isQuiescent) { if (d.commitments.isQuiescent) {
val parentCommitment = d.commitments.latest.commitment val parentCommitment = d.commitments.latest.commitment
val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates) val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates)
val fundingContribution = InteractiveTxFunder.computeSpliceContribution( val fundingContribution = InteractiveTxFunder.computeSpliceContribution(
isInitiator = true, isInitiator = true,
sharedInput = Multisig2of2Input(parentCommitment), sharedInput = Multisig2of2Input(parentCommitment),

View File

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

View File

@ -75,7 +75,7 @@ object ReplaceableTxFunder {
Behaviors.withMdc(txPublishContext.mdc()) { Behaviors.withMdc(txPublishContext.mdc()) {
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case FundTransaction(replyTo, cmd, tx, requestedFeerate) => case FundTransaction(replyTo, cmd, tx, requestedFeerate) =>
val targetFeerate = requestedFeerate.min(maxFeerate(cmd.txInfo, cmd.commitment, nodeParams.currentFeerates, nodeParams.onChainFeeConf)) val targetFeerate = requestedFeerate.min(maxFeerate(cmd.txInfo, cmd.commitment, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf))
val txFunder = new ReplaceableTxFunder(nodeParams, replyTo, cmd, bitcoinClient, context) val txFunder = new ReplaceableTxFunder(nodeParams, replyTo, cmd, bitcoinClient, context)
tx match { tx match {
case Right(txWithWitnessData) => txFunder.fund(txWithWitnessData, targetFeerate) case Right(txWithWitnessData) => txFunder.fund(txWithWitnessData, targetFeerate)

View File

@ -158,7 +158,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
} }
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case CheckUtxosResult(isSafe, currentBlockHeight) => case CheckUtxosResult(isSafe, currentBlockHeight) =>
val targetFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe) val targetFeerate = getFeerate(nodeParams.currentBitcoinCoreFeerates, confirmationTarget, currentBlockHeight, isSafe)
fund(txWithWitnessData, targetFeerate) fund(txWithWitnessData, targetFeerate)
case UpdateConfirmationTarget(target) => case UpdateConfirmationTarget(target) =>
confirmationTarget = target confirmationTarget = target
@ -220,7 +220,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case CheckUtxosResult(isSafe, currentBlockHeight) => case CheckUtxosResult(isSafe, currentBlockHeight) =>
// We make sure we increase the fees by at least 20% as we get closer to the confirmation target. // We make sure we increase the fees by at least 20% as we get closer to the confirmation target.
val bumpRatio = 1.2 val bumpRatio = 1.2
val currentFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe) val currentFeerate = getFeerate(nodeParams.currentBitcoinCoreFeerates, confirmationTarget, currentBlockHeight, isSafe)
val targetFeerate_opt = confirmationTarget match { val targetFeerate_opt = confirmationTarget match {
case ConfirmationTarget.Absolute(confirmBefore) => case ConfirmationTarget.Absolute(confirmBefore) =>
if (confirmBefore <= currentBlockHeight + 6) { if (confirmBefore <= currentBlockHeight + 6) {

View File

@ -29,7 +29,7 @@ import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator
import fr.acinq.eclair._ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher
import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.blockchain.{OnChainChannelFunder, OnchainPubkeyCache} import fr.acinq.eclair.blockchain.{CurrentFeerates, OnChainChannelFunder, OnchainPubkeyCache}
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.io.MessageRelay.Status import fr.acinq.eclair.io.MessageRelay.Status
@ -63,6 +63,8 @@ class Peer(val nodeParams: NodeParams,
import Peer._ import Peer._
context.system.eventStream.subscribe(self, classOf[CurrentFeerates])
startWith(INSTANTIATING, Nothing) startWith(INSTANTIATING, Nothing)
when(INSTANTIATING) { when(INSTANTIATING) {
@ -170,8 +172,8 @@ class Peer(val nodeParams: NodeParams,
} else { } else {
randomBytes32() randomBytes32()
} }
val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates)) val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates))
val commitTxFeerate = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelType.commitmentFormat, c.fundingAmount) val commitTxFeerate = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelType.commitmentFormat, 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") 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.fundingTxFeeBudget_opt, c.pushAmount_opt, requireConfirmedInputs, c.requestFunding_opt, localParams, d.peerConnection, d.remoteInit, c.channelFlags_opt.getOrElse(nodeParams.channelConf.channelFlags), channelConfig, channelType, c.channelOrigin, replyTo) channel ! INPUT_INIT_CHANNEL_INITIATOR(temporaryChannelId, c.fundingAmount, dualFunded, commitTxFeerate, fundingTxFeerate, c.fundingTxFeeBudget_opt, c.pushAmount_opt, requireConfirmedInputs, c.requestFunding_opt, 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)) stay() using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel))
@ -345,6 +347,13 @@ class Peer(val nodeParams: NodeParams,
} }
stay() stay()
case Event(_: CurrentFeerates, d) =>
d match {
case d: ConnectedData => d.peerConnection ! nodeParams.recommendedFeerates(remoteNodeId, d.localFeatures, d.remoteFeatures)
case _ => ()
}
stay()
case Event(_: Peer.OutgoingMessage, _) => stay() // we got disconnected or reconnected and this message was for the previous connection case Event(_: Peer.OutgoingMessage, _) => stay() // we got disconnected or reconnected and this message was for the previous connection
case Event(RelayOnionMessage(messageId, _, replyTo_opt), _) => case Event(RelayOnionMessage(messageId, _, replyTo_opt), _) =>
@ -389,6 +398,9 @@ class Peer(val nodeParams: NodeParams,
// let's bring existing/requested channels online // let's bring existing/requested channels online
channels.values.toSet[ActorRef].foreach(_ ! INPUT_RECONNECTED(connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit)) // we deduplicate with toSet because there might be two entries per channel (tmp id and final id) channels.values.toSet[ActorRef].foreach(_ ! INPUT_RECONNECTED(connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit)) // we deduplicate with toSet because there might be two entries per channel (tmp id and final id)
// We tell our peer what our current feerates are.
connectionReady.peerConnection ! nodeParams.recommendedFeerates(remoteNodeId, connectionReady.localInit.features, connectionReady.remoteInit.features)
goto(CONNECTED) using ConnectedData(connectionReady.address, connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit, channels) goto(CONNECTED) using ConnectedData(connectionReady.address, connectionReady.peerConnection, connectionReady.localInit, connectionReady.remoteInit, channels)
} }

View File

@ -429,6 +429,12 @@ object LightningMessageCodecs {
// //
val recommendedFeeratesCodec: Codec[RecommendedFeerates] = (
("chainHash" | blockHash) ::
("fundingFeerate" | feeratePerKw) ::
("commitmentFeerate" | feeratePerKw) ::
("tlvStream" | RecommendedFeeratesTlv.recommendedFeeratesTlvCodec)).as[RecommendedFeerates]
val unknownMessageCodec: Codec[UnknownMessage] = ( val unknownMessageCodec: Codec[UnknownMessage] = (
("tag" | uint16) :: ("tag" | uint16) ::
("message" | bytes) ("message" | bytes)
@ -479,14 +485,15 @@ object LightningMessageCodecs {
.typecase(513, onionMessageCodec) .typecase(513, onionMessageCodec)
// NB: blank lines to minimize merge conflicts // NB: blank lines to minimize merge conflicts
//
// //
.typecase(37000, spliceInitCodec) .typecase(37000, spliceInitCodec)
.typecase(37002, spliceAckCodec) .typecase(37002, spliceAckCodec)
.typecase(37004, spliceLockedCodec) .typecase(37004, spliceLockedCodec)
// //
//
//
.typecase(39409, recommendedFeeratesCodec)
// //
// //

View File

@ -607,4 +607,15 @@ case class OnionMessage(blindingKey: PublicKey, onionRoutingPacket: OnionRouting
// //
/**
* This message informs our peers of the feerates we recommend using.
* We may reject funding attempts that use values that are too far from our recommended feerates.
*/
case class RecommendedFeerates(chainHash: BlockHash, fundingFeerate: FeeratePerKw, commitmentFeerate: FeeratePerKw, tlvStream: TlvStream[RecommendedFeeratesTlv] = TlvStream.empty) extends SetupMessage with HasChainHash {
val minFundingFeerate: FeeratePerKw = tlvStream.get[RecommendedFeeratesTlv.FundingFeerateRange].map(_.min).getOrElse(fundingFeerate)
val maxFundingFeerate: FeeratePerKw = tlvStream.get[RecommendedFeeratesTlv.FundingFeerateRange].map(_.max).getOrElse(fundingFeerate)
val minCommitmentFeerate: FeeratePerKw = tlvStream.get[RecommendedFeeratesTlv.CommitmentFeerateRange].map(_.min).getOrElse(commitmentFeerate)
val maxCommitmentFeerate: FeeratePerKw = tlvStream.get[RecommendedFeeratesTlv.CommitmentFeerateRange].map(_.max).getOrElse(commitmentFeerate)
}
case class UnknownMessage(tag: Int, data: ByteVector) extends LightningMessage case class UnknownMessage(tag: Int, data: ByteVector) extends LightningMessage

View File

@ -18,6 +18,7 @@ package fr.acinq.eclair.wire.protocol
import fr.acinq.bitcoin.scalacompat.BlockHash import fr.acinq.bitcoin.scalacompat.BlockHash
import fr.acinq.eclair.UInt64 import fr.acinq.eclair.UInt64
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tlvStream} import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tlvStream}
import scodec.Codec import scodec.Codec
@ -85,4 +86,23 @@ sealed trait PongTlv extends Tlv
object PongTlv { object PongTlv {
val pongTlvCodec: Codec[TlvStream[PongTlv]] = tlvStream(discriminated[PongTlv].by(varint)) val pongTlvCodec: Codec[TlvStream[PongTlv]] = tlvStream(discriminated[PongTlv].by(varint))
}
sealed trait RecommendedFeeratesTlv extends Tlv
object RecommendedFeeratesTlv {
/** Detailed range of values that will be accepted until the next [[RecommendedFeerates]] message is sent. */
case class FundingFeerateRange(min: FeeratePerKw, max: FeeratePerKw) extends RecommendedFeeratesTlv
private val fundingFeerateRangeCodec: Codec[FundingFeerateRange] = tlvField(feeratePerKw :: feeratePerKw)
/** Detailed range of values that will be accepted until the next [[RecommendedFeerates]] message is sent. */
case class CommitmentFeerateRange(min: FeeratePerKw, max: FeeratePerKw) extends RecommendedFeeratesTlv
private val commitmentFeerateRangeCodec: Codec[CommitmentFeerateRange] = tlvField(feeratePerKw :: feeratePerKw)
val recommendedFeeratesTlvCodec = tlvStream(discriminated[RecommendedFeeratesTlv].by(varint)
.typecase(UInt64(1), fundingFeerateRangeCodec)
.typecase(UInt64(3), commitmentFeerateRangeCodec)
)
} }

View File

@ -90,7 +90,7 @@ object TestConstants {
channelKeyManager, channelKeyManager,
onChainKeyManager_opt = None, onChainKeyManager_opt = None,
blockHeight = new AtomicLong(defaultBlockHeight), blockHeight = new AtomicLong(defaultBlockHeight),
feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), bitcoinCoreFeerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)),
alias = "alice", alias = "alice",
color = Color(1, 2, 3), color = Color(1, 2, 3),
publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil, publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil,
@ -264,7 +264,7 @@ object TestConstants {
channelKeyManager, channelKeyManager,
onChainKeyManager_opt = None, onChainKeyManager_opt = None,
blockHeight = new AtomicLong(defaultBlockHeight), blockHeight = new AtomicLong(defaultBlockHeight),
feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), bitcoinCoreFeerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)),
alias = "bob", alias = "bob",
color = Color(4, 5, 6), color = Color(4, 5, 6),
publicAddresses = NodeAddress.fromParts("localhost", 9732).get :: Nil, publicAddresses = NodeAddress.fromParts("localhost", 9732).get :: Nil,

View File

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

View File

@ -82,11 +82,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bc0.availableBalanceForReceive == a) assert(bc0.availableBalanceForReceive == a)
val (payment_preimage, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) 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.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, 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.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) assert(ac1.availableBalanceForReceive == b)
val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc1.availableBalanceForSend == b) assert(bc1.availableBalanceForSend == b)
assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee) assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee)
@ -167,11 +167,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bc0.availableBalanceForReceive == a) assert(bc0.availableBalanceForReceive == a)
val (_, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) val (_, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight)
val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, 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.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) assert(ac1.availableBalanceForReceive == b)
val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) val Right(bc1) = bc0.receiveAdd(add, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc1.availableBalanceForSend == b) assert(bc1.availableBalanceForSend == b)
assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee) assert(bc1.availableBalanceForReceive == a - p - htlcOutputFee)
@ -255,29 +255,29 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bc0.availableBalanceForReceive == a) assert(bc0.availableBalanceForReceive == a)
val (payment_preimage1, cmdAdd1) = makeCmdAdd(p1, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) 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.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val Right((ac1, add1)) = ac0.sendAdd(cmdAdd1, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, 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.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) assert(ac1.availableBalanceForReceive == b)
val (_, cmdAdd2) = makeCmdAdd(p2, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) val (_, cmdAdd2) = makeCmdAdd(p2, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight)
val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, 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.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) assert(ac2.availableBalanceForReceive == b)
val (payment_preimage3, cmdAdd3) = makeCmdAdd(p3, alice.underlyingActor.nodeParams.nodeId, currentBlockHeight) 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.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) val Right((bc1, add3)) = bc0.sendAdd(cmdAdd3, currentBlockHeight, bob.underlyingActor.nodeParams.channelConf, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc1.availableBalanceForSend == b - p3) // bob doesn't pay the fee assert(bc1.availableBalanceForSend == b - p3) // bob doesn't pay the fee
assert(bc1.availableBalanceForReceive == a) assert(bc1.availableBalanceForReceive == a)
val Right(bc2) = bc1.receiveAdd(add1, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) val Right(bc2) = bc1.receiveAdd(add1, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc2.availableBalanceForSend == b - p3) assert(bc2.availableBalanceForSend == b - p3)
assert(bc2.availableBalanceForReceive == a - p1 - htlcOutputFee) assert(bc2.availableBalanceForReceive == a - p1 - htlcOutputFee)
val Right(bc3) = bc2.receiveAdd(add2, bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.nodeParams.onChainFeeConf) val Right(bc3) = bc2.receiveAdd(add2, bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.nodeParams.onChainFeeConf)
assert(bc3.availableBalanceForSend == b - p3) assert(bc3.availableBalanceForSend == b - p3)
assert(bc3.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee) assert(bc3.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee)
val Right(ac3) = ac2.receiveAdd(add3, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val Right(ac3) = ac2.receiveAdd(add3, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf)
assert(ac3.availableBalanceForSend == a - p1 - htlcOutputFee - p2 - htlcOutputFee) assert(ac3.availableBalanceForSend == a - p1 - htlcOutputFee - p2 - htlcOutputFee)
assert(ac3.availableBalanceForReceive == b - p3) assert(ac3.availableBalanceForReceive == b - p3)

View File

@ -84,8 +84,8 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
/** Set uniform feerate for all block targets. */ /** Set uniform feerate for all block targets. */
def setFeerate(feerate: FeeratePerKw, fastest: FeeratePerKw = FeeratePerKw(100_000 sat)): Unit = { def setFeerate(feerate: FeeratePerKw, fastest: FeeratePerKw = FeeratePerKw(100_000 sat)): Unit = {
alice.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest))
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest))
} }
/** Set feerate for a specific block target. */ /** Set feerate for a specific block target. */
@ -97,8 +97,8 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
case _ => currentFeerates.copy(slow = feerate) case _ => currentFeerates.copy(slow = feerate)
} }
alice.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates)) alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates))
bob.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates)) bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates))
} }
def getMempool(): Seq[Transaction] = { def getMempool(): Seq[Transaction] = {
@ -496,7 +496,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
probe.expectMsg(commitTx.tx.txid) probe.expectMsg(commitTx.tx.txid)
assert(getMempool().length == 1) assert(getMempool().length == 1)
val maxFeerate = ReplaceableTxFunder.maxFeerate(anchorTx.txInfo, anchorTx.commitment, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val maxFeerate = ReplaceableTxFunder.maxFeerate(anchorTx.txInfo, anchorTx.commitment, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf)
val targetFeerate = FeeratePerKw(50_000 sat) val targetFeerate = FeeratePerKw(50_000 sat)
assert(maxFeerate <= targetFeerate / 2) assert(maxFeerate <= targetFeerate / 2)
setFeerate(targetFeerate, blockTarget = 12) setFeerate(targetFeerate, blockTarget = 12)
@ -1168,12 +1168,12 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val (commitTx, htlcSuccess, htlcTimeout) = closeChannelWithHtlcs(f, aliceBlockHeight() + 30, outgoingHtlcAmount = 5_000_000 msat, incomingHtlcAmount = 4_000_000 msat) val (commitTx, htlcSuccess, htlcTimeout) = closeChannelWithHtlcs(f, aliceBlockHeight() + 30, outgoingHtlcAmount = 5_000_000 msat, incomingHtlcAmount = 4_000_000 msat)
setFeerate(targetFeerate, blockTarget = 12) setFeerate(targetFeerate, blockTarget = 12)
assert(htlcSuccess.txInfo.fee == 0.sat) assert(htlcSuccess.txInfo.fee == 0.sat)
val htlcSuccessMaxFeerate = ReplaceableTxFunder.maxFeerate(htlcSuccess.txInfo, htlcSuccess.commitment, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val htlcSuccessMaxFeerate = ReplaceableTxFunder.maxFeerate(htlcSuccess.txInfo, htlcSuccess.commitment, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf)
assert(htlcSuccessMaxFeerate < targetFeerate / 2) assert(htlcSuccessMaxFeerate < targetFeerate / 2)
val htlcSuccessTx = testPublishHtlcSuccess(f, commitTx, htlcSuccess, htlcSuccessMaxFeerate) val htlcSuccessTx = testPublishHtlcSuccess(f, commitTx, htlcSuccess, htlcSuccessMaxFeerate)
assert(htlcSuccessTx.txIn.length > 1) assert(htlcSuccessTx.txIn.length > 1)
assert(htlcTimeout.txInfo.fee == 0.sat) assert(htlcTimeout.txInfo.fee == 0.sat)
val htlcTimeoutMaxFeerate = ReplaceableTxFunder.maxFeerate(htlcTimeout.txInfo, htlcTimeout.commitment, alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.nodeParams.onChainFeeConf) val htlcTimeoutMaxFeerate = ReplaceableTxFunder.maxFeerate(htlcTimeout.txInfo, htlcTimeout.commitment, alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.nodeParams.onChainFeeConf)
assert(htlcTimeoutMaxFeerate < targetFeerate / 2) assert(htlcTimeoutMaxFeerate < targetFeerate / 2)
val htlcTimeoutTx = testPublishHtlcTimeout(f, commitTx, htlcTimeout, htlcTimeoutMaxFeerate) val htlcTimeoutTx = testPublishHtlcTimeout(f, commitTx, htlcTimeout, htlcTimeoutMaxFeerate)
assert(htlcTimeoutTx.txIn.length > 1) assert(htlcTimeoutTx.txIn.length > 1)
@ -1567,7 +1567,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f => withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { f =>
import f._ import f._
val currentFeerate = alice.underlyingActor.nodeParams.currentFeerates.fast val currentFeerate = alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates.fast
val (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) = remoteCloseChannelWithHtlcs(f, aliceBlockHeight() + 50, nextCommit = false) val (remoteCommitTx, claimHtlcSuccess, claimHtlcTimeout) = remoteCloseChannelWithHtlcs(f, aliceBlockHeight() + 50, nextCommit = false)
val claimHtlcSuccessTx = testPublishClaimHtlcSuccess(f, remoteCommitTx, claimHtlcSuccess, currentFeerate) val claimHtlcSuccessTx = testPublishClaimHtlcSuccess(f, remoteCommitTx, claimHtlcSuccess, currentFeerate)
assert(claimHtlcSuccess.txInfo.fee > 0.sat) assert(claimHtlcSuccess.txInfo.fee > 0.sat)

View File

@ -655,7 +655,7 @@ object ChannelStateTestsBase {
val nodeParams: NodeParams = channel.underlyingActor.nodeParams val nodeParams: NodeParams = channel.underlyingActor.nodeParams
def setFeerates(feerates: FeeratesPerKw): Unit = channel.underlyingActor.nodeParams.setFeerates(feerates) def setFeerates(feerates: FeeratesPerKw): Unit = channel.underlyingActor.nodeParams.setBitcoinCoreFeerates(feerates)
def setFeerate(feerate: FeeratePerKw): Unit = setFeerates(FeeratesPerKw.single(feerate)) def setFeerate(feerate: FeeratePerKw): Unit = setFeerates(FeeratesPerKw.single(feerate))
} }

View File

@ -211,7 +211,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
// When we only splice-out, the fees are paid by deducing them from the next funding amount. // When we only splice-out, the fees are paid by deducing them from the next funding amount.
val fundingTx = alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get val fundingTx = alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.localFundingStatus.signedTx_opt.get
val feerate = alice.nodeParams.onChainFeeConf.getFundingFeerate(alice.nodeParams.currentFeerates) val feerate = alice.nodeParams.onChainFeeConf.getFundingFeerate(alice.nodeParams.currentBitcoinCoreFeerates)
val expectedMiningFee = Transactions.weight2fee(feerate, fundingTx.weight()) val expectedMiningFee = Transactions.weight2fee(feerate, fundingTx.weight())
val actualMiningFee = capacity - alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.capacity val actualMiningFee = capacity - alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.capacity
// Fee computation is approximate (signature size isn't constant). // Fee computation is approximate (signature size isn't constant).
@ -518,7 +518,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice ! cmd alice ! cmd
// we tweak the feerate // we tweak the feerate
val spliceInit = alice2bob.expectMsgType[SpliceInit].copy(feerate = FeeratePerKw(100.sat)) val spliceInit = alice2bob.expectMsgType[SpliceInit].copy(feerate = FeeratePerKw(100.sat))
bob.setFeerates(alice.nodeParams.currentFeerates.copy(minimum = FeeratePerKw(101.sat))) bob.setFeerates(alice.nodeParams.currentBitcoinCoreFeerates.copy(minimum = FeeratePerKw(101.sat)))
alice2bob.forward(bob, spliceInit) alice2bob.forward(bob, spliceInit)
val txAbortBob = bob2alice.expectMsgType[TxAbort] val txAbortBob = bob2alice.expectMsgType[TxAbort]
bob2alice.forward(alice, txAbortBob) bob2alice.forward(alice, txAbortBob)

View File

@ -280,7 +280,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
crossSign(alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice)
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat)
// We increase the feerate to get Alice's balance closer to her channel reserve. // We increase the feerate to get Alice's balance closer to her channel reserve.
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(17_500 sat))) bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(17_500 sat)))
updateFee(FeeratePerKw(17_500 sat), alice, bob, alice2bob, bob2alice) updateFee(FeeratePerKw(17_500 sat), alice, bob, alice2bob, bob2alice)
// At this point alice has the minimal amount to sustain a channel. // At this point alice has the minimal amount to sustain a channel.
@ -557,7 +557,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val sender = TestProbe() val sender = TestProbe()
bob.setFeerate(FeeratePerKw(20000 sat)) bob.setFeerate(FeeratePerKw(20000 sat))
bob ! CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(20000 sat))) bob ! CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(20000 sat)))
bob2alice.expectNoMessage(100 millis) // we don't close because the commitment doesn't contain any HTLC bob2alice.expectNoMessage(100 millis) // we don't close because the commitment doesn't contain any HTLC
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
@ -570,7 +570,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// we now agree on feerate so we can send HTLCs // we now agree on feerate so we can send HTLCs
bob.setFeerate(FeeratePerKw(11000 sat)) bob.setFeerate(FeeratePerKw(11000 sat))
bob ! CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) bob ! CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob2alice.expectNoMessage(100 millis) bob2alice.expectNoMessage(100 millis)
bob ! add bob ! add
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
@ -2313,7 +2313,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._ import f._
val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments
val tx = bobCommitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx val tx = bobCommitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
val expectedFeeratePerKw = bob.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(bob.underlyingActor.nodeParams.currentFeerates, bob.underlyingActor.remoteNodeId, bobCommitments.params.commitmentFormat, bobCommitments.latest.capacity) val expectedFeeratePerKw = bob.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(bob.underlyingActor.nodeParams.currentBitcoinCoreFeerates, bob.underlyingActor.remoteNodeId, bobCommitments.params.commitmentFormat, bobCommitments.latest.capacity)
assert(bobCommitments.latest.localCommit.spec.commitTxFeerate == expectedFeeratePerKw) assert(bobCommitments.latest.localCommit.spec.commitTxFeerate == expectedFeeratePerKw)
bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat)) bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat))
val error = bob2alice.expectMsgType[Error] val error = bob2alice.expectMsgType[Error]
@ -2973,23 +2973,23 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f =>
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
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))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.commitmentFormat, initialState.commitments.latest.capacity))) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.commitmentFormat, initialState.commitments.latest.capacity)))
} }
test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
val event1 = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat))) val event1 = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event1.feeratesPerKw) alice.setFeerates(event1.feeratesPerKw)
alice ! event1 alice ! event1
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2)) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2))
alice2bob.expectMsgType[CommitSig] alice2bob.expectMsgType[CommitSig]
// The configured maximum feerate is bypassed if it's below the propagation threshold. // The configured maximum feerate is bypassed if it's below the propagation threshold.
val event2 = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = TestConstants.anchorOutputsFeeratePerKw)) val event2 = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = TestConstants.anchorOutputsFeeratePerKw))
alice.setFeerates(event2.feeratesPerKw) alice.setFeerates(event2.feeratesPerKw)
alice ! event2 alice ! event2
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw * 1.25)) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw * 1.25))
@ -2997,7 +2997,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectNoMessage(500 millis) alice2bob.expectNoMessage(500 millis)
@ -3007,7 +3007,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectNoMessage(500 millis) alice2bob.expectNoMessage(500 millis)
@ -3015,7 +3015,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f => test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectNoMessage(500 millis) bob2alice.expectNoMessage(500 millis)
@ -3027,7 +3027,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
addHtlc(10000000 msat, alice, bob, alice2bob, bob2alice) addHtlc(10000000 msat, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice)
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(14000 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(14000 sat)))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectMsgType[Error] bob2alice.expectMsgType[Error]
@ -3051,7 +3051,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw / 2) 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. // The network fees spike, but Bob doesn't close the channel because we're using anchor outputs.
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 10)) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 10))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectNoMessage(250 millis) bob2alice.expectNoMessage(250 millis)
@ -3061,7 +3061,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, without HTLCs)") { f => test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different, without HTLCs)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(15_000 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(15_000 sat)))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC bob2alice.expectNoMessage(250 millis) // we don't close because the commitment doesn't contain any HTLC
@ -3138,7 +3138,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(getClaimHtlcTimeoutTxs(rcp).length == 2) assert(getClaimHtlcTimeoutTxs(rcp).length == 2)
// assert the feerate of the claim main is what we expect // assert the feerate of the claim main is what we expect
val expectedFeeRate = alice.underlyingActor.nodeParams.onChainFeeConf.getClosingFeerate(alice.underlyingActor.nodeParams.currentFeerates) val expectedFeeRate = alice.underlyingActor.nodeParams.onChainFeeConf.getClosingFeerate(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates)
val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount).sum - claimMain.txOut.map(_.amount).sum val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount).sum - claimMain.txOut.map(_.amount).sum
val claimFeeRate = Transactions.fee2rate(claimFee, claimMain.weight()) val claimFeeRate = Transactions.fee2rate(claimFee, claimMain.weight())
assert(claimFeeRate >= expectedFeeRate * 0.9 && claimFeeRate <= expectedFeeRate * 1.2) assert(claimFeeRate >= expectedFeeRate * 0.9 && claimFeeRate <= expectedFeeRate * 1.2)

View File

@ -664,7 +664,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// alice is funder // alice is funder
alice.setFeerates(networkFeerates) alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates) alice ! CurrentFeerates.BitcoinCore(networkFeerates)
if (shouldClose) { if (shouldClose) {
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid)
} else { } else {
@ -691,7 +691,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// this time Alice will ignore feerate changes for the offline channel // this time Alice will ignore feerate changes for the offline channel
alice.setFeerates(networkFeerates) alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates) alice ! CurrentFeerates.BitcoinCore(networkFeerates)
alice2blockchain.expectNoMessage() alice2blockchain.expectNoMessage()
alice2bob.expectNoMessage() alice2bob.expectNoMessage()
} }
@ -708,7 +708,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Alice ignores feerate changes while offline // Alice ignores feerate changes while offline
alice.setFeerates(networkFeerates) alice.setFeerates(networkFeerates)
alice ! CurrentFeerates(networkFeerates) alice ! CurrentFeerates.BitcoinCore(networkFeerates)
alice2blockchain.expectNoMessage() alice2blockchain.expectNoMessage()
alice2bob.expectNoMessage() alice2bob.expectNoMessage()
@ -775,7 +775,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob is fundee // bob is fundee
bob.setFeerates(networkFeerates) bob.setFeerates(networkFeerates)
bob ! CurrentFeerates(networkFeerates) bob ! CurrentFeerates.BitcoinCore(networkFeerates)
if (shouldClose) { if (shouldClose) {
assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == bobCommitTx.txid) assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == bobCommitTx.txid)
} else { } else {

View File

@ -662,17 +662,17 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f =>
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
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))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw(minimum = FeeratePerKw(250 sat), fastest = FeeratePerKw(10_000 sat), fast = FeeratePerKw(5_000 sat), medium = FeeratePerKw(1000 sat), slow = FeeratePerKw(500 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.commitmentFormat, initialState.commitments.latest.capacity))) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, alice.underlyingActor.nodeParams.onChainFeeConf.getCommitmentFeerate(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates, alice.underlyingActor.remoteNodeId, initialState.commitments.params.commitmentFormat, initialState.commitments.latest.capacity)))
} }
test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => test("recv CurrentFeerate (when funder, triggers an UpdateFee, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw / 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2)) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2))
@ -680,7 +680,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectNoMessage(500 millis) alice2bob.expectNoMessage(500 millis)
@ -690,7 +690,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
import f._ import f._
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
val event = CurrentFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
alice.setFeerates(event.feeratesPerKw) alice.setFeerates(event.feeratesPerKw)
alice ! event alice ! event
alice2bob.expectNoMessage(500 millis) alice2bob.expectNoMessage(500 millis)
@ -698,7 +698,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f => test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat)))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectNoMessage(500 millis) bob2alice.expectNoMessage(500 millis)
@ -706,7 +706,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f => test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f =>
import f._ import f._
val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(25000 sat))) val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(25000 sat)))
bob.setFeerates(event.feeratesPerKw) bob.setFeerates(event.feeratesPerKw)
bob ! event bob ! event
bob2alice.expectMsgType[Error] bob2alice.expectMsgType[Error]

View File

@ -291,7 +291,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._ import f._
val sender = TestProbe() val sender = TestProbe()
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures)
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(minimum = FeeratePerKw(250 sat), slow = FeeratePerKw(250 sat))) bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(2500 sat)).copy(minimum = FeeratePerKw(250 sat), slow = FeeratePerKw(250 sat)))
// alice initiates a closing with a low fee // 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)))) alice ! CMD_CLOSE(sender.ref, None, Some(ClosingFeerates(FeeratePerKw(500 sat), FeeratePerKw(250 sat), FeeratePerKw(1000 sat))))
alice2bob.expectMsgType[Shutdown] alice2bob.expectMsgType[Shutdown]
@ -651,7 +651,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// simulate a node restart after a feerate increase // simulate a node restart after a feerate increase
val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING]
alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing) alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing)
alice.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(15_000 sat))) alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(15_000 sat)))
alice ! INPUT_RESTORED(beforeRestart) alice ! INPUT_RESTORED(beforeRestart)
alice2blockchain.expectMsgType[SetChannelId] alice2blockchain.expectMsgType[SetChannelId]
awaitCond(alice.stateName == CLOSING) awaitCond(alice.stateName == CLOSING)
@ -739,7 +739,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// We simulate a node restart after a feerate increase. // We simulate a node restart after a feerate increase.
val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING]
alice.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(15_000 sat))) alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(FeeratePerKw(15_000 sat)))
alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing) alice.setState(WAIT_FOR_INIT_INTERNAL, Nothing)
alice ! INPUT_RESTORED(beforeRestart) alice ! INPUT_RESTORED(beforeRestart)
alice2blockchain.expectMsgType[SetChannelId] alice2blockchain.expectMsgType[SetChannelId]

View File

@ -74,7 +74,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat
torAddress_opt = None, torAddress_opt = None,
database = TestDatabases.inMemoryDb(), database = TestDatabases.inMemoryDb(),
blockHeight = new AtomicLong(400_000), blockHeight = new AtomicLong(400_000),
feerates = new AtomicReference(FeeratesPerKw.single(FeeratePerKw(253 sat))) bitcoinCoreFeerates = new AtomicReference(FeeratesPerKw.single(FeeratePerKw(253 sat)))
).modify(_.alias).setTo(alias) ).modify(_.alias).setTo(alias)
.modify(_.chainHash).setTo(Block.RegtestGenesisBlock.hash) .modify(_.chainHash).setTo(Block.RegtestGenesisBlock.hash)
.modify(_.routerConf.routerBroadcastInterval).setTo(1 second) .modify(_.routerConf.routerBroadcastInterval).setTo(1 second)

View File

@ -25,8 +25,8 @@ import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features._ import fr.acinq.eclair.Features._
import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair._ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw} import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.blockchain.{CurrentFeerates, DummyOnChainWallet}
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.ChannelStateTestsTags import fr.acinq.eclair.channel.states.ChannelStateTestsTags
import fr.acinq.eclair.io.Peer._ import fr.acinq.eclair.io.Peer._
@ -112,6 +112,7 @@ class PeerSpec extends FixtureSpec {
switchboard.send(peer, Peer.Init(channels)) switchboard.send(peer, Peer.Init(channels))
val localInit = protocol.Init(peer.underlyingActor.nodeParams.features.initFeatures()) val localInit = protocol.Init(peer.underlyingActor.nodeParams.features.initFeatures())
switchboard.send(peer, PeerConnection.ConnectionReady(peerConnection.ref, remoteNodeId, fakeIPAddress, outgoing = true, localInit, remoteInit)) switchboard.send(peer, PeerConnection.ConnectionReady(peerConnection.ref, remoteNodeId, fakeIPAddress, outgoing = true, localInit, remoteInit))
peerConnection.expectMsgType[RecommendedFeerates]
val probe = TestProbe() val probe = TestProbe()
probe.send(peer, Peer.GetPeerInfo(Some(probe.ref.toTyped))) probe.send(peer, Peer.GetPeerInfo(Some(probe.ref.toTyped)))
val peerInfo = probe.expectMsgType[Peer.PeerInfo] val peerInfo = probe.expectMsgType[Peer.PeerInfo]
@ -282,6 +283,7 @@ class PeerSpec extends FixtureSpec {
} }
peerConnection2.send(peer, PeerConnection.ConnectionReady(peerConnection2.ref, remoteNodeId, fakeIPAddress, outgoing = false, localInit, remoteInit)) peerConnection2.send(peer, PeerConnection.ConnectionReady(peerConnection2.ref, remoteNodeId, fakeIPAddress, outgoing = false, localInit, remoteInit))
peerConnection2.expectMsgType[RecommendedFeerates]
// peer should kill previous connection // peer should kill previous connection
peerConnection1.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced)) peerConnection1.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced))
channel.expectMsg(INPUT_DISCONNECTED) channel.expectMsg(INPUT_DISCONNECTED)
@ -291,6 +293,7 @@ class PeerSpec extends FixtureSpec {
} }
peerConnection3.send(peer, PeerConnection.ConnectionReady(peerConnection3.ref, remoteNodeId, fakeIPAddress, outgoing = false, localInit, remoteInit)) peerConnection3.send(peer, PeerConnection.ConnectionReady(peerConnection3.ref, remoteNodeId, fakeIPAddress, outgoing = false, localInit, remoteInit))
peerConnection3.expectMsgType[RecommendedFeerates]
// peer should kill previous connection // peer should kill previous connection
peerConnection2.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced)) peerConnection2.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced))
channel.expectMsg(INPUT_DISCONNECTED) channel.expectMsg(INPUT_DISCONNECTED)
@ -325,6 +328,26 @@ class PeerSpec extends FixtureSpec {
monitor.expectMsg(FSM.Transition(reconnectionTask, ReconnectionTask.CONNECTING, ReconnectionTask.IDLE)) monitor.expectMsg(FSM.Transition(reconnectionTask, ReconnectionTask.CONNECTING, ReconnectionTask.IDLE))
} }
test("send recommended feerates when feerate changes") { f =>
import f._
connect(remoteNodeId, peer, peerConnection, switchboard, channels = Set(ChannelCodecsSpec.normal))
// We regularly update our internal feerates.
val bitcoinCoreFeerates = FeeratesPerKw(FeeratePerKw(253 sat), FeeratePerKw(1000 sat), FeeratePerKw(2500 sat), FeeratePerKw(5000 sat), FeeratePerKw(10_000 sat))
nodeParams.setBitcoinCoreFeerates(bitcoinCoreFeerates)
peer ! CurrentFeerates.BitcoinCore(bitcoinCoreFeerates)
peerConnection.expectMsg(RecommendedFeerates(
chainHash = Block.RegtestGenesisBlock.hash,
fundingFeerate = FeeratePerKw(2_500 sat),
commitmentFeerate = FeeratePerKw(5000 sat),
tlvStream = TlvStream[RecommendedFeeratesTlv](
RecommendedFeeratesTlv.FundingFeerateRange(FeeratePerKw(1250 sat), FeeratePerKw(20_000 sat)),
RecommendedFeeratesTlv.CommitmentFeerateRange(FeeratePerKw(2500 sat), FeeratePerKw(40_000 sat))
)
))
}
test("don't spawn a channel with duplicate temporary channel id", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => test("don't spawn a channel with duplicate temporary channel id", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._ import f._
@ -470,14 +493,14 @@ class PeerSpec extends FixtureSpec {
assert(peer.stateData.channels.isEmpty) assert(peer.stateData.channels.isEmpty)
// We ensure the current network feerate is higher than the default anchor output feerate. // We ensure the current network feerate is higher than the default anchor output feerate.
nodeParams.setFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None)) probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None))
val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR]
assert(init.channelType == ChannelTypes.AnchorOutputs()) assert(init.channelType == ChannelTypes.AnchorOutputs())
assert(!init.dualFunded) assert(!init.dualFunded)
assert(init.fundingAmount == 15000.sat) assert(init.fundingAmount == 15000.sat)
assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates)) assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates))
} }
test("use correct on-chain fee rates when spawning a channel (anchor outputs zero fee htlc)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => test("use correct on-chain fee rates when spawning a channel (anchor outputs zero fee htlc)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
@ -488,14 +511,14 @@ class PeerSpec extends FixtureSpec {
assert(peer.stateData.channels.isEmpty) assert(peer.stateData.channels.isEmpty)
// We ensure the current network feerate is higher than the default anchor output feerate. // We ensure the current network feerate is higher than the default anchor output feerate.
nodeParams.setFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat))) nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(minimum = FeeratePerKw(250 sat)))
probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None)) probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, None, None, None, None, None, None, None))
val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR]
assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx())
assert(!init.dualFunded) assert(!init.dualFunded)
assert(init.fundingAmount == 15000.sat) assert(init.fundingAmount == 15000.sat)
assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw)
assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates)) assert(init.fundingTxFeerate == nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates))
} }
test("use correct final script if option_static_remotekey is negotiated", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f => test("use correct final script if option_static_remotekey is negotiated", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>

View File

@ -514,6 +514,24 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
} }
} }
test("encode/decode recommended_feerates") {
val fundingRange = RecommendedFeeratesTlv.FundingFeerateRange(FeeratePerKw(5000 sat), FeeratePerKw(15_000 sat))
val commitmentRange = RecommendedFeeratesTlv.CommitmentFeerateRange(FeeratePerKw(253 sat), FeeratePerKw(2_000 sat))
val testCases = Seq(
// @formatter:off
RecommendedFeerates(Block.Testnet3GenesisBlock.hash, FeeratePerKw(2500 sat), FeeratePerKw(2500 sat)) -> hex"99f1 43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000 000009c4 000009c4",
RecommendedFeerates(Block.Testnet3GenesisBlock.hash, FeeratePerKw(5000 sat), FeeratePerKw(253 sat)) -> hex"99f1 43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000 00001388 000000fd",
RecommendedFeerates(Block.Testnet3GenesisBlock.hash, FeeratePerKw(10_000 sat), FeeratePerKw(1000 sat), TlvStream(fundingRange, commitmentRange)) -> hex"99f1 43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000 00002710 000003e8 01080000138800003a98 0308000000fd000007d0"
// @formatter:on
)
for ((expected, encoded) <- testCases) {
val decoded = lightningMessageCodec.decode(encoded.bits).require.value
assert(decoded == expected)
val reEncoded = lightningMessageCodec.encode(decoded).require.bytes
assert(reEncoded == encoded)
}
}
test("unknown messages") { test("unknown messages") {
// Non-standard tag number so this message can only be handled by a codec with a fallback // Non-standard tag number so this message can only be handled by a codec with a fallback
val unknown = UnknownMessage(tag = 47282, data = ByteVector32.Zeroes.bytes) val unknown = UnknownMessage(tag = 47282, data = ByteVector32.Zeroes.bytes)