What changed, and why it matters
This commit only adds user-facing documentation for a new experimental feature and adds metrics that report profit and payment volume. It also removes a temporary workaround that delayed automated actions until enough historical data was available. There is no security-relevant code change visible in the diff.
No security action required. Operators enabling the experimental peer scorer should review the documented configuration carefully, especially the auto-fund, auto-close, and auto-update relay fees settings, because these can affect funds and routing policy.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit adds release notes describing the experimental PeerScorer, introduces four Kamon gauges for rolling daily/weekly profit and volume, and removes the hasPastData guard that previously prevented automated channel open/close/fee-update actions from running immediately after restart. The feature remains disabled by default and all automated actions are opt-in via configuration.
Changed components
docs/release-notes/eclair-vnext.mdeclair-core/src/main/scala/fr/acinq/eclair/profit/Monitoring.scalaeclair-core/src/main/scala/fr/acinq/eclair/profit/PeerScorer.scalaInspect captured patch +134 / −3
diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md
index ed697f9..9ec1a25 100644
--- a/docs/release-notes/eclair-vnext.md
+++ b/docs/release-notes/eclair-vnext.md
@@ -54,6 +54,104 @@ It should be acceptable, since liquidity decisions should be taken based on rela
We expose a now `relaystats` API that ranks peers based on the routing fees they're generating.
See #3245 for more details.
+### Experimental peer scoring
+
+We're introducing a `PeerScorer` actor that gathers payment statistics about every peer we have a channel with.
+It tracks fees earned, on-chain fees paid and liquidity fees (earned or paid) and how payment volume evolves over time.
+It then ranks peers based on those statistics and identifies peers that may need additional liquidity to optimize revenue.
+It will print tables of actions that should be taken, which can be extracted from `eclair.log` with:
+
+```sh
+grep PeerScorer eclair.log
+```
+
+It will provide recommendations for:
+
+- channels to fund based on payment activity
+- relay fees based on payment activity and fees earned
+- channels to close to reclaim inefficient liquidity allocation
+
+It is disabled by default: it can be enabled by adding the following line to `eclair.conf`:
+
+```conf
+eclair.peer-scoring.enabled = true
+```
+
+It contains a lot of parameters that need to be set to match your node's strategy. Here are the default values:
+
+```conf
+eclair.peer-scoring {
+ // Set this to true if you want to start collecting data to score peers.
+ enabled = false
+ // Frequency at which we run our peer scoring algorithm.
+ frequency = 1 hour
+ // Maximum number of peers to select as candidates for liquidity and relay fee updates.
+ top-peers-count = 10
+ // A list of node_ids with whom we will try to maintain liquidity.
+ top-peers-whitelist = []
+ // On restart, we read all past events from the previous week from the DB, which is expensive.
+ // We do this in 56 3-hour chunks to avoid performance issues, with a delay between each chunk.
+ // We only read the first chunk after an initial delay, since this isn't critical and there are already a lot of DB
+ // reads when restarting an eclair node that have higher priority.
+ past-events {
+ init-delay = 10 minutes
+ chunk-delay = 10 seconds
+ }
+ // We can automatically allocate liquidity to our top peers when necessary.
+ liquidity {
+ // If true, we will automatically fund channels.
+ auto-fund = false
+ // If true, we will automatically close unused channels to reclaim liquidity.
+ auto-close = false
+ // We only fund channels if at least this amount is necessary.
+ min-funding-amount-satoshis = 1000000 // 0.01 btc
+ // We never fund channels with more than this amount.
+ max-funding-amount-satoshis = 50000000 // 0.5 btc
+ // Maximum total capacity (across all channels) per peer.
+ max-per-peer-capacity-satoshis = 1000000000 // 10 btc
+ // We won't close channels if our local balance is below this amount.
+ local-balance-closing-threshold-satoshis = 10000000 // 0.1 btc
+ // We won't close channels where the remote balance exceeds this amount.
+ remote-balance-closing-threshold-satoshis = 5000000 // 0.05 btc
+ // We stop funding channels if our on-chain balance is below this amount.
+ min-on-chain-balance-satoshis = 50000000 // 0.5 btc
+ // We stop funding channels if the on-chain feerate is above this value.
+ max-feerate-sat-per-byte = 5
+ // Rate-limit the number of funding transactions we make per day (on average).
+ max-funding-tx-per-day = 6
+ // Minimum time between funding the same peer, to evaluate whether the previous funding was effective.
+ funding-cooldown = 72 hours
+ }
+ // We can automatically update our relay fees to our top peers when necessary.
+ relay-fees {
+ // If true, we will automatically update our relay fees based on variations in outgoing payment volume.
+ auto-update = false
+ // We will not lower our fees below these values.
+ min-fee-base-msat = 1
+ min-fee-proportional-millionths = 500
+ // We will not increase our fees above these values.
+ max-fee-base-msat = 10000
+ max-fee-proportional-millionths = 5000
+ // We only increase fees if the daily outgoing payment volume exceeds this threshold or daily-payment-volume-threshold-percent.
+ daily-payment-volume-threshold-satoshis = 10000000 // 0.1 btc
+ // We only increase fees if the daily outgoing payment volume exceeds this percentage of our peer capacity or daily-payment-volume-threshold.
+ daily-payment-volume-threshold-percent = 0.05
+ }
+}
+```
+
+Node operators should adjust these values until the recommendations made by the peer scorer start making sense.
+At that point, node operators may consider letting the peer scorer automatically perform actions by setting:
+
+```conf
+eclair.peer-scoring.liquidity.auto-fund = true
+eclair.peer-scoring.liquidity.auto-close = true
+eclair.peer-scoring.relay-fees.auto-update = true
+```
+
+This is highly experimental, and it is extremely hard to ensure that heuristics perform well for all types of nodes.
+Use this at your own risk!
+
### Plugin validation of interactive transactions
We add a new `ValidateInteractiveTxPlugin` trait that can be extended by plugins that want to perform custom validation of remote inputs and outputs added to interactive transactions.
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/profit/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/profit/Monitoring.scala
new file mode 100644
index 0000000..cd39cea
--- /dev/null
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/profit/Monitoring.scala
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2026 ACINQ SAS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package fr.acinq.eclair.profit
+
+import kamon.Kamon
+import kamon.metric.Metric
+
+object Monitoring {
+
+ object Metrics {
+ val DailyProfit: Metric.Gauge = Kamon.gauge("routing.profit.daily", "Rolling daily routing profit (mBTC)")
+ val WeeklyProfit: Metric.Gauge = Kamon.gauge("routing.profit.weekly", "Rolling weekly routing profit (mBTC)")
+ val DailyVolume: Metric.Gauge = Kamon.gauge("routing.volume.daily", "Rolling daily payment volume (mBTC)")
+ val WeeklyVolume: Metric.Gauge = Kamon.gauge("routing.volume.weekly", "Rolling weekly payment volume (mBTC)")
+ }
+
+}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/profit/PeerScorer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/profit/PeerScorer.scala
index 7a754cd..c0703f8 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/profit/PeerScorer.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/profit/PeerScorer.scala
@@ -211,6 +211,10 @@ private class PeerScorer(nodeParams: NodeParams, wallet: OnChainBalanceChecker,
log.info("scoring {} peers", peers.size)
val dailyProfit = peers.map(_.stats.take(Bucket.bucketsPerDay).map(_.profit).sum).sum.truncateToSatoshi.toMilliBtc
val weeklyProfit = peers.map(_.stats.map(_.profit).sum).sum.truncateToSatoshi.toMilliBtc
+ Monitoring.Metrics.DailyProfit.withoutTags().update(dailyProfit.toDouble)
+ Monitoring.Metrics.WeeklyProfit.withoutTags().update(weeklyProfit.toDouble)
+ Monitoring.Metrics.DailyVolume.withoutTags().update(peers.map(_.dailyVolumeOut).sum.truncateToSatoshi.toMilliBtc.toDouble)
+ Monitoring.Metrics.WeeklyVolume.withoutTags().update(peers.map(_.stats.map(_.totalAmountOut).sum).sum.truncateToSatoshi.toMilliBtc.toDouble)
log.info("rolling daily profit = {} and weekly profit = {}", dailyProfit, weeklyProfit)
// We select peers that have the largest outgoing payment volume of the past day.
@@ -287,9 +291,7 @@ private class PeerScorer(nodeParams: NodeParams, wallet: OnChainBalanceChecker,
// Some actions such as opening or closing channels or updating relay fees should only run periodically, not when
// explicitly requested by a caller (replyTo_opt).
- // TODO: remove hasPastData after successfully deploying the AuditDb changes.
- val hasPastData = bestPeersByVolume.exists(_.stats.drop(Bucket.bucketsPerDay).exists(_ != PeerStats.empty))
- if (hasPastData && replyTo_opt.isEmpty) {
+ if (replyTo_opt.isEmpty) {
closeUnbalancedChannelsIfNeeded(peers)
closeIdleChannelsIfNeeded(peers)
val (updatedPeers, history1) = updateRelayFeesIfNeeded(bestPeersByVolume, history)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.