Skip to content

x/bor: remove unused MsgBackfillSpans plumbing#598

Open
lucca30 wants to merge 3 commits into
developfrom
lmartins/backfill-cleanup
Open

x/bor: remove unused MsgBackfillSpans plumbing#598
lucca30 wants to merge 3 commits into
developfrom
lmartins/backfill-cleanup

Conversation

@lucca30

@lucca30 lucca30 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the side-tx response-slot censorship vector around MsgBackfillSpans and removes the dead scaffolding around it that becomes orphan once the dispatch is gone, while preserving the msg-server handler so historical txs still replay correctly. Targeting v0.9.0.

The side handler was a constant VOTE_NO and the post handler was therefore unreachable, but every msg with a registered side handler reserves one of the 50 maxSideTxResponsesCount slots in ExtendVoteHandler regardless of the vote it returns. A block heavy with no-op MsgBackfillSpans txs could crowd the side-tx response budget and delay responses for legitimate side-tx-bearing msgs (MsgCheckpoint, MsgEventRecord, validator-join, ...) later in the same block. Removing the dispatch drops the registration and removes that pressure source.

The msg-server handler is intentionally retained as validation-only (no state writes) so the 2 historical Amoy txs (see audit below) replay with the same ExecTxResult.

Side benefit, called out by @mardizzone on the equivalent private draft.

Touch points

Slot-censorship fix:

  • x/bor/keeper/side_msg_server.go — drop FillMissingSpansMsgTypeURL dispatch from SideTxHandler and PostTxHandler, drop the now-orphan SideHandleMsgBackfillSpans / PostHandleMsgBackfillSpans functions, drop the FillMissingSpansMsgTypeURL constant.

Orphan cleanup (post slot-fix):

  • x/bor/types/util.go — drop GenerateBorCommittedSpans (only the post handler used it).
  • x/bor/types/msg.go — drop MsgBackfillSpans.Type() / .Route() (deprecated in modern cosmos-sdk).
  • x/bor/types/events.go — drop EventTypeBackfillSpans, AttributesKeyLatestSpanId, AttributesKeyLatestBorSpanId (only the post handler emitted them).
  • metrics/api/bor.go — drop PostHandleMsgBackfillSpansMethod.
  • x/bor/keeper/side_msg_server.go:197 — rewrite the stale "we need to backfill before proposing a new span" comment that referenced the now-removed recovery path.
  • *_test.go — drop tests pinning the removed APIs (TestGenerateBorCommittedSpans, TestMsgBackfillSpans_Type, TestEventTypeBackfillSpans, TestAttributesKeyLatest*).

Retained: BackfillSpans rpc method, msg-server handler, BackfillSpansMethod metric, ErrInvalidLastBorSpanID, CalcCurrentBorSpanId, MsgBackfillSpans + MsgBackfillSpansResponse proto messages, and corresponding tests. See "Replay safety audit" below.

Replay safety audit

The pre-PR msg-server handler ran with no state writes but consumed ~81k gas via 2-3 DB reads (GetLastSpan, GetLastMilestone, GetSpan). Removing it would replace historical ExecTxResult{Code=0, GasUsed=81753} with ExecTxResult{Code=ErrUnknownRequest, GasUsed=0} during replay, causing LastResultsHash mismatch and halting sync-from-genesis on any chain where such a tx ever landed in a block.

Audited via CometBFT tx_search for message.action='/heimdallv2.bor.MsgBackfillSpans':

  • Amoy (pos-amoy-pbss-archive, tx_index: on, blocks 8788501..35935529): 2 txs found — heights 8790181 (AE3D46F7…) and 8790209 (2FC9428B…), both Code=0, gas_used=81753, submitted by 0x93feed2cc3d58c2b1bb62ce63125fa7fcaae7177.
  • Mainnet: no co-located heimdalld with tx_index: on on our observation infra (all hosts run bor against the public heimdall API). Conservative assumption: behave as if mainnet has at least one such tx too.

Hence: keep the msg-server handler routable. The PR drops the dispatch (slot-fix) and the orphan post-handler-only scaffolding, not the handler itself.

Executed tests

  • go test ./x/bor/... ./app/... ./metrics/... -count=1 — 655 tests pass.
  • make proto-gen — clean.
  • TestABCI_StressTestWith100Blocks — exercises 5 txs per block across 5 distinct msg types (including the retained MsgBackfillSpans) over 100 blocks; passes.
  • Slot-censorship regression test (app/abci_backfill_spans_slot_test.go, ported from the private draft): 50 MsgBackfillSpans txs followed by a MsgCheckpoint yield exactly 1 SideTxResponse — for the checkpoint.

Rollout notes

  • Not consensus-affecting for live validators with state already past height 8790209 on Amoy. The dispatch removal changes vote-extension behavior only for new MsgBackfillSpans txs (none are expected; the msg has been a no-op since bc907770 always voted NO).
  • Sync-from-genesis on Amoy continues to work because the msg-server handler is retained and produces matching ExecTxResult for the 2 historical txs.
  • No operator-facing change. No coordinated upgrade beyond shipping the binary in v0.9.0.

MsgBackfillSpans has no consumer: its side handler is a constant VOTE_NO
no-op, the post handler is unreachable, and the msg-server handler only
runs input validation and returns success without state writes. The
feature was intentionally disabled in bc90777… and never re-wired.

Keeping the registration around makes ExtendVoteHandler walk these txs and
allocate one of the 50 maxSideTxResponsesCount slots per registered msg
regardless of vote, so a block packed with these admissible no-ops can
delay or skip the sideTx response of legitimate side-tx-bearing messages
later in the block. Dropping the registration is enough to close that
behavior — but since the entire plumbing is dead, this PR removes it
end-to-end (proto + generated code + msg-server + side handler + post
handler + metrics + tests + the case in the ABCI stress test). When the
feature is genuinely needed it can be reintroduced with a real side handler
and post handler.

Touch points:
- proto/heimdallv2/bor/tx.proto: drop BackfillSpans rpc + messages
- api/* + x/bor/types/tx.pb.go: regenerated via `make proto-gen`
- x/bor/keeper/msg_server.go: drop BackfillSpans handler
- x/bor/keeper/side_msg_server.go: drop SideHandle/PostHandle + dispatch
  cases + FillMissingSpansMsgTypeURL
- x/bor/types/{msg,events}.go: drop Type/Route/event constants
- metrics/api/bor.go: drop BackfillSpansMethod and PostHandleMsgBackfillSpansMethod
- *_test.go: drop tests pinning the removed APIs; rebalance the abci
  stress test from 5-way to 4-way msg cycling
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 45.2%. Comparing base (346a323) to head (747924d).
⚠️ Report is 37 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop    #598   +/-   ##
=======================================
  Coverage     45.2%   45.2%           
=======================================
  Files          172     172           
  Lines        18663   18582   -81     
=======================================
- Hits          8437    8407   -30     
+ Misses        9019    8967   -52     
- Partials      1207    1208    +1     
Files with missing lines Coverage Δ
x/bor/keeper/side_msg_server.go 68.3% <ø> (+8.2%) ⬆️
x/bor/types/msg.go 100.0% <ø> (ø)
x/bor/types/util.go 91.2% <ø> (-2.2%) ⬇️

... and 3 files with indirect coverage changes

Files with missing lines Coverage Δ
x/bor/keeper/side_msg_server.go 68.3% <ø> (+8.2%) ⬆️
x/bor/types/msg.go 100.0% <ø> (ø)
x/bor/types/util.go 91.2% <ø> (-2.2%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lucca30

lucca30 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude review once

@lucca30 lucca30 marked this pull request as ready for review May 28, 2026 13:46

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Comment thread proto/heimdallv2/bor/tx.proto
Comment thread x/bor/keeper/side_msg_server.go
Address review feedback on #598:

- Re-add MsgBackfillSpans (no rpc, no handlers) to proto and register
  it explicitly in codec.go so historical blocks containing a
  MsgBackfillSpans tx remain decodable post-upgrade. Removing the rpc
  method also removed the type from the interface registry via
  RegisterMsgServiceDesc, which would have broken TxDecode during
  replay for any chain whose history included such a tx.

- Drop orphan helpers/symbols left behind by the original cleanup:
  GenerateBorCommittedSpans and CalcCurrentBorSpanId (+ tests),
  ErrInvalidLastBorSpanID (+ test entries), and the stale "we need to
  backfill before proposing a new span" comment in the side handler --
  all unreachable now that the handlers are gone.
@lucca30

lucca30 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude Review again

Comment thread x/bor/types/codec.go
… 2 historical txs)

Audit of mainnet + Amoy heimdall RPC tx_search for /heimdallv2.bor.MsgBackfillSpans
found 2 historical txs on Amoy: heights 8790181 (hash AE3D46F7..) and 8790209 (hash
2FC9428B..), both Code=0 with gas_used=81753. Removing the msg-server handler in the
prior commits would have made these txs unroutable post-upgrade -- msgServiceRouter
would return nil, runTx would short-circuit with ErrUnknownRequest+gas_used=0, and
the resulting ExecTxResult divergence would halt replay via LastResultsHash mismatch
for any node syncing from before height 8790181 on Amoy.

Scope-reduce: keep the side-tx response-slot censorship fix (the real win from this
PR, also tracked in the equivalent private draft) but restore the bits required for
historical replay:

- Restore `BackfillSpans` rpc method + msg-server handler (validation only, no state
  writes -- exactly matches pre-PR ExecTxResult for the 2 historical Amoy txs).
- Restore BackfillSpansMethod metric, ErrInvalidLastBorSpanID, CalcCurrentBorSpanId
  + its test (all called by the restored handler).
- Restore msg_server_test.go (TestBackfillSpans), errors_test.go entries, and the
  abci stress test's 5-way msg cycling (now exercises a routable msg again).
- Drop the explicit RegisterImplementations(&MsgBackfillSpans{}) from codec.go --
  redundant now that the rpc is back in _Msg_serviceDesc (RegisterMsgServiceDesc
  handles it).

Stays removed (orphan after the slot-censorship fix, unreachable in production):

- SideHandleMsgBackfillSpans + PostHandleMsgBackfillSpans funcs + their dispatch
  in SideTxHandler/PostTxHandler (this IS the slot-censorship fix).
- FillMissingSpansMsgTypeURL const, GenerateBorCommittedSpans helper (only post
  handler used them), PostHandleMsgBackfillSpansMethod metric.
- MsgBackfillSpans.Type() / .Route() methods + their test (deprecated in modern
  cosmos-sdk, not required for routing).
- EventTypeBackfillSpans + AttributesKeyLatest{Span,BorSpan}Id constants + tests
  (only PostHandleMsgBackfillSpans emitted them).
- Stale "we need to backfill before proposing a new span" comment in side handler.
@sonarqubecloud

Copy link
Copy Markdown

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for running the on-chain audit and going with full restoration of the msg-server handler — that's the right call for preserving ExecTxResult on the 2 known Amoy historical txs. Deferring final sign-off to a human reviewer since this still removes side-handler registration for a msg type with on-chain history and touches consensus-critical side-tx wiring.

Extended reasoning...

Overview

PR removes the side handler / post handler / dispatch for MsgBackfillSpans (the actual goal: closing the side-tx response-slot censorship vector) while keeping the rpc method, msg-server handler, codec registration, and supporting helpers (CalcCurrentBorSpanId, ErrInvalidLastBorSpanID) intact. Two prior review rounds flagged (1) TxDecode failure on replay if the proto type went away entirely, and (2) ExecTxResult divergence if the msg-server handler went away while leaving codec registration. Author addressed both by auditing Amoy archive (tx_search on /heimdallv2.bor.MsgBackfillSpans returned txs at heights 8790181 and 8790209 with code=0, gas_used=81753) and restoring the full handler.

Security risks

No direct security risk in the diff itself — it's a removal, and the remaining wiring keeps the msg-server handler doing identical work (validation + DB reads, no state writes), so the per-tx ExecTxResult shape is preserved for the historical txs. tallyVotes in app/vote_ext_utils.go tallies votes from extVoteInfo without consulting GetSideHandler, so historical VOTE_NO votes in old VEs will still tally the same way; the post handler being absent doesn't matter because the tx never reached the approved set in the first place. SideTxDecorator only enforces ≤1 side handler per tx, so 0 side handlers is fine at ante time.

Level of scrutiny

This is consensus-critical territory: side_msg_server.go changes, removing side-handler registration for a msg type with confirmed on-chain history, and a per-tx ExecTxResult invariant that depends on the msg-server handler continuing to do exactly the same work. Per the consensus-critical and hardfork-rollout rules, even surgical removals here warrant expert human sign-off — particularly someone who can confirm the audit also covered any production devnet that ran v0.6.0+ besides Amoy (the rollout note explicitly couldn't verify mainnet from the available infra).

Other factors

Bug hunting system found no new bugs. Both of my prior inline-comment bugs are marked resolved with substantive fixes. SonarQube clean, Codecov reports all modified lines covered. CI green. Author's responses have been technically detailed and correct (including the catch that a stub no-op handler wouldn't replicate the original gas_used=81753 from real DB reads). This is high-quality work that just happens to live in a part of the codebase where I should not be the one signing off.

@lucca30

lucca30 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@claude review once. So no other findings remaining? Ensure this is a non-breaking change and that it just remove this unused message while keeps retrocompatibility

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Backwards-compatibility looks preserved: msg-server handler, proto/codec registration, gRPC registration, and CalcCurrentBorSpanId are all retained so the 2 historical Amoy txs (heights 8790181, 8790209) replay with matching ExecTxResult; the only behavior change is dropping the side-handler dispatch (the intended slot-censorship fix, covered by the regression test). Deferring final approval to a human since this touches ABCI side-tx/vote-extension semantics and consensus replay paths.

Extended reasoning...

Overview

This PR closes a side-tx response-slot censorship vector around MsgBackfillSpans by removing its side-handler / post-handler dispatch entries in x/bor/keeper/side_msg_server.go, while retaining the msg-server handler, proto definitions, codec/gRPC registration, and the CalcCurrentBorSpanId helper so historical blocks replay with matching ExecTxResult. Orphan scaffolding that only served the removed post-handler is also cleaned up: GenerateBorCommittedSpans, PostHandleMsgBackfillSpansMethod metric, post-handler-only event constants (EventTypeBackfillSpans, AttributesKeyLatestSpanId, AttributesKeyLatestBorSpanId), and deprecated Type()/Route() methods. A stale comment in SideHandleMsgSpan was rewritten.

Security risks

The change closes a slot-censorship vector (a no-op MsgBackfillSpans previously reserved one of the 50 maxSideTxResponsesCount slots regardless of its constant VOTE_NO vote, allowing crowding of legitimate side-tx-bearing msgs in the same block). There is a regression test (app/abci_backfill_spans_slot_test.go) covering 50 MsgBackfillSpans + 1 MsgCheckpoint → exactly 1 SideTxResponse. No new attack surface is introduced — the retained msg-server handler is the same validation-only path it has been since bc907770.

Level of scrutiny

This is consensus-critical: the change alters side-tx response budgeting (vote-extension shape) and any historical MsgBackfillSpans tx must replay deterministically. The author conducted an Amoy archive audit (pos-amoy-pbss-archive, tx_index: on) and found 2 historical txs (heights 8790181, 8790209, both Code=0, gas_used=81753), then deliberately chose option (a) from my prior review — keeping the msg-server handler routable so Code and GasUsed (which depends on real DB reads GetLastSpan / GetLastMilestone / GetSpan) match on replay. A no-op stub would not have preserved GasUsed. Mainnet audit was not feasible from the team's infra, so a conservative assumption was adopted — appropriate for v0.9.0.

Other factors

  • Two prior detailed review cycles (codec.go:23, tx.proto:19, side_msg_server.go:32) identified real replay-safety bugs that the author addressed by reverting their initial "full removal" approach to the safer "keep handler routable + drop dispatch" approach.
  • Codecov shows coverage holding (45.2%), SonarQube quality gate passed, tests passing including TestABCI_StressTestWith100Blocks cycling 5 msg types including MsgBackfillSpans.
  • No bugs found by the bug hunting system this round.
  • I'm deferring rather than approving because the change touches ABCI side-tx handlers and vote-extension slot accounting — exactly the surface my consensus-critical review guidance flags as needing a human signoff. The work itself looks correct; this is about the policy that consensus-critical surfaces are not eligible for shadow-approval.

@marcello33

Copy link
Copy Markdown
Collaborator

Hey @lucca30 I have a doubt that this change would require a HF 😓
If you look at git history, you find

  • cccd9b94 @ 2025-05-31 — Bor lag detection + auto span rotation (backfill functional)
  • 9de3d893 @ 2025-06-03 — backfill span overlap handling (still writing spans)
  • d403fe73 @ 2025-07-08 — "Delete spans backfill"
  • bc907770 @ 2025-07-16 — "Always vote NO on spans backfill"
    Since backfill could vote YES and write spans once, I think this change is not backward compatible in case backfill was ever approved and wrote spans in production. Now, I can't remember exactly if we used the backfill, but I am leaning through a yes for both networks. Not sure how to verify tho (maybe we need non-pruned nodes to query).
    Can you please double check?
    Thanks

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions Bot added the Stale label Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants