Skip to content

fix: prune retained attestation state#408

Open
randygrok wants to merge 3 commits into
mainfrom
jgimeno/issue-399-attestation-retention
Open

fix: prune retained attestation state#408
randygrok wants to merge 3 commits into
mainfrom
jgimeno/issue-399-attestation-retention

Conversation

@randygrok

@randygrok randygrok commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Enables epoch-end pruning for retained attestation state, including per-height signatures.
  • Centralizes the attestation retention boundary used by pruning and MsgAttest height validation.
  • Adds tests for full attestation-state pruning, EndBlocker pruning, and retention-boundary inclusivity.
  • Documents attestation state retention behavior in the network module README.

Validation

  • go test -count=1 ./modules/network/keeper

Summary by CodeRabbit

  • New Features

    • Automatic pruning of old attestation data at epoch boundaries based on a configurable retention window.
    • Attestation submissions now rejected if below the retention window to prevent invalid state recreation.
  • Documentation

    • Enhanced network module documentation describing attestation state retention policy and pruning behavior at epoch boundaries.

@randygrok randygrok requested a review from a team as a code owner June 10, 2026 16:11
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@randygrok, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 26 minutes and 38 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2c3b239-bef4-4a2d-bf5d-190d031e718c

📥 Commits

Reviewing files that changed from the base of the PR and between 0d47359 and e5b5e5e.

📒 Files selected for processing (2)
  • modules/network/keeper/msg_server_test.go
  • modules/network/types/params.go
📝 Walkthrough

Walkthrough

This PR implements attestation state retention and pruning for the network module. A new retention boundary abstraction computes which epochs and block heights should be retained based on the PruneAfter parameter. The pruning logic is refactored to use this boundary to clear old attestation bitmaps, signatures, and related state at epoch boundaries. Attestation messages are validated against the boundary to reject writes below the retention window. The implementation is documented and tested.

Changes

Attestation State Retention and Pruning

Layer / File(s) Summary
Retention boundary abstraction and computation
modules/network/keeper/keeper.go
Introduces attestationRetentionBoundary struct and helper method to compute the first retained epoch and block height from PruneAfter and EpochLength, with a guard for disabled pruning.
Pruning implementation with boundary-based cleanup
modules/network/keeper/keeper.go
Refactors PruneOldBitmaps to compute and use the retention boundary, clearing attestation bitmaps, stored info, epoch bitmaps, and signature entries for heights below the boundary.
Attestation message validation with retention enforcement
modules/network/keeper/msg_server.go
Updates Attest handler to compute and apply the retention boundary, rejecting attestations below the first retained height with boundary-aware error messages.
Epoch-end pruning orchestration
modules/network/keeper/abci.go
Integrates PruneOldBitmaps into processEpochEnd, replacing TODO placeholder code with active pruning before validator index rebuild.
Documentation and test coverage
modules/network/README.md, modules/network/keeper/msg_server_test.go
Adds README section documenting the attestation retention policy and EventSoftCheckpoint emission; introduces three tests validating boundary computation, pruning behavior, and end-blocker integration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • evstack/ev-abci#359: Modifies the same Attest message validation in msg_server.go to enforce the attestation retention-window lower height bound and adds corresponding test coverage.

Suggested reviewers

  • julienrbrt

Poem

🐰 The rabbit hops through epochs past,
Old bitmaps pruned, retention steadfast,
Boundaries drawn with careful might,
Attestations validated right! 🌙✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Prune retained attestation state' directly and specifically summarizes the main change: implementing epoch-end pruning for retained attestation state across the keeper implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jgimeno/issue-399-attestation-retention

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 and usage tips.

@randygrok randygrok changed the title Prune retained attestation state fix: prune retained attestation state Jun 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/network/keeper/msg_server.go (1)

55-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative attestation heights before they become unprunable state.

When currentEpoch <= PruneAfter, boundary is nil, so an authorized attester can still write msg.Height < 0. Downstream, PruneOldBitmaps only clears AttestationBitmap and StoredAttestationInfo over [0, firstRetainedHeight), so those negative-height entries never age out.

Suggested fix
 	currentHeight := ctx.BlockHeight()
 	maxFutureHeight := currentHeight + 1
+	if msg.Height < 0 {
+		return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height must be non-negative: %d", msg.Height)
+	}
 	if msg.Height > maxFutureHeight {
 		return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d exceeds max allowed height %d", msg.Height, maxFutureHeight)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/network/keeper/msg_server.go` around lines 55 - 69, Add an explicit
rejection for negative attestation heights: before using
attestationRetentionBoundary(k.attestationRetentionBoundary) or calling
boundary.prunesHeight, check if msg.Height < 0 and return an
sdkerrors.ErrInvalidRequest explaining the height is negative; this ensures when
currentEpoch <= PruneAfter (boundary == nil) callers cannot write negative
heights that PruneOldBitmaps (which only trims [0, firstRetainedHeight)) will
never remove from AttestationBitmap/StoredAttestationInfo. Use the same error
wrapping style as the existing checks so the check sits alongside the current
maxFutureHeight and boundary.prunesHeight validations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/network/keeper/msg_server.go`:
- Around line 67-69: The Attest validation uses
k.attestationRetentionBoundary(ctx, k.GetCurrentEpoch(ctx)) causing the write
cutoff to advance at the start of a new epoch before pruning runs in
processEpochEnd; change the boundary calculation to use the last
completed/pruned epoch (e.g., compute lastCompletedEpoch :=
k.GetCurrentEpoch(ctx) - 1 or only advance when ctx.BlockHeight() is the epoch
boundary) and pass that into attestationRetentionBoundary so Attest rejects only
heights actually pruned by PruneOldBitmaps; update attestationRetentionBoundary
call sites (including Attest) accordingly and add a regression test exercising
EpochLength > 1 demonstrating block-start rejects do not occur until the
epoch-end prune has run.

In `@modules/network/README.md`:
- Line 130: The README's checkpoint event description is out of sync with the
actual EventSoftCheckpoint emitted by emitCheckpointHashes: update the
documentation text to list the real event fields (height, validator_hash,
commit_hash, soft_confirmed) and remove/replace references to block_hash,
attester bitmap, and voting power, or alternatively change emitCheckpointHashes
(EventSoftCheckpoint emission in abci.go) to emit the documented fields; refer
to the function emitCheckpointHashes and the EventSoftCheckpoint usage to ensure
the docs exactly match the emitted event structure.

---

Outside diff comments:
In `@modules/network/keeper/msg_server.go`:
- Around line 55-69: Add an explicit rejection for negative attestation heights:
before using attestationRetentionBoundary(k.attestationRetentionBoundary) or
calling boundary.prunesHeight, check if msg.Height < 0 and return an
sdkerrors.ErrInvalidRequest explaining the height is negative; this ensures when
currentEpoch <= PruneAfter (boundary == nil) callers cannot write negative
heights that PruneOldBitmaps (which only trims [0, firstRetainedHeight)) will
never remove from AttestationBitmap/StoredAttestationInfo. Use the same error
wrapping style as the existing checks so the check sits alongside the current
maxFutureHeight and boundary.prunesHeight validations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f9cade7-fe32-4715-a7ef-6810375c3f74

📥 Commits

Reviewing files that changed from the base of the PR and between 2deaf94 and 0d47359.

📒 Files selected for processing (5)
  • modules/network/README.md
  • modules/network/keeper/abci.go
  • modules/network/keeper/keeper.go
  • modules/network/keeper/msg_server.go
  • modules/network/keeper/msg_server_test.go

Comment on lines +67 to +69
boundary := k.attestationRetentionBoundary(ctx, k.GetCurrentEpoch(ctx))
if boundary != nil && boundary.prunesHeight(msg.Height) {
return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d is below retention window (min %d)", msg.Height, boundary.firstRetainedHeight)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The write cutoff advances a full epoch before pruning does.

Attest uses GetCurrentEpoch() on every block, but pruning only runs in processEpochEnd() on the last block of the epoch. With EpochLength > 1, the first block of a new epoch starts rejecting heights that are still retained for the rest of that epoch. For example, with EpochLength=10 and PruneAfter=2, block 50 rejects height 25 (min=30), but PruneOldBitmaps will not delete height 25 until block 59.

Base the validation boundary on the last completed pruning epoch, or only advance it on epoch-boundary blocks, and add a regression test with EpochLength > 1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/network/keeper/msg_server.go` around lines 67 - 69, The Attest
validation uses k.attestationRetentionBoundary(ctx, k.GetCurrentEpoch(ctx))
causing the write cutoff to advance at the start of a new epoch before pruning
runs in processEpochEnd; change the boundary calculation to use the last
completed/pruned epoch (e.g., compute lastCompletedEpoch :=
k.GetCurrentEpoch(ctx) - 1 or only advance when ctx.BlockHeight() is the epoch
boundary) and pass that into attestationRetentionBoundary so Attest rejects only
heights actually pruned by PruneOldBitmaps; update attestationRetentionBoundary
call sites (including Attest) accordingly and add a regression test exercising
EpochLength > 1 demonstrating block-start rejects do not occur until the
epoch-end prune has run.

Comment thread modules/network/README.md
@@ -128,7 +128,20 @@ The module's `EndBlocker` is executed at the end of every block and performs the

1. **Quorum Evaluation**: It iterates through recent blocks that have received attestations and checks if the cumulative voting power of the attesters has reached the required quorum.
2. **Checkpoint Emission**: If quorum is met for a block, it emits an `EventSoftCheckpoint` with the `height`, `block_hash`, a bitmap of the participating attesters, and the total voting power.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Event field documentation doesn't match implementation.

The documentation claims the checkpoint event includes block_hash, a bitmap of participating attesters, and total voting power. However, the actual event emitted by emitCheckpointHashes (abci.go:131-148) includes different fields:

  • Actual: height, validator_hash, commit_hash, soft_confirmed
  • Documented: height, block_hash, bitmap, voting power

This mismatch will mislead consumers trying to parse checkpoint events.

📝 Proposed fix to align documentation with actual event
-2.  **Checkpoint Emission**: If quorum is met for a block, it emits an `EventSoftCheckpoint` with the `height`, `block_hash`, a bitmap of the participating attesters, and the total voting power.
+2.  **Checkpoint Emission**: If quorum is met for a block, it emits a `checkpoint` event with the `height`, `validator_hash`, `commit_hash`, and `soft_confirmed` status.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
2. **Checkpoint Emission**: If quorum is met for a block, it emits an `EventSoftCheckpoint` with the `height`, `block_hash`, a bitmap of the participating attesters, and the total voting power.
2. **Checkpoint Emission**: If quorum is met for a block, it emits a `checkpoint` event with the `height`, `validator_hash`, `commit_hash`, and `soft_confirmed` status.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 130-130: Spaces after list markers
Expected: 1; Actual: 2

(MD030, list-marker-space)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/network/README.md` at line 130, The README's checkpoint event
description is out of sync with the actual EventSoftCheckpoint emitted by
emitCheckpointHashes: update the documentation text to list the real event
fields (height, validator_hash, commit_hash, soft_confirmed) and remove/replace
references to block_hash, attester bitmap, and voting power, or alternatively
change emitCheckpointHashes (EventSoftCheckpoint emission in abci.go) to emit
the documented fields; refer to the function emitCheckpointHashes and the
EventSoftCheckpoint usage to ensure the docs exactly match the emitted event
structure.

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.

1 participant