Skip to content

feat(maintainer): record SPV proof-submission metrics - #4190

Open
piotr-roslaniec wants to merge 29 commits into
chore/code-quality-followupsfrom
feat/maintainer-spv-metrics
Open

feat(maintainer): record SPV proof-submission metrics#4190
piotr-roslaniec wants to merge 29 commits into
chore/code-quality-followupsfrom
feat/maintainer-spv-metrics

Conversation

@piotr-roslaniec

@piotr-roslaniec piotr-roslaniec commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

What

Wires a clientinfo metrics registry + PerformanceMetrics into the maintainer command and threads a real metrics recorder through maintainer.Initialize into the SPV maintainer, so deposit-sweep and redemption proof-submission counters are actually recorded in production.

Why

The SPV proof submitters accept an optional metrics recorder, but the maintainer boot path never built one, so the recorder was always nil and the counters were dead in production. maintainer is the only command that runs the SPV maintainer, so these metrics were never observable.

Changes

  • cmd/maintainer.go: build the client-info registry + PerformanceMetrics (new initializeMaintainerMetrics helper), register baseline client-info + Ethereum sources, and pass the recorder to maintainer.Initialize. The recorder is derived from the client-info registry: when clientInfo.port=0, clientinfo.Initialize reports not-configured and the recorder stays a genuine nil interface (no typed-nil trap), disabling metrics recording.
  • config/category.go: add ClientInfo to MaintainerCategories so the client-info port is parsed for the maintainer command. Because clientInfo.port defaults to 9601 (the same shared flag the node start command uses), the maintainer now serves the client-info metrics/diagnostics endpoint on :9601 by default, consistent with start. Operators who do not want the endpoint can set clientInfo.port=0. See the operator impact note below.
  • pkg/maintainer/maintainer.go: thread spv.MetricsRecorder through Initialize into spv.Initialize.
  • pkg/maintainer/spv: add an exported MetricsRecorder interface; store it on spvMaintainer; extend the transactionProofSubmitter type; forward the recorder (instead of hardcoded nil) at the deposit-sweep and redemption submitters. Moving-funds and moved-funds-sweep submitters accept the recorder to satisfy the type but are not yet instrumented (no counters defined for them).
  • cmd/maintainercli.go: the two manual one-off proof-submit subcommands pass nil (no registry in that path).
  • docs/performance-metrics.adoc: document the six SPV proof-submission counters that this change makes observable in production.

Release note / operator impact

The maintainer command now exposes the client-info metrics/diagnostics HTTP endpoint on port 9601 (all interfaces) by default, matching the behavior of the node start command. The endpoint serves system/process metrics, client version, Ethereum connectivity/latest-block info, and the SPV proof-submission counters. It does not expose the Ethereum operator key or peer/operator identity. Operators who do not want the maintainer to open this endpoint should set clientInfo.port=0 in the ClientInfo config section (or via --clientInfo.port=0).

Tests

  • TestSubmitDepositSweepProof and TestSubmitRedemptionProof thread a real recorder and assert the attempt/success/failure counters fire on the success path.
  • TestSubmitDepositSweepProofRecordsFailureMetrics / TestSubmitRedemptionProofRecordsFailureMetrics cover the attempt+failure counters on the early zero-confirmations reject path.
  • TestSubmitDepositSweepProofRecordsAssembleFailureMetrics / TestSubmitRedemptionProofRecordsAssembleFailureMetrics cover the failed-counter increment on the SPV-proof-assembly error branch.
  • TestInitializeMaintainerMetricsDisabledWhenPortUnset guards the metrics on/off gate: a 0 client-info port yields a nil recorder.

Base / stacking

Based on chore/code-quality-followups (#4187), not main: the param-based recorder seam this PR fills only exists after that PR removed the global metrics singleton. Once #4187 merges, this branch should be rebased --onto main and becomes a standalone change.

…ments

Extract registerAllMetrics into per-type helpers (counters, wallet actions,
histograms, gauges) to isolate responsibilities, document the two-phase
map-populate-then-observe concurrency invariant once per helper, remove
field-group comments that restated field names, and correct the stale
system-metrics ticker comment (60s).
…mments

The coordinationFailed variable was only ever set true in branches that
return immediately, so the success-metrics guard was always taken; remove
the variable and simplify the guard. Also drop track-narration comments in
coordination_window_metrics.go that restated the following line.
- add named DepositKey type replacing the anonymous struct used for
  DepositSweepProposal.DepositsKeys across tbtc, tbtcpg and ethereum
- extract movingFundsSafetyMarginChain interface shared by
  ValidateMovingFundsSafetyMargin and isWalletPendingMovingFundsTarget
- switch ParseWalletActionType on WalletActionType iota constants
- collapse three identical frequency-window guards into a single guard
- EstimateDepositsSweepFee wraps the real error (was formatting the
  zero-valued sweepMaxSize) when GetDepositSweepMaxSize fails
- sync_machine wraps the WaitForBlockHeight error with %w so callers can
  inspect the root cause
- rename fnLogger to taskLogger to match the established logger naming
- fix two Chain interface doc comments to start with the method name
- correct the tools.go comment to describe indirect-dependency pinning
Wrap the finalSigningGroup error with %w so callers can inspect the
underlying cause instead of only the outer message.
- add and register clientinfo deposit-sweep proof-submission metric
  constants, mirroring the redemption ones, and replace the raw metric
  name strings in the SPV maintainer with them
- remove the getGlobalMetricsRecorder passthrough and call
  getMetricsRecorder directly
- trim variable comments that restated the variable names in
  parseDepositSweepTransactionInputs, keeping the vault constraint note
Rename the minority marshalling.go files to the majority marshaling
spelling for consistency across packages (git mv, no code changes).
The movingFundsSafetyMarginChain interface was inserted between the
function's doc comment and its declaration, detaching the doc. Move the
interface above the doc comment so it attaches again.
All five pinned modules are direct requires in go.mod, not indirect;
describe them by what they actually are (build-time-only).
- declare loop index with var i int instead of var i = 0 in chain.Addresses.String
- use the any alias instead of interface{} for the requestWithRetry type parameter
Add non-integration unit tests for GetBlockNumberByTimestamp and closerBlock,
which previously had coverage only under a skippable integration test. The
tests use a lightweight in-memory client to exercise the backward/forward
search loops and the closer-block tie-breaking.
The three-method metrics-recorder interface was declared inline in many
places across the package. Introduce a named fullMetricsRecorder interface
(MetricsRecorder plus SetGauge) and use it at those sites. The transport
keeps its narrower two-method MetricsRecorder contract.
EstimateMovingFundsFee and EstimateMovedFundsSweepFee shared an identical
virtual-size, fee-estimate, and cap-check block. Extract it into
estimateCappedFee, parameterized by the size estimator, the cap, and the
fee-too-high error to return.
…cs singleton

- extract unprovenSearchStartBlock and collectUnprovenWalletTransactions,
  shared by the four getUnproven*Transactions functions
- remove the package-level global metrics recorder and its setter/getter,
  which were never wired in production and always resolved to nil; the proof
  submission functions retain their metricsRecorder parameter as the DI seam
JoinDKGIfEligible and GenerateRelayEntry logged a SetFilter failure and then
launched protocol goroutines on an unfiltered broadcast channel, accepting
messages from operators outside the selected group. Abort on the failure
instead, matching the fail-closed behavior already used by the tbtc node.
The receivedQualifiedSharesT (t_ji) map on the member struct was written and
deleted on the production path but never read there; only tests consumed it.
Remove it from the struct and keep only receivedQualifiedSharesS (s_ji), which
is the actual reconstruction state. The share-count assertions now rely on the
S map (populated identically), and the accusation tests obtain the t_ji shares
from a value returned by the group-initialization helper.
The follower-routine coordination test slept a fixed second hoping the
receiver had registered its broadcast channel handler before the sender
started publishing. Wrap the follower channel so the sender waits for the
actual Recv registration, removing the timing assumption.
cmd.start wires the performance metrics recorder into the provider via a
structural type assertion against an anonymous interface. Naming that
parameter fullMetricsRecorder made the assertion no longer match the provider
(a defined type differs from an identical anonymous interface), so metrics
were silently no longer wired. Restore the anonymous parameter and add a test
that pins this wiring contract.
Directly exercise collectUnprovenWalletTransactions, pinning the
stop-at-first-match branch in both directions and the chain and
predicate error paths that were previously only reached indirectly.
Both JoinDKGIfEligible and GenerateRelayEntry installed the membership
filter and aborted on failure with identical logic. Extract it into
setBroadcastChannelFilter so the fail-closed contract lives in one place
and can be exercised directly.
Assert setBroadcastChannelFilter surfaces the SetFilter error so callers
abort instead of proceeding on an unfiltered channel that would accept
messages from operators outside the group.
unprovenSearchStartBlock returned currentBlock - historyDepth without
guarding the subtraction. On short chains where historyDepth exceeds the
current tip the unsigned subtraction wraps to a near-maximum block number,
silently changing the search range. Clamp to the genesis block instead.

This preserves behavior on mainnet, where the tip always dwarfs the
configured history depth.
The existing timestamp-search table uses a 12s block spacing, below the
13s averageBlockTime the algorithm assumes, so the initial backward jump
always lands at or after the target and the forward-walk branch never
runs. Add a case with 15s spacing so the backward jump overshoots below
the target and the forward loop is exercised.
Replace the hand-rolled blockOutOfRangeError struct with a plain
errors.New sentinel; it was only ever used as an opaque marker error.
The old comment block described the pre-refactor behavior (storing t_ji
on the member) that no longer holds, named the wrong function, and
carried a typo. Keep only the accurate block.
The SPV proof submitters accept an optional metrics recorder, but the
maintainer command never built one, so proof-submission counters were
never recorded in production (the recorder was always passed as nil).

Wire a clientinfo registry and PerformanceMetrics into the maintainer
boot path and thread the recorder through maintainer.Initialize into the
SPV maintainer, replacing the hardcoded nil at the deposit sweep and
redemption submitters. Add ClientInfo to the maintainer config
categories so the metrics endpoint port is parsed.

Moving funds and moved funds sweep submitters accept the recorder to
satisfy the submitter type but are not yet instrumented, as those proof
submissions have no metrics counters defined.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f33d298-4a2f-46c8-a9d7-5c5134dff16b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/maintainer-spv-metrics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ilure

Close review gaps on the SPV proof-submission metrics change:

- assert the redemption success-path counters; the success test
  previously passed a nil recorder and asserted nothing, so a
  removed/misplaced success increment would go undetected.
- cover the assemble-error failed-counter branch for both the deposit
  sweep and redemption provers (a realistic production failure mode);
  only the early zero-confirmations reject was asserted before. The
  on-chain-submit branch shares the same guard idiom but the local chain
  double cannot be forced to fail that call.
- add a cmd test for initializeMaintainerMetrics: a 0 client-info port
  yields a nil recorder, guarding the sole production metrics on/off gate
  against an inverted condition.
…ntract

- list the six SPV proof-submission counters in performance-metrics.adoc;
  this change is what makes them observable in production.
- reframe the MetricsRecorder doc as an API contract (callers must treat
  nil as "metrics off" and guard every call) instead of the rot-prone
  "all call sites guard against it" status claim.
lionakhnazarov
lionakhnazarov previously approved these changes Jul 23, 2026

@lionakhnazarov lionakhnazarov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@piotr-roslaniec
piotr-roslaniec dismissed lionakhnazarov’s stale review August 7, 2026 19:30

The merge-base changed after approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants