From db8290f80e3559b8d3a5e6325afb52014c65e21d Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:22:24 +0200 Subject: [PATCH] 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 --- .../main/scala/fr/acinq/eclair/Eclair.scala | 6 +-- .../scala/fr/acinq/eclair/NodeParams.scala | 34 +++++++++--- .../main/scala/fr/acinq/eclair/Setup.scala | 2 +- .../eclair/blockchain/BlockchainEvents.scala | 12 ++++- .../fr/acinq/eclair/channel/Helpers.scala | 4 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 52 +++++++++---------- .../eclair/channel/fsm/ErrorHandlers.scala | 10 ++-- .../channel/publish/ReplaceableTxFunder.scala | 2 +- .../publish/ReplaceableTxPublisher.scala | 4 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 18 +++++-- .../protocol/LightningMessageCodecs.scala | 13 +++-- .../wire/protocol/LightningMessageTypes.scala | 11 ++++ .../wire/protocol/SetupAndControlTlv.scala | 20 +++++++ .../scala/fr/acinq/eclair/TestConstants.scala | 4 +- .../eclair/channel/ChannelDataSpec.scala | 14 ++--- .../eclair/channel/CommitmentsSpec.scala | 20 +++---- .../publish/ReplaceableTxPublisherSpec.scala | 16 +++--- .../ChannelStateTestsHelperMethods.scala | 2 +- .../states/e/NormalSplicesStateSpec.scala | 4 +- .../channel/states/e/NormalStateSpec.scala | 30 +++++------ .../channel/states/e/OfflineStateSpec.scala | 8 +-- .../channel/states/f/ShutdownStateSpec.scala | 14 ++--- .../channel/states/h/ClosingStateSpec.scala | 6 +-- .../basic/fixtures/MinimalNodeFixture.scala | 2 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 33 ++++++++++-- .../protocol/LightningMessageCodecsSpec.scala | 18 +++++++ 26 files changed, 241 insertions(+), 118 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 526e03356..0b8754dd4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -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] = { val feeRate = confirmationTargetOrFeerate match { case Left(blocks) => - if (blocks < 3) appKit.nodeParams.currentFeerates.fast - else if (blocks > 6) appKit.nodeParams.currentFeerates.slow - else appKit.nodeParams.currentFeerates.medium + if (blocks < 3) appKit.nodeParams.currentBitcoinCoreFeerates.fast + else if (blocks > 6) appKit.nodeParams.currentBitcoinCoreFeerates.slow + else appKit.nodeParams.currentBitcoinCoreFeerates.medium case Right(feeratePerByte) => FeeratePerKw(feeratePerByte) } appKit.wallet match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index d63115325..c6825d3fa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong} import fr.acinq.eclair.Setup.Seeds import fr.acinq.eclair.blockchain.fee._ -import fr.acinq.eclair.channel.ChannelFlags import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy} +import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes} import fr.acinq.eclair.crypto.Noise.KeyPair import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager} import fr.acinq.eclair.db._ @@ -36,6 +36,7 @@ import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios} import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf} import fr.acinq.eclair.tor.Socks5ProxyParams +import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ import grizzled.slf4j.Logging import scodec.bits.ByteVector @@ -57,7 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager], instanceId: UUID, // a unique instance ID regenerated after each restart private val blockHeight: AtomicLong, - private val feerates: AtomicReference[FeeratesPerKw], + private val bitcoinCoreFeerates: AtomicReference[FeeratesPerKw], alias: String, color: Color, publicAddresses: List[NodeAddress], @@ -102,13 +103,34 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get) - def currentFeerates: FeeratesPerKw = feerates.get() + def currentBitcoinCoreFeerates: FeeratesPerKw = bitcoinCoreFeerates.get() /** 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. */ 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) { @@ -219,7 +241,7 @@ object NodeParams extends Logging { def makeNodeParams(config: Config, instanceId: UUID, 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 = { // check configuration for keys that have been renamed val deprecatedKeyPaths = Map( @@ -513,7 +535,7 @@ object NodeParams extends Logging { onChainKeyManager_opt = onChainKeyManager_opt, instanceId = instanceId, blockHeight = blockHeight, - feerates = feerates, + bitcoinCoreFeerates = bitcoinCoreFeerates, alias = nodeAlias, color = Color(color(0), color(1), color(2)), publicAddresses = addresses, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 935043f10..ce74a08de 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -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.Fast).update(feeratesPerKw.get.fast.toLong.toDouble) blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Fastest).update(feeratesPerKw.get.fastest.toLong.toDouble) - system.eventStream.publish(CurrentFeerates(feeratesPerKw.get)) + system.eventStream.publish(CurrentFeerates.BitcoinCore(feeratesPerKw.get)) logger.info(s"current feeratesPerKB=$feeratesPerKB feeratesPerKw=${feeratesPerKw.get}") feeratesRetrieved.trySuccess(Done) case Failure(exception) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala index f65769062..73f55d52d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala @@ -32,4 +32,14 @@ case class NewTransaction(tx: Transaction) 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 +} + + diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index c36fc5199..4771371e3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -119,7 +119,7 @@ object Helpers { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) // BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large. - val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.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)) // 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) // 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)) for { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index ca6c7448e..569532e1d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -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) 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 - context.system.eventStream.subscribe(self, classOf[CurrentFeerates]) + context.system.eventStream.subscribe(self, classOf[CurrentFeerates.BitcoinCore]) /* 8888888 888b 888 8888888 88888888888 @@ -435,7 +435,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with handleAddHtlcCommandError(c, error, Some(d.channelUpdate)) case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) => - d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentFeerates, nodeParams.onChainFeeConf) match { + d.commitments.sendAdd(c, nodeParams.currentBlockHeight, nodeParams.channelConf, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf) match { case Right((commitments1, add)) => if (c.commit) self ! CMD_SIGN() 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) => - 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 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) => - 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 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 if (d.commitments.params.localParams.paysClosingFees) { // 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 } else { // 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(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 => 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) { log.info("rejecting splice request: channel not quiescent") 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") stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceRequest(d.channelId).getMessage) } else { @@ -1295,7 +1295,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } 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 Left(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1341,7 +1341,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with if (commitments1.hasNoPendingHtlcsOrFeeUpdate) { if (d.commitments.params.localParams.paysClosingFees) { // 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 } else { // 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) if (d.commitments.params.localParams.paysClosingFees) { // 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 } else { // 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(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) => 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 => // 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 - 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) { 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}") @@ -1483,7 +1483,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with val lastLocalClosingFee_opt = lastLocalClosingSigned_opt.map(_.localClosingSigned.feeSatoshis) val (closingTx, closingSigned) = { // if we are not the channel initiator and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee - val localClosingFees = Closing.MutualClose.firstClosingFee(d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.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) 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) } else { log.info("updating our closing feerates: {}", feerates) - val (closingTx, closingSigned) = Closing.MutualClose.makeFirstClosingTx(keyManager, d.commitments.latest, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.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 { case previousNegotiations :+ currentNegotiation => previousNegotiations :+ (currentNegotiation :+ 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) val commitment = commitments1.latest val localCommitPublished1 = d.localCommitPublished.map(localCommitPublished => localCommitPublished.copy(htlcTxs = Closing.LocalClose.claimHtlcOutputs(keyManager, commitment))) - val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => remoteCommitPublished.copy(claimHtlcTxs = Closing.RemoteClose.claimHtlcOutputs(keyManager, commitment, commitment.remoteCommit, nodeParams.currentFeerates, 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 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.currentBitcoinCoreFeerates, d.finalScriptPubKey))) def republish(): Unit = { 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 (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 => blockchain ! WatchOutputSpent(self, tx.txid, claimTx.input.outPoint.index.toInt, hints = Set(claimTx.tx.txid))) rev1 @@ -1730,7 +1730,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with val d1 = d.copy( localCommitPublished = d.localCommitPublished.map(localCommitPublished => { // If the tx is one of our HTLC txs, we now publish a 3rd-stage claim-htlc-tx that claims its output. - val (localCommitPublished1, claimHtlcTx_opt) = Closing.LocalClose.claimHtlcDelayedOutput(localCommitPublished, keyManager, d.commitments.latest, tx, nodeParams.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 => { 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))) @@ -1939,7 +1939,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with 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) @@ -2133,7 +2133,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with 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? 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)) { 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 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 - 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)) goto(NEGOTIATING) using d.copy(closingTxProposed = closingTxProposed1) storing() sending d.localShutdown :: closingSigned :: Nil } 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(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) @@ -2282,7 +2282,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case Event(ProcessCurrentBlockHeight(_), _) => stay() // 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 case Event(_: BroadcastChannelUpdate, _) => stay() @@ -2558,7 +2558,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with private def handleCurrentFeerate(c: CurrentFeerates, d: ChannelDataWithCommitments) = { // TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw? val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(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 shouldUpdateFee = d.commitments.params.localParams.paysCommitTxFees && nodeParams.onChainFeeConf.shouldUpdateFee(currentFeeratePerKw, networkFeeratePerKw) 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) = { // TODO: all active commitments use the same feerate, but may have a different channel capacity: how should we compute networkFeeratePerKw? val commitments = d.commitments.latest - val networkFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(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 // if the network fees are too high we risk to not be able to confirm our current commitment 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] = { if (d.commitments.isQuiescent) { val parentCommitment = d.commitments.latest.commitment - val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates) + val targetFeerate = nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates) val fundingContribution = InteractiveTxFunder.computeSpliceContribution( isInitiator = true, sharedInput = Multisig2of2Input(parentCommitment), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala index bbd6a2018..4ca0696ca 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala @@ -206,7 +206,7 @@ trait ErrorHandlers extends CommonHandlers { val commitment = d.commitments.latest log.error(s"force-closing with fundingIndex=${commitment.fundingTxIndex}") val commitTx = commitment.fullySignedLocalCommitTx(keyManager).tx - val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) + val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, commitment, commitTx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey) val nextData = d match { case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished)) case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, waitingSince = nodeParams.currentBlockHeight, finalScriptPubKey = finalScriptPubKey, negotiating.closingTxProposed.flatten.map(_.unsignedTx), localCommitPublished = Some(localCommitPublished)) @@ -252,7 +252,7 @@ trait ErrorHandlers extends CommonHandlers { require(commitTx.txid == commitments.remoteCommit.txid, "txid mismatch") val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitments.commitInput, commitTx, d.commitments.params.localParams.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 { 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)) @@ -270,7 +270,7 @@ trait ErrorHandlers extends CommonHandlers { val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(commitment.commitInput, commitTx, d.commitments.params.localParams.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 { 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)) @@ -305,7 +305,7 @@ trait ErrorHandlers extends CommonHandlers { val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d) Closing.RevokedClose.getRemotePerCommitmentSecret(keyManager, d.commitments.params, d.commitments.remotePerCommitmentSecrets, tx) match { case Some((commitmentNumber, remotePerCommitmentSecret)) => - val revokedCommitPublished = Closing.RevokedClose.claimCommitTxOutputs(keyManager, d.commitments.params, tx, commitmentNumber, remotePerCommitmentSecret, nodeParams.db.channels, nodeParams.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") 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) @@ -324,7 +324,7 @@ trait ErrorHandlers extends CommonHandlers { val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint val remoteCommitPublished = RemoteCommitPublished( commitTx = tx, - claimMainOutputTx = Closing.RemoteClose.claimMainOutput(keyManager, d.commitments.params, remotePerCommitmentPoint, tx, nodeParams.currentFeerates, nodeParams.onChainFeeConf, finalScriptPubKey), + claimMainOutputTx = Closing.RemoteClose.claimMainOutput(keyManager, d.commitments.params, remotePerCommitmentPoint, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, finalScriptPubKey), claimHtlcTxs = Map.empty, claimAnchorTxs = List.empty, irrevocablySpent = Map.empty) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala index 6826df439..d0be96825 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala @@ -75,7 +75,7 @@ object ReplaceableTxFunder { Behaviors.withMdc(txPublishContext.mdc()) { Behaviors.receiveMessagePartial { 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) tx match { case Right(txWithWitnessData) => txFunder.fund(txWithWitnessData, targetFeerate) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala index 38385f31c..bc68f87e9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala @@ -158,7 +158,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, } Behaviors.receiveMessagePartial { case CheckUtxosResult(isSafe, currentBlockHeight) => - val targetFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe) + val targetFeerate = getFeerate(nodeParams.currentBitcoinCoreFeerates, confirmationTarget, currentBlockHeight, isSafe) fund(txWithWitnessData, targetFeerate) case UpdateConfirmationTarget(target) => confirmationTarget = target @@ -220,7 +220,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams, case CheckUtxosResult(isSafe, currentBlockHeight) => // We make sure we increase the fees by at least 20% as we get closer to the confirmation target. val bumpRatio = 1.2 - val currentFeerate = getFeerate(nodeParams.currentFeerates, confirmationTarget, currentBlockHeight, isSafe) + val currentFeerate = getFeerate(nodeParams.currentBitcoinCoreFeerates, confirmationTarget, currentBlockHeight, isSafe) val targetFeerate_opt = confirmationTarget match { case ConfirmationTarget.Absolute(confirmBefore) => if (confirmBefore <= currentBlockHeight + 6) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index a35f524e0..ee8d65860 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -29,7 +29,7 @@ import fr.acinq.eclair.NotificationsLogger.NotifyNodeOperator import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.blockchain.{OnChainChannelFunder, OnchainPubkeyCache} +import fr.acinq.eclair.blockchain.{CurrentFeerates, OnChainChannelFunder, OnchainPubkeyCache} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.io.MessageRelay.Status @@ -63,6 +63,8 @@ class Peer(val nodeParams: NodeParams, import Peer._ + context.system.eventStream.subscribe(self, classOf[CurrentFeerates]) + startWith(INSTANTIATING, Nothing) when(INSTANTIATING) { @@ -170,8 +172,8 @@ class Peer(val nodeParams: NodeParams, } else { randomBytes32() } - val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentFeerates)) - val commitTxFeerate = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelType.commitmentFormat, c.fundingAmount) + val fundingTxFeerate = c.fundingTxFeerate_opt.getOrElse(nodeParams.onChainFeeConf.getFundingFeerate(nodeParams.currentBitcoinCoreFeerates)) + 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") 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)) @@ -345,6 +347,13 @@ class Peer(val nodeParams: NodeParams, } 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(RelayOnionMessage(messageId, _, replyTo_opt), _) => @@ -389,6 +398,9 @@ class Peer(val nodeParams: NodeParams, // 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) + // 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) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index f7790558a..26eaf3264 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -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] = ( ("tag" | uint16) :: ("message" | bytes) @@ -479,14 +485,15 @@ object LightningMessageCodecs { .typecase(513, onionMessageCodec) // NB: blank lines to minimize merge conflicts + // // .typecase(37000, spliceInitCodec) .typecase(37002, spliceAckCodec) .typecase(37004, spliceLockedCodec) - // - - // + // + // + .typecase(39409, recommendedFeeratesCodec) // // diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index f2053774d..66acf2f01 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -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 \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala index 289e21483..0744644fa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/SetupAndControlTlv.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.BlockHash 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.TlvCodecs.{tlvField, tlvStream} import scodec.Codec @@ -85,4 +86,23 @@ sealed trait PongTlv extends Tlv object PongTlv { 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) + ) } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index b40a02cad..bad2b3eb2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -90,7 +90,7 @@ object TestConstants { channelKeyManager, onChainKeyManager_opt = None, blockHeight = new AtomicLong(defaultBlockHeight), - feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), + bitcoinCoreFeerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), alias = "alice", color = Color(1, 2, 3), publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil, @@ -264,7 +264,7 @@ object TestConstants { channelKeyManager, onChainKeyManager_opt = None, blockHeight = new AtomicLong(defaultBlockHeight), - feerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), + bitcoinCoreFeerates = new AtomicReference(FeeratesPerKw.single(feeratePerKw)), alias = "bob", color = Color(4, 5, 6), publicAddresses = NodeAddress.fromParts("localhost", 9732).get :: Nil, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala index f025093b6..8aeb636b0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala @@ -118,7 +118,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) { case (current, tx) => - val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.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) } assert(!lcp3.isDone) @@ -141,7 +141,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) { case (current, tx) => - val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.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) } assert(!lcp3.isDone) @@ -160,7 +160,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel assert(lcp5.claimHtlcDelayedTxs.length == 3) val newHtlcSuccessTx = lcp5.htlcTxs(remainingHtlcOutpoint).get.tx - val (lcp6, Some(newClaimHtlcDelayedTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp5, nodeParams.channelKeyManager, aliceClosing.commitments.latest, newHtlcSuccessTx, nodeParams.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) val lcp7 = Closing.updateLocalCommitPublished(lcp6, newHtlcSuccessTx) @@ -177,7 +177,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val remoteHtlcSuccess = rcp.claimHtlcTxs.values.collectFirst { case Some(tx: ClaimHtlcSuccessTx) => tx }.get val lcp3 = (htlcSuccessTxs.map(_.tx) ++ Seq(remoteHtlcSuccess.tx)).foldLeft(lcp) { case (current, tx) => - val (current1, _) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.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) } assert(lcp3.claimHtlcDelayedTxs.length == 1) @@ -188,7 +188,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val remainingHtlcTimeoutTxs = htlcTimeoutTxs.filter(_.input.outPoint != remoteHtlcSuccess.input.outPoint) assert(remainingHtlcTimeoutTxs.length == 1) - val (lcp5, Some(remainingClaimHtlcTx)) = Closing.LocalClose.claimHtlcDelayedOutput(lcp4, nodeParams.channelKeyManager, aliceClosing.commitments.latest, remainingHtlcTimeoutTxs.head.tx, nodeParams.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) val lcp6 = (remainingHtlcTimeoutTxs.map(_.tx) ++ Seq(remainingClaimHtlcTx.tx)).foldLeft(lcp5) { @@ -207,7 +207,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val lcp3 = htlcTimeoutTxs.map(_.tx).foldLeft(lcp) { case (current, tx) => - val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.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) } assert(!lcp3.isDone) @@ -233,7 +233,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel val lcp3 = (htlcSuccessTxs.map(_.tx) ++ htlcTimeoutTxs.map(_.tx)).foldLeft(lcp) { case (current, tx) => - val (current1, Some(_)) = Closing.LocalClose.claimHtlcDelayedOutput(current, nodeParams.channelKeyManager, aliceClosing.commitments.latest, tx, nodeParams.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) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala index 3351149b8..6b80738ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala @@ -82,11 +82,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (payment_preimage, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.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.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.availableBalanceForReceive == a - p - htlcOutputFee) @@ -167,11 +167,11 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (_, cmdAdd) = makeCmdAdd(p, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add)) = ac0.sendAdd(cmdAdd, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.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.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.availableBalanceForReceive == a - p - htlcOutputFee) @@ -255,29 +255,29 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bc0.availableBalanceForReceive == a) val (payment_preimage1, cmdAdd1) = makeCmdAdd(p1, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac1, add1)) = ac0.sendAdd(cmdAdd1, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.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.availableBalanceForReceive == b) val (_, cmdAdd2) = makeCmdAdd(p2, bob.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((ac2, add2)) = ac1.sendAdd(cmdAdd2, currentBlockHeight, alice.underlyingActor.nodeParams.channelConf, alice.underlyingActor.nodeParams.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.availableBalanceForReceive == b) val (payment_preimage3, cmdAdd3) = makeCmdAdd(p3, alice.underlyingActor.nodeParams.nodeId, currentBlockHeight) - val Right((bc1, add3)) = bc0.sendAdd(cmdAdd3, currentBlockHeight, bob.underlyingActor.nodeParams.channelConf, bob.underlyingActor.nodeParams.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.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.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.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.availableBalanceForReceive == b - p3) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index dc44a122d..e1d3d3f1e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -84,8 +84,8 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w /** Set uniform feerate for all block targets. */ def setFeerate(feerate: FeeratePerKw, fastest: FeeratePerKw = FeeratePerKw(100_000 sat)): Unit = { - alice.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) - bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) + alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) + bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(FeeratesPerKw.single(feerate).copy(fastest = fastest)) } /** Set feerate for a specific block target. */ @@ -97,8 +97,8 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w case _ => currentFeerates.copy(slow = feerate) } - alice.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates)) - bob.underlyingActor.nodeParams.setFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentFeerates)) + alice.underlyingActor.nodeParams.setBitcoinCoreFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates)) + bob.underlyingActor.nodeParams.setBitcoinCoreFeerates(updateFeerates(alice.underlyingActor.nodeParams.currentBitcoinCoreFeerates)) } def getMempool(): Seq[Transaction] = { @@ -496,7 +496,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w probe.expectMsg(commitTx.tx.txid) 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) assert(maxFeerate <= targetFeerate / 2) 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) setFeerate(targetFeerate, blockTarget = 12) 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) val htlcSuccessTx = testPublishHtlcSuccess(f, commitTx, htlcSuccess, htlcSuccessMaxFeerate) assert(htlcSuccessTx.txIn.length > 1) 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) val htlcTimeoutTx = testPublishHtlcTimeout(f, commitTx, htlcTimeout, htlcTimeoutMaxFeerate) assert(htlcTimeoutTx.txIn.length > 1) @@ -1567,7 +1567,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) { 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 claimHtlcSuccessTx = testPublishClaimHtlcSuccess(f, remoteCommitTx, claimHtlcSuccess, currentFeerate) assert(claimHtlcSuccess.txInfo.fee > 0.sat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index d3a2d1ed8..2e459a7aa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -655,7 +655,7 @@ object ChannelStateTestsBase { 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)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala index f625d5810..e8825fb4c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala @@ -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. 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 actualMiningFee = capacity - alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.latest.capacity // Fee computation is approximate (signature size isn't constant). @@ -518,7 +518,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice ! cmd // we tweak the feerate 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) val txAbortBob = bob2alice.expectMsgType[TxAbort] bob2alice.forward(alice, txAbortBob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 9781fcf4b..6b93eb5b9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -280,7 +280,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with crossSign(alice, bob, alice2bob, bob2alice) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat) // We increase the feerate to get Alice's balance closer to her channel reserve. - bob.underlyingActor.nodeParams.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) // 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() 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 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 bob.setFeerate(FeeratePerKw(11000 sat)) - bob ! CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) + bob ! CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat))) bob2alice.expectNoMessage(100 millis) bob ! add sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] @@ -2313,7 +2313,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments val tx = bobCommitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val expectedFeeratePerKw = bob.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) bob ! UpdateFee(ByteVector32.Zeroes, FeeratePerKw(252 sat)) 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 => import f._ 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 ! 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 => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] 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 ! event1 alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, TestConstants.anchorOutputsFeeratePerKw / 2)) alice2bob.expectMsgType[CommitSig] // The configured maximum feerate is bypassed if it's below the propagation threshold. - 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 ! event2 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 => import f._ - val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat))) + val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat))) alice.setFeerates(event.feeratesPerKw) alice ! event alice2bob.expectNoMessage(500 millis) @@ -3007,7 +3007,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) - 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 ! event 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 => import f._ - val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) + val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat))) bob.setFeerates(event.feeratesPerKw) bob ! event bob2alice.expectNoMessage(500 millis) @@ -3027,7 +3027,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with addHtlc(10000000 msat, 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 ! event 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) // 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 ! event 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 => 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 ! event 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 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 claimFeeRate = Transactions.fee2rate(claimFee, claimMain.weight()) assert(claimFeeRate >= expectedFeeRate * 0.9 && claimFeeRate <= expectedFeeRate * 1.2) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 3e4e5ce5a..42dd76dd7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -664,7 +664,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // alice is funder alice.setFeerates(networkFeerates) - alice ! CurrentFeerates(networkFeerates) + alice ! CurrentFeerates.BitcoinCore(networkFeerates) if (shouldClose) { assert(alice2blockchain.expectMsgType[PublishFinalTx].tx.txid == aliceCommitTx.txid) } else { @@ -691,7 +691,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // this time Alice will ignore feerate changes for the offline channel alice.setFeerates(networkFeerates) - alice ! CurrentFeerates(networkFeerates) + alice ! CurrentFeerates.BitcoinCore(networkFeerates) alice2blockchain.expectNoMessage() alice2bob.expectNoMessage() } @@ -708,7 +708,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // Alice ignores feerate changes while offline alice.setFeerates(networkFeerates) - alice ! CurrentFeerates(networkFeerates) + alice ! CurrentFeerates.BitcoinCore(networkFeerates) alice2blockchain.expectNoMessage() alice2bob.expectNoMessage() @@ -775,7 +775,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // bob is fundee bob.setFeerates(networkFeerates) - bob ! CurrentFeerates(networkFeerates) + bob ! CurrentFeerates.BitcoinCore(networkFeerates) if (shouldClose) { assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == bobCommitTx.txid) } else { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 863ed8e61..2b2c264f7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -662,17 +662,17 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val event = CurrentFeerates(FeeratesPerKw(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 ! 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 => import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] 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 ! event 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 => import f._ - val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10010 sat))) + val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(10010 sat))) alice.setFeerates(event.feeratesPerKw) alice ! event alice2bob.expectNoMessage(500 millis) @@ -690,7 +690,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] assert(initialState.commitments.latest.localCommit.spec.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) - 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 ! event 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 => import f._ - val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(11000 sat))) + val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(11000 sat))) bob.setFeerates(event.feeratesPerKw) bob ! event 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 => import f._ - val event = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(25000 sat))) + val event = CurrentFeerates.BitcoinCore(FeeratesPerKw.single(FeeratePerKw(25000 sat))) bob.setFeerates(event.feeratesPerKw) bob ! event bob2alice.expectMsgType[Error] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index a508a41c9..597a2b43d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -291,7 +291,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val sender = TestProbe() 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 ! CMD_CLOSE(sender.ref, None, Some(ClosingFeerates(FeeratePerKw(500 sat), FeeratePerKw(250 sat), FeeratePerKw(1000 sat)))) alice2bob.expectMsgType[Shutdown] @@ -651,7 +651,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // simulate a node restart after a feerate increase val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING] 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) alice2blockchain.expectMsgType[SetChannelId] awaitCond(alice.stateName == CLOSING) @@ -739,7 +739,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // We simulate a node restart after a feerate increase. 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 ! INPUT_RESTORED(beforeRestart) alice2blockchain.expectMsgType[SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 7bb087c68..abbfb5e3f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -74,7 +74,7 @@ object MinimalNodeFixture extends Assertions with Eventually with IntegrationPat torAddress_opt = None, database = TestDatabases.inMemoryDb(), 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(_.chainHash).setTo(Block.RegtestGenesisBlock.hash) .modify(_.routerConf.routerBroadcastInterval).setTo(1 second) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 6db735d6d..41fe28282 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -25,8 +25,8 @@ import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features._ import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.DummyOnChainWallet 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.states.ChannelStateTestsTags import fr.acinq.eclair.io.Peer._ @@ -112,6 +112,7 @@ class PeerSpec extends FixtureSpec { switchboard.send(peer, Peer.Init(channels)) val localInit = protocol.Init(peer.underlyingActor.nodeParams.features.initFeatures()) switchboard.send(peer, PeerConnection.ConnectionReady(peerConnection.ref, remoteNodeId, fakeIPAddress, outgoing = true, localInit, remoteInit)) + peerConnection.expectMsgType[RecommendedFeerates] val probe = TestProbe() probe.send(peer, Peer.GetPeerInfo(Some(probe.ref.toTyped))) 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.expectMsgType[RecommendedFeerates] // peer should kill previous connection peerConnection1.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced)) 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.expectMsgType[RecommendedFeerates] // peer should kill previous connection peerConnection2.expectMsg(PeerConnection.Kill(PeerConnection.KillReason.ConnectionReplaced)) channel.expectMsg(INPUT_DISCONNECTED) @@ -325,6 +328,26 @@ class PeerSpec extends FixtureSpec { 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 => import f._ @@ -470,14 +493,14 @@ class PeerSpec extends FixtureSpec { assert(peer.stateData.channels.isEmpty) // 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)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.channelType == ChannelTypes.AnchorOutputs()) assert(!init.dualFunded) assert(init.fundingAmount == 15000.sat) assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) - assert(init.fundingTxFeerate == 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 => @@ -488,14 +511,14 @@ class PeerSpec extends FixtureSpec { assert(peer.stateData.channels.isEmpty) // 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)) val init = channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR] assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx()) assert(!init.dualFunded) assert(init.fundingAmount == 15000.sat) assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) - assert(init.fundingTxFeerate == 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 => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 0fd298ab8..d20d86b4e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -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") { // 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)