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 526e033564..0b8754dd4c 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 d631153258..c6825d3fa8 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 935043f10b..ce74a08deb 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 f65769062a..73f55d52d9 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 c36fc5199b..4771371e3f 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 ca6c7448e0..569532e1df 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 bbd6a20182..4ca0696caa 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 6826df4393..d0be968257 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 38385f31c1..bc68f87e93 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 a35f524e0e..ee8d65860d 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 f7790558a0..26eaf3264f 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 f2053774da..66acf2f012 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 289e21483d..0744644fa2 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 b40a02cade..bad2b3eb28 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 f025093b69..8aeb636b0e 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 3351149b89..6b80738ece 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 dc44a122da..e1d3d3f1e4 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 d3a2d1ed82..2e459a7aa9 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 f625d58108..e8825fb4c7 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 9781fcf4b0..6b93eb5b98 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 3e4e5ce5a3..42dd76dd7e 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 863ed8e613..2b2c264f73 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 a508a41c92..597a2b43de 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 7bb087c68f..abbfb5e3f2 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 6db735d6d9..41fe28282a 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 0fd298ab8d..d20d86b4e3 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)