Add metrics on interactive-tx inputs and outputs (#3300)
What changed, and why it matters
This commit only adds new telemetry (metrics) to observe how many inputs and outputs are used in interactive Bitcoin transactions during Lightning channel funding or splicing. It records counts and amounts into monitoring histograms but does not change any transaction validation, cryptographic checks, or user-facing behavior. There is no security issue visible in the diff.
No security action required. This is a metrics-only change. Reviewers may optionally confirm that recorded amounts are not sensitive (they are on-chain UTXO amounts already visible to both parties) and that the new metric names do not leak private channel identifiers.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces four Kamon histograms (InteractiveTxInputs, InteractiveTxOutputs, InteractiveTxInputsPerSession, InteractiveTxOutputsPerSession) and two new tag keys (InputType, OutputType) plus a DiffSign tag. In InteractiveTxBuilder, after a complete interactive-tx session is validated, it records the number and satoshi amounts of shared/local/remote inputs and outputs. The existing validation logic, including the check that sharedOutputs.length > 1 is invalid, is left unchanged. No code paths are removed or weakened.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/channel/Monitoring.scalaeclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scalaInspect captured patch +23 / −0
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Monitoring.scala
index 863de3a..d0322f1 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Monitoring.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Monitoring.scala
@@ -33,6 +33,10 @@ object Monitoring {
val HtlcValueInFlightGlobal = Kamon.gauge("channels.htlc-value-in-flight-global", "Global HTLC value in flight across all channels")
val LocalFeeratePerByte = Kamon.histogram("channels.local-feerate-per-byte")
val RemoteFeeratePerByte = Kamon.histogram("channels.remote-feerate-per-byte")
+ val InteractiveTxInputs = Kamon.histogram("channels.interactive-tx.inputs", "Interactive tx inputs")
+ val InteractiveTxOutputs = Kamon.histogram("channels.interactive-tx.outputs", "Interactive tx outputs")
+ val InteractiveTxInputsPerSession = Kamon.histogram("channels.interactive-tx.inputs-per-session", "Interactive tx inputs per session")
+ val InteractiveTxOutputsPerSession = Kamon.histogram("channels.interactive-tx.outputs-per-session", "Interactive tx outputs per session")
val Splices = Kamon.histogram("channels.splices", "Splices")
val ProcessMessage = Kamon.timer("channels.messages-processed")
val HtlcDropped = Kamon.counter("channels.htlc-dropped")
@@ -91,7 +95,10 @@ object Monitoring {
val State = "state"
val CommitmentFormat = "commitment-format"
val SpliceType = "splice-type"
+ val InputType = "input-type"
+ val OutputType = "output-type"
val Reason = "reason"
+ val DiffSign = "sign"
object Events {
val Created = "created"
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
index b054ecc..ce0db2a 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala
@@ -783,6 +783,22 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
val localOutputs = session.localOutputs.collect { case o: Output.Local => o }
val remoteOutputs = session.remoteOutputs.collect { case o: Output.Remote => o }
+ // Global, not "per session". The goal is to measure the total number of inputs/outputs and distribution of amounts across all interactive-tx sessions.
+ sharedInputs.foreach(i => Monitoring.Metrics.InteractiveTxInputs.withTag(Monitoring.Tags.InputType, "shared").record(i.txOut.amount.toLong))
+ localInputs.foreach(i => Monitoring.Metrics.InteractiveTxInputs.withTag(Monitoring.Tags.InputType, "local").record(i.txOut.amount.toLong))
+ remoteInputs.foreach(i => Monitoring.Metrics.InteractiveTxInputs.withTag(Monitoring.Tags.InputType, "remote").record(i.txOut.amount.toLong))
+ sharedOutputs.foreach(o => Monitoring.Metrics.InteractiveTxOutputs.withTag(Monitoring.Tags.OutputType, "shared").record(o.amount.toLong))
+ localOutputs.foreach(o => Monitoring.Metrics.InteractiveTxOutputs.withTag(Monitoring.Tags.OutputType, "local").record(o.amount.toLong))
+ remoteOutputs.foreach(o => Monitoring.Metrics.InteractiveTxOutputs.withTag(Monitoring.Tags.OutputType, "remote").record(o.amount.toLong))
+
+ // We measure the number of each input/output type per session.
+ if (sharedInputs.nonEmpty) Monitoring.Metrics.InteractiveTxInputsPerSession.withTag(Monitoring.Tags.InputType, "shared").record(sharedInputs.size)
+ if (localInputs.nonEmpty) Monitoring.Metrics.InteractiveTxInputsPerSession.withTag(Monitoring.Tags.InputType, "local").record(localInputs.size)
+ if (remoteInputs.nonEmpty) Monitoring.Metrics.InteractiveTxInputsPerSession.withTag(Monitoring.Tags.InputType, "remote").record(remoteInputs.size)
+ if (sharedOutputs.nonEmpty) Monitoring.Metrics.InteractiveTxOutputsPerSession.withTag(Monitoring.Tags.OutputType, "shared").record(sharedOutputs.size)
+ if (localOutputs.nonEmpty) Monitoring.Metrics.InteractiveTxOutputsPerSession.withTag(Monitoring.Tags.OutputType, "local").record(localOutputs.size)
+ if (remoteOutputs.nonEmpty) Monitoring.Metrics.InteractiveTxOutputsPerSession.withTag(Monitoring.Tags.OutputType, "remote").record(remoteOutputs.size)
+
if (sharedOutputs.length > 1) {
log.warn("invalid interactive tx: funding script included multiple times")
return Left(InvalidCompleteInteractiveTx(fundingParams.channelId, "funding script included multiple times"))
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.