Skip to content

Commit db8290f

Browse files
t-bastpm47
andauthored
Add recommended_feerates optional message (#2860)
We send to our peers an optional message that tells them the feerates we'd like to use for funding channels. This lets them know which values are acceptable to us, in case we reject their funding requests. This is using an odd type and will be automatically ignored by existing nodes who don't support that feature. Co-authored-by: Pierre-Marie Padiou <[email protected]>
1 parent cfdb088 commit db8290f

26 files changed

+241
-118
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,9 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
381381
override def sendOnChain(address: String, amount: Satoshi, confirmationTargetOrFeerate: Either[Long, FeeratePerByte]): Future[TxId] = {
382382
val feeRate = confirmationTargetOrFeerate match {
383383
case Left(blocks) =>
384-
if (blocks < 3) appKit.nodeParams.currentFeerates.fast
385-
else if (blocks > 6) appKit.nodeParams.currentFeerates.slow
386-
else appKit.nodeParams.currentFeerates.medium
384+
if (blocks < 3) appKit.nodeParams.currentBitcoinCoreFeerates.fast
385+
else if (blocks > 6) appKit.nodeParams.currentBitcoinCoreFeerates.slow
386+
else appKit.nodeParams.currentBitcoinCoreFeerates.medium
387387
case Right(feeratePerByte) => FeeratePerKw(feeratePerByte)
388388
}
389389
appKit.wallet match {

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2121
import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, Crypto, Satoshi, SatoshiLong}
2222
import fr.acinq.eclair.Setup.Seeds
2323
import fr.acinq.eclair.blockchain.fee._
24-
import fr.acinq.eclair.channel.ChannelFlags
2524
import fr.acinq.eclair.channel.fsm.Channel
2625
import fr.acinq.eclair.channel.fsm.Channel.{BalanceThreshold, ChannelConf, UnhandledExceptionStrategy}
26+
import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes}
2727
import fr.acinq.eclair.crypto.Noise.KeyPair
2828
import fr.acinq.eclair.crypto.keymanager.{ChannelKeyManager, NodeKeyManager, OnChainKeyManager}
2929
import fr.acinq.eclair.db._
@@ -36,6 +36,7 @@ import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios}
3636
import fr.acinq.eclair.router.Router._
3737
import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf}
3838
import fr.acinq.eclair.tor.Socks5ProxyParams
39+
import fr.acinq.eclair.transactions.Transactions
3940
import fr.acinq.eclair.wire.protocol._
4041
import grizzled.slf4j.Logging
4142
import scodec.bits.ByteVector
@@ -57,7 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
5758
onChainKeyManager_opt: Option[OnChainKeyManager],
5859
instanceId: UUID, // a unique instance ID regenerated after each restart
5960
private val blockHeight: AtomicLong,
60-
private val feerates: AtomicReference[FeeratesPerKw],
61+
private val bitcoinCoreFeerates: AtomicReference[FeeratesPerKw],
6162
alias: String,
6263
color: Color,
6364
publicAddresses: List[NodeAddress],
@@ -102,13 +103,34 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
102103

103104
def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get)
104105

105-
def currentFeerates: FeeratesPerKw = feerates.get()
106+
def currentBitcoinCoreFeerates: FeeratesPerKw = bitcoinCoreFeerates.get()
106107

107108
/** Only to be used in tests. */
108-
def setFeerates(value: FeeratesPerKw): Unit = feerates.set(value)
109+
def setBitcoinCoreFeerates(value: FeeratesPerKw): Unit = bitcoinCoreFeerates.set(value)
109110

110111
/** Returns the features that should be used in our init message with the given peer. */
111112
def initFeaturesFor(nodeId: PublicKey): Features[InitFeature] = overrideInitFeatures.getOrElse(nodeId, features).initFeatures()
113+
114+
/** Returns the feerates we'd like our peer to use when funding channels. */
115+
def recommendedFeerates(remoteNodeId: PublicKey, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): RecommendedFeerates = {
116+
val feerateTolerance = onChainFeeConf.feerateToleranceFor(remoteNodeId)
117+
val fundingFeerate = onChainFeeConf.getFundingFeerate(currentBitcoinCoreFeerates)
118+
val fundingRange = RecommendedFeeratesTlv.FundingFeerateRange(
119+
min = fundingFeerate * feerateTolerance.ratioLow,
120+
max = fundingFeerate * feerateTolerance.ratioHigh,
121+
)
122+
// We use the most likely commitment format, even though there is no guarantee that this is the one that will be used.
123+
val commitmentFormat = ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, announceChannel = false).commitmentFormat
124+
val commitmentFeerate = onChainFeeConf.getCommitmentFeerate(currentBitcoinCoreFeerates, remoteNodeId, commitmentFormat, channelConf.minFundingPrivateSatoshis)
125+
val commitmentRange = RecommendedFeeratesTlv.CommitmentFeerateRange(
126+
min = commitmentFeerate * feerateTolerance.ratioLow,
127+
max = commitmentFormat match {
128+
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
129+
case _: Transactions.AnchorOutputsCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
130+
},
131+
)
132+
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))
133+
}
112134
}
113135

114136
case class PaymentFinalExpiryConf(min: CltvExpiryDelta, max: CltvExpiryDelta) {
@@ -219,7 +241,7 @@ object NodeParams extends Logging {
219241

220242
def makeNodeParams(config: Config, instanceId: UUID,
221243
nodeKeyManager: NodeKeyManager, channelKeyManager: ChannelKeyManager, onChainKeyManager_opt: Option[OnChainKeyManager],
222-
torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, feerates: AtomicReference[FeeratesPerKw],
244+
torAddress_opt: Option[NodeAddress], database: Databases, blockHeight: AtomicLong, bitcoinCoreFeerates: AtomicReference[FeeratesPerKw],
223245
pluginParams: Seq[PluginParams] = Nil): NodeParams = {
224246
// check configuration for keys that have been renamed
225247
val deprecatedKeyPaths = Map(
@@ -513,7 +535,7 @@ object NodeParams extends Logging {
513535
onChainKeyManager_opt = onChainKeyManager_opt,
514536
instanceId = instanceId,
515537
blockHeight = blockHeight,
516-
feerates = feerates,
538+
bitcoinCoreFeerates = bitcoinCoreFeerates,
517539
alias = nodeAlias,
518540
color = Color(color(0), color(1), color(2)),
519541
publicAddresses = addresses,

eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class Setup(val datadir: File,
255255
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Medium).update(feeratesPerKw.get.medium.toLong.toDouble)
256256
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Fast).update(feeratesPerKw.get.fast.toLong.toDouble)
257257
blockchain.Monitoring.Metrics.FeeratesPerByte.withTag(blockchain.Monitoring.Tags.Priority, blockchain.Monitoring.Tags.Priorities.Fastest).update(feeratesPerKw.get.fastest.toLong.toDouble)
258-
system.eventStream.publish(CurrentFeerates(feeratesPerKw.get))
258+
system.eventStream.publish(CurrentFeerates.BitcoinCore(feeratesPerKw.get))
259259
logger.info(s"current feeratesPerKB=$feeratesPerKB feeratesPerKw=${feeratesPerKw.get}")
260260
feeratesRetrieved.trySuccess(Done)
261261
case Failure(exception) =>

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/BlockchainEvents.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,14 @@ case class NewTransaction(tx: Transaction) extends BlockchainEvent
3232

3333
case class CurrentBlockHeight(blockHeight: BlockHeight) extends BlockchainEvent
3434

35-
case class CurrentFeerates(feeratesPerKw: FeeratesPerKw) extends BlockchainEvent
35+
sealed trait CurrentFeerates extends BlockchainEvent {
36+
val feeratesPerKw: FeeratesPerKw
37+
}
38+
39+
object CurrentFeerates {
40+
//@formatter:off
41+
case class BitcoinCore(feeratesPerKw: FeeratesPerKw) extends CurrentFeerates
42+
//@formatter:on
43+
}
44+
45+

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ object Helpers {
119119
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
120120

121121
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
122-
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis)
122+
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis)
123123
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.feeratePerKw)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw))
124124

125125
// 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 {
166166
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
167167

168168
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
169-
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount)
169+
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingAmount)
170170
if (nodeParams.onChainFeeConf.feerateToleranceFor(remoteNodeId).isFeeDiffTooHigh(channelFeatures.commitmentFormat, localFeeratePerKw, open.commitmentFeerate)) return Left(FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.commitmentFeerate))
171171

172172
for {

0 commit comments

Comments
 (0)