Skip to content

Conversation

Eoous
Copy link
Contributor

@Eoous Eoous commented Apr 22, 2025

Add new store type DefaultPruningStore to prune storage.

Related: #2093

Summary by CodeRabbit

  • New Features
    • Introduced configurable pruning options for node storage to manage retention and deletion of block data.
    • Added command-line flags for pruning strategy, number of recent blocks to keep, and pruning operation frequency.
  • Enhancements
    • Enabled automated pruning of block data according to user-defined settings for improved storage management.
    • Integrated asynchronous pruning to optimize block data maintenance without impacting node performance.

Copy link
Contributor

coderabbitai bot commented Apr 22, 2025

Walkthrough

This update introduces a configurable pruning mechanism to the node's data storage system. It adds a PruningConfig struct to the node configuration, exposes new command-line flags for pruning strategy, and implements the logic for pruning stored block data based on these settings. The changes include new configuration defaults, new constants and struct definitions for pruning, and a new DefaultPruningStore that handles periodic deletion of old block data according to the chosen strategy. The store interfaces are extended to support block deletion and pruning operations, and the underlying store implementation is updated to allow deletion of block data by height. The node startup process is updated to normalize pruning configuration based on the selected strategy.

Changes

File(s) Change Summary
pkg/config/pruning_config.go New file: Defines pruning-related flags, constants, strategies, and the PruningConfig struct with defaults and a helper function.
pkg/config/config.go Adds Pruning field to NodeConfig and registers new pruning flags in AddFlags; updates validation to include pruning config.
pkg/config/defaults.go Adds default pruning configuration to the default node config.
pkg/store/pruning.go New file: Implements DefaultPruningStore with pruning logic for block data deletion based on config.
pkg/store/store.go Adds DeleteBlockData method for removing block data by height; improves error messages in batch creation.
pkg/store/types.go Adds DeleteBlockData to Store interface and introduces new PruningStore interface with PruneBlockData method.
pkg/cmd/run_node.go Updates ParseConfig function to normalize the pruning config from the pruning strategy before validation.
node/full.go, node/light.go Changes store initialization to use NewDefaultPruningStore with pruning config instead of generic store; adds async pruner to full node.
block/async_store.go New file: Defines AsyncPruner for periodic asynchronous pruning of block data using the pruning store interface.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Node (CLI)
    participant Config
    participant AsyncPruningStore
    participant Store

    User->>Node (CLI): Start node with pruning flags
    Node (CLI)->>Config: Parse flags, build NodeConfig with PruningConfig
    Node (CLI)->>AsyncPruningStore: Initialize with Store and PruningConfig
    AsyncPruningStore->>Store: Delegates most storage operations

    loop On new block
        Node (CLI)->>AsyncPruningStore: SaveBlockData(header, data, sig)
        AsyncPruningStore->>AsyncPruningStore: Update latest block height
    end

    loop Periodic pruning interval
        AsyncPruningStore->>AsyncPruningStore: Trigger PruneBlockData(latestBlockHeight)
        AsyncPruningStore->>Store: DeleteBlockData(old heights)
    end
Loading

Suggested reviewers

  • tac0turtle
  • alpe

Poem

🐇 In data fields where blocks reside,
The pruning rabbit hops with pride.
Old chains trimmed with gentle care,
Keeping recent blocks to spare.
Flags set right, the store runs clean,
A tidy node, lean and keen! 🌿✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6541d0 and 086904a.

📒 Files selected for processing (6)
  • node/full.go (4 hunks)
  • node/light.go (1 hunks)
  • pkg/cmd/run_node.go (1 hunks)
  • pkg/config/config.go (3 hunks)
  • pkg/store/store.go (2 hunks)
  • pkg/store/types.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • node/light.go
  • pkg/cmd/run_node.go
  • pkg/store/types.go
  • pkg/store/store.go
  • node/full.go
  • pkg/config/config.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test / Run Unit Tests
  • GitHub Check: test / Build Rollkit EVM Single Docker Image
  • GitHub Check: Summary
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@RollkitBot RollkitBot requested review from a team, tuxcanfly, tzdybal and gupadhyaya and removed request for a team April 22, 2025 11:21
@Eoous Eoous mentioned this pull request Apr 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (8)
pkg/config/pruning_config.go (3)

10-11: Fix typo in comment: "offen" should be "often"

There's a typo in the comment for FlagPruningInterval that should be corrected.

-	// FlagPruningInterval is a flag for specifying how offen prune blocks store
+	// FlagPruningInterval is a flag for specifying how often to prune blocks store

49-49: Fix typo in comment: "offen" should be "often"

Similar to the earlier flag comment, there's a typo in the PruningConfig struct field comment.

-	Interval   uint64 `mapstructure:"interval" yaml:"interval" comment:"how offen the pruning process should run"`
+	Interval   uint64 `mapstructure:"interval" yaml:"interval" comment:"how often the pruning process should run"`

45-54: Consider adding validation for configuration values

There's no validation for the values in the PruningConfig struct. It might be useful to add a validation method to ensure that:

  1. The strategy is one of the predefined constants
  2. KeepRecent is at least 2 for non-none strategies
  3. Interval is greater than 0

This would prevent misconfigurations that could lead to data loss or performance issues.

pkg/store/pruning.go (5)

67-70: Incorrect comment for GetSignatureByHash method

The method comment doesn't match the function's behavior. It mentions returning a signature for a block at a given height, but the method actually returns a signature by block hash.

-// GetSignatureByHash returns signature for a block at given height, or error if it's not found in Store.
+// GetSignatureByHash returns signature for a block with given block header hash, or error if it's not found in Store.

72-75: Incorrect comment for GetSignature method

The method comment doesn't match the function's behavior. It mentions returning a signature for a block with a given hash, but the method actually returns a signature by block height.

-// GetSignature returns signature for a block with given block header hash, or error if it's not found in Store.
+// GetSignature returns signature for a block at given height, or error if it's not found in Store.

115-118: Consider logging pruning operations

Adding logging when pruning operations occur would help with debugging and monitoring the pruning process. Consider adding log statements that indicate when pruning is skipped and when it's performed, including how many blocks are pruned.


100-132: Consider implementing batch deletion for better performance

The current implementation deletes blocks one by one in a loop, which might not be efficient for large pruning operations. Consider implementing a batch deletion mechanism if supported by the underlying store, or at least adding a mechanism to limit the number of blocks pruned in a single operation to avoid long-running transactions.


124-129: Add recovery mechanism for partially completed pruning

If pruning fails partway through (e.g., due to a crash or error), there's no mechanism to recover or continue from where it left off. Consider adding a way to track pruning progress, so it can be resumed if interrupted.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2f92e and 6b0cc41.

📒 Files selected for processing (6)
  • pkg/config/config.go (2 hunks)
  • pkg/config/defaults.go (1 hunks)
  • pkg/config/pruning_config.go (1 hunks)
  • pkg/store/pruning.go (1 hunks)
  • pkg/store/store.go (2 hunks)
  • pkg/store/types.go (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
pkg/config/defaults.go (1)
pkg/config/pruning_config.go (1)
  • PruningConfigDefault (27-31)
pkg/config/config.go (2)
pkg/config/pruning_config.go (4)
  • PruningConfig (45-54)
  • FlagPruningStrategy (7-7)
  • FlagPruningKeepRecent (9-9)
  • FlagPruningInterval (11-11)
node/node.go (1)
  • Node (21-25)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: rollkit-docker / docker-build (GHCR; ghcr.io/rollkit/rollkit)
  • GitHub Check: test / Build All Rollkit Binaries
  • GitHub Check: test / Run Unit Tests
  • GitHub Check: Summary
🔇 Additional comments (9)
pkg/config/defaults.go (1)

54-54: Integration of pruning configuration looks good.

The pruning configuration has been correctly added to the default node configuration, initialized with the PruningConfigDefault values from the pruning_config.go file.

pkg/store/types.go (2)

19-20: Good addition of block deletion capability.

This new method is essential for implementing the pruning functionality. The method signature and documentation are clear and consistent with the rest of the interface.


50-54: Well-designed PruningStore interface.

The new interface follows good design practices:

  1. Embeds the existing Store interface for composition
  2. Adds only the specific pruning functionality needed
  3. Allows for clean separation between basic and pruning-capable stores

This design makes it easy to wrap an existing Store implementation with pruning capabilities.

pkg/store/store.go (2)

95-95: Improved error message clarity.

The error message now provides more context about the operation that failed.


140-140: Valid optimization opportunity identified.

The TODO comment correctly identifies a potential optimization - avoiding unnecessary marshaling/unmarshaling operations. This would be a good future improvement.

pkg/config/config.go (1)

173-174: Pruning field added appropriately to NodeConfig.

The new field is properly tagged for serialization with mapstructure and yaml, consistent with other fields in the struct.

pkg/config/pruning_config.go (2)

27-31:

❓ Verification inconclusive

Verify if the default KeepRecent value is appropriate

The default configuration keeps 362,880 blocks which is quite high. Assuming 1-minute blocks, this would retain approximately a year of block data. Consider if this is the appropriate default value for most users or if a smaller value would be more storage-efficient.


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or comments elsewhere in the code that explains this specific value
rg -i "362880" --type go

Length of output: 75


Confirm rationale for default KeepRecent retention

I didn’t find any comments or docs explaining why KeepRecent is set to 362,880 (~1 year at 1‑minute intervals). This may be higher than most users need:

• pkg/config/pruning_config.go:29 (KeepRecent: 362880)

Please verify this aligns with your storage and performance goals. If not, consider reducing the default or documenting the reasoning.


32-36:

❓ Verification inconclusive

Verify the safety of keeping only 2 recent blocks

The PruningConfigEverything strategy keeps only 2 recent blocks. This seems very aggressive and could potentially cause issues if the node needs historical blocks for state synchronization or verification. Consider adding a comment explaining the risks of this strategy.


🏁 Script executed:

#!/bin/bash
# Check if there's any minimum safety threshold mentioned elsewhere in the codebase
rg -i "minimum.*blocks|minimum.*height" --type go

Length of output: 51


No documented minimum for KeepRecent
I wasn’t able to find any existing minimum‐blocks safety threshold in the codebase. Keeping only 2 recent blocks is very aggressive and may break state sync or light‑client verification. Please review and ensure this value is safe for your use case:

  • File: pkg/config/pruning_config.go (lines 32–36)
  • Consider raising KeepRecent to a more conservative default (e.g. 100)
  • Add a comment (or README entry) explaining the risks of aggressive pruning
  • Optionally enforce a minimum threshold in code or via config validation
pkg/store/pruning.go (1)

44-50: LGTM: Good practice calling PruneBlockData before saving new blocks

The implementation correctly calls pruning before saving new blocks, which ensures that storage is managed efficiently.

@Eoous Eoous changed the title support pruning store feat(store): support pruning store Apr 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/store/pruning.go (1)

120-123: ⚠️ Potential issue

Critical bug in startHeight calculation

There's a significant logic error in the calculation of startHeight. The min(0, endHeight-s.Config.KeepRecent) will always return 0 since the first argument is 0 and the second is likely to be negative. This means it will always start pruning from height 0, potentially causing permanent data loss.

Additionally, the comment on line 120 states "Must keep at least 2 blocks" but the code is using s.Config.KeepRecent which could be any value.

Apply this fix:

-	// Must keep at least 2 blocks(while strategy is everything).
+	// Calculate pruning range: keep blocks from (endHeight-KeepRecent) to height
 	endHeight := height - s.Config.KeepRecent
-	startHeight := min(0, endHeight-s.Config.KeepRecent)
+	startHeight := uint64(1) // Start from the first block
+	// Avoid deleting necessary blocks if endHeight is too low
+	if endHeight <= startHeight {
+		return nil
+	}
🧹 Nitpick comments (1)
pkg/store/pruning.go (1)

124-129: Improve error handling for block deletion.

The current implementation stops pruning at the first error. Consider adding more robust error handling to continue pruning even if individual block deletions fail, perhaps by logging errors and continuing.

 	for i := startHeight; i <= endHeight; i++ {
 		err = s.DeleteBlockData(ctx, i)
 		if err != nil {
-			return err
+			// Log the error but continue pruning
+			// Consider adding a logger field to the struct
+			// s.logger.Error("Failed to delete block", "height", i, "error", err)
+			continue
 		}
 	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca9b67 and 82c76be.

📒 Files selected for processing (1)
  • pkg/store/pruning.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
pkg/store/pruning.go (5)
pkg/store/types.go (2)
  • Store (10-48)
  • PruningStore (50-54)
pkg/config/pruning_config.go (2)
  • PruningConfig (45-54)
  • PruningConfigStrategyNone (15-15)
types/signed_header.go (1)
  • SignedHeader (22-27)
types/block.go (2)
  • Data (33-36)
  • Signature (39-39)
types/state.go (1)
  • State (19-38)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: rollkit-docker / docker-build (GHCR; ghcr.io/rollkit/rollkit)
  • GitHub Check: lint / markdown-lint
  • GitHub Check: test / Run Unit Tests
  • GitHub Check: test / Build All Rollkit Binaries
  • GitHub Check: Summary
🔇 Additional comments (5)
pkg/store/pruning.go (5)

11-15: Good struct design with clean separation of concerns.

The DefaultPruningStore struct embeds Store and maintains a separate Config field, demonstrating good composition principles and separation of concerns.


19-25: LGTM: Constructor is clean and well-designed.

This constructor properly initializes the DefaultPruningStore struct and returns it as a PruningStore interface type, adhering to interface-based design principles.


42-50: Good implementation of pruning integration with SaveBlockData.

Calling PruneBlockData before saving a block ensures pruning happens regularly during normal operation. The error handling and wrapping are also well-implemented.


100-119: Pruning trigger logic looks good.

The implementation correctly:

  1. Skips pruning if strategy is set to "none"
  2. Skips pruning if not at the configured interval
  3. Skips pruning if height is less than or equal to the configured KeepRecent value

This ensures pruning only occurs when appropriate and prevents pruning blocks that should be kept.


27-98: LGTM: Delegation methods are correctly implemented.

All the methods that delegate to the embedded Store implementation are correctly forwarding their calls. This pattern ensures that the pruning store maintains the same interface while adding pruning functionality.

Copy link
Contributor

@tac0turtle tac0turtle left a comment

Choose a reason for hiding this comment

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

hey thank you for the work, sorry for the slow review. I left a question, the code looks good but it doesnt solve the issue in terms of nodes being able to prune, they will need to modify the implemention of full.go to use this

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/store/pruning.go (1)

58-60: Misleading comment about keeping "at least 2 blocks"

The comment states "Must keep at least 2 blocks" but the code is using s.Config.KeepRecent which could be configured to any value. This comment should be updated to match the actual implementation.

- // Must keep at least 2 blocks(while strategy is everything).
+ // Calculate pruning boundary: keep the most recent s.Config.KeepRecent blocks
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 685dbe7 and 350b21d.

📒 Files selected for processing (2)
  • pkg/store/pruning.go (1 hunks)
  • pkg/store/types.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/store/types.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
pkg/store/pruning.go (5)
pkg/store/types.go (2)
  • Store (10-48)
  • PruningStore (50-54)
pkg/config/pruning_config.go (2)
  • PruningConfig (45-54)
  • PruningConfigStrategyNone (15-15)
pkg/store/store.go (1)
  • DefaultStore (27-29)
types/signed_header.go (1)
  • SignedHeader (22-27)
types/block.go (2)
  • Data (33-36)
  • Signature (39-39)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: rollkit-docker / docker-build (GHCR; ghcr.io/rollkit/rollkit)
  • GitHub Check: test / Build All Rollkit Binaries
  • GitHub Check: Summary
🔇 Additional comments (5)
pkg/store/pruning.go (5)

12-16: Good structural design with embedded interface

The DefaultPruningStore design appropriately embeds the Store interface and adds a configuration field for pruning. This approach follows the composition over inheritance principle and allows for clean extension of the storage functionality.


18-18: LGTM - Good compile-time interface verification

Using the var _ PruningStore = &DefaultPruningStore{} pattern ensures at compile-time that the struct implements the required interface.


20-26: LGTM - Clean constructor implementation

The constructor properly initializes the DefaultPruningStore by wrapping the provided datastore in a DefaultStore and setting the pruning configuration.


28-36: LGTM - Proper error handling in SaveBlockData

Good approach to run pruning before saving new block data, with proper error handling.


54-56: LGTM - Appropriate pruning conditions

The conditions for skipping pruning are appropriate - checking the interval and ensuring there are enough blocks to maintain the required history.

@Eoous
Copy link
Contributor Author

Eoous commented Apr 28, 2025

Addressed the question.

Not sure it's best to prune blocks while calling SaveBlockData, but for now this way should be easiest way to implemention without changing other codes

@Eoous Eoous requested a review from tac0turtle May 2, 2025 16:39
@Eoous
Copy link
Contributor Author

Eoous commented May 28, 2025

I think SaveBlockData must do in main progress. If async saving new blocks, there is a risk for losing some new blocks.

Store cannot see latest height(there is an interval between calling SetHeight and SaveBlockData), tried to add latestBlockDataHeight in DefaultPruningStore to prune accurately.

Comment on lines 57 to 62
type AsyncPruningStore struct {
PruningStore

latestBlockDataHeight uint64
flushInterval time.Duration
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The AsyncPruningStore implementation has a potential thread safety issue. The latestBlockDataHeight field is updated in SaveBlockData() and read in a separate goroutine started by Start(), but there's no synchronization mechanism (such as a mutex) to protect this shared state. This could lead to race conditions where the pruning goroutine might operate on an inconsistent view of the latest height.

Consider adding a mutex to protect access to latestBlockDataHeight or using atomic operations to ensure thread-safe updates and reads of this value:

import "sync/atomic"

// Use atomic.Uint64 for thread-safe access
var latestHeight atomic.Uint64

// When updating
latestHeight.Store(newValue)

// When reading
currentHeight := latestHeight.Load()
Suggested change
type AsyncPruningStore struct {
PruningStore
latestBlockDataHeight uint64
flushInterval time.Duration
}
type AsyncPruningStore struct {
PruningStore
latestBlockDataHeight atomic.Uint64
flushInterval time.Duration
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines 88 to 102
func (s *AsyncPruningStore) Start(ctx context.Context) {
// todo: use ctx for cancellation
ticker := time.NewTicker(s.flushInterval)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Currently PruneBlockData only returns nil.
_ = s.PruneBlockData(ctx, s.latestBlockDataHeight)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The Start method should check if the context is already canceled before entering the loop. If ctx.Done() is triggered immediately after calling Start(), the ticker will be created but the deferred ticker.Stop() won't execute until the function returns, which could lead to a goroutine leak.

Consider adding a check at the beginning:

func (s *AsyncPruningStore) Start(ctx context.Context) {
    if ctx.Err() != nil {
        return
    }
    
    ticker := time.NewTicker(s.flushInterval)
    defer ticker.Stop()
    
    // rest of the function...
}

This ensures resources are properly cleaned up when the context is already canceled.

Suggested change
func (s *AsyncPruningStore) Start(ctx context.Context) {
// todo: use ctx for cancellation
ticker := time.NewTicker(s.flushInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Currently PruneBlockData only returns nil.
_ = s.PruneBlockData(ctx, s.latestBlockDataHeight)
}
}
}
func (s *AsyncPruningStore) Start(ctx context.Context) {
if ctx.Err() != nil {
return
}
ticker := time.NewTicker(s.flushInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
// Currently PruneBlockData only returns nil.
_ = s.PruneBlockData(ctx, s.latestBlockDataHeight)
}
}
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
block/async_store.go (1)

12-17: Consider more descriptive field naming.

The struct design is sound, but the field name ps could be more descriptive for better code readability.

 type AsyncPruner struct {
-	ps store.PruningStore
+	pruningStore store.PruningStore
 
 	flushInterval time.Duration
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99c5b39 and cda2d67.

📒 Files selected for processing (5)
  • block/async_store.go (1 hunks)
  • node/full.go (4 hunks)
  • node/light.go (1 hunks)
  • pkg/store/pruning.go (1 hunks)
  • pkg/store/types.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • node/light.go
  • pkg/store/types.go
  • node/full.go
  • pkg/store/pruning.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
block/async_store.go (1)
pkg/store/types.go (1)
  • PruningStore (50-54)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: rollkit-docker / docker-build (GHCR; ghcr.io/rollkit/rollkit)
  • GitHub Check: test / Run Unit Tests
  • GitHub Check: test / Run EVM Execution Tests
  • GitHub Check: test / Build All Rollkit Binaries
  • GitHub Check: Summary
🔇 Additional comments (3)
block/async_store.go (3)

1-8: LGTM - Clean package structure and appropriate imports.

The package declaration and imports are well-organized and include only the necessary dependencies for the asynchronous pruning functionality.


10-10: LGTM - Reasonable default flush interval.

A 1-second default flush interval strikes a good balance between pruning frequency and system performance.


19-29: LGTM - Well-implemented constructor with proper validation.

The constructor properly validates the flush interval and provides a sensible default, following good defensive programming practices.

@Eoous
Copy link
Contributor Author

Eoous commented Jun 4, 2025

Still checking for pruning range, could review without it @alpe @tac0turtle

@Eoous Eoous requested review from alpe and tac0turtle June 4, 2025 02:59
Copy link
Contributor

@alpe alpe left a comment

Choose a reason for hiding this comment

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

Good progress. Some more nits and comments. Keep on 💪

}
if err := batch.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit batch: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

how do we ensure that we delete everything? there might be more prefix stores in the future?

Copy link
Contributor Author

@Eoous Eoous Jun 7, 2025

Choose a reason for hiding this comment

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

Structures like block and header may need to refactor in future

I think currently it's enough to be same with SaveBlockData

startHeight = endHeight - s.Config.Interval
}

for i := startHeight; i < endHeight; i++ {
Copy link
Contributor

Choose a reason for hiding this comment

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

As this is a public method, it can be called by other threads as well. It would not cost much to exit early when pruning is not completed.

@Eoous Eoous requested a review from alpe June 7, 2025 19:45
@Eoous
Copy link
Contributor Author

Eoous commented Jun 7, 2025

Thanks for review @alpe. Addressed some comments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cda2d67 and cca398e.

📒 Files selected for processing (5)
  • block/async_store.go (1 hunks)
  • node/full.go (4 hunks)
  • pkg/cmd/run_node.go (1 hunks)
  • pkg/config/pruning_config.go (1 hunks)
  • pkg/store/pruning.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/cmd/run_node.go
  • node/full.go
  • pkg/store/pruning.go
  • pkg/config/pruning_config.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
block/async_store.go (1)
pkg/store/types.go (1)
  • PruningStore (50-54)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: rollkit-docker / docker-build (GHCR; ghcr.io/rollkit/rollkit)
  • GitHub Check: test / Run EVM Execution Tests
  • GitHub Check: test / Run Unit Tests
  • GitHub Check: test / Build All Rollkit Binaries
  • GitHub Check: Summary
🔇 Additional comments (4)
block/async_store.go (4)

1-9: Package declaration and imports look good.

The package structure and imports are clean and follow Go conventions.


11-11: Default flush interval is well-chosen.

The 10-second interval aligns with the block time defaults and provides a reasonable balance for pruning frequency.


13-19: Well-designed struct with clear purpose.

The AsyncPruner struct contains appropriate fields and the documentation clearly explains its purpose.


21-28: Constructor is properly implemented.

The NewAsyncPruner constructor follows Go conventions and correctly initializes all required fields.

@alpe
Copy link
Contributor

alpe commented Aug 4, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The code changes introduce a new store type, DefaultPruningStore, to prune storage, along with an AsyncPruner to periodically prune block data in the background. The changes also include command-line flags for pruning strategy, number of recent blocks to keep, and pruning operation frequency. The review identified a critical issue in the endHeight calculation in pkg/store/pruning.go that would prevent blocks from being pruned, and a critical issue in pkg/store/store.go where the hash-to-height index was not being deleted, leading to dangling index entries. Additionally, the review identified a high severity issue in pkg/store/pruning.go where errors from s.DeleteBlockData were being ignored.

}

// Must keep at least 2 blocks(while strategy is everything).
endHeight := height - 1 - s.config.KeepRecent
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The calculation for endHeight seems to be off by one, which will result in not pruning a block that should be pruned.

If we want to keep KeepRecent blocks, the highest block to prune is height - KeepRecent. The loop for i := startHeight; i < endHeight; i++ prunes up to endHeight - 1.

With the current logic, endHeight is height - 1 - s.config.KeepRecent. This means the loop prunes up to height - s.config.KeepRecent - 2, leaving one block unpruned.

To fix this, endHeight should be height - s.config.KeepRecent. This makes the loop prune up to height - s.config.KeepRecent - 1, which is what we want.

Suggested change
endHeight := height - 1 - s.config.KeepRecent
endHeight := height - s.config.KeepRecent

Comment on lines +108 to +128
func (s *DefaultStore) DeleteBlockData(ctx context.Context, height uint64) error {
batch, err := s.db.Batch(ctx)
if err != nil {
return fmt.Errorf("failed to create a new batch for deleting block data: %w", err)
}

if err := batch.Delete(ctx, ds.NewKey(getHeaderKey(height))); err != nil {
return fmt.Errorf("failed to delete header blob in batch: %w", err)
}
if err := batch.Delete(ctx, ds.NewKey(getDataKey(height))); err != nil {
return fmt.Errorf("failed to delete data blob in batch: %w", err)
}
if err := batch.Delete(ctx, ds.NewKey(getSignatureKey(height))); err != nil {
return fmt.Errorf("failed to delete signature of block blob in batch: %w", err)
}
if err := batch.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit batch: %w", err)
}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The current implementation of DeleteBlockData does not delete the hash-to-height index entry that is created in SaveBlockData. This will lead to dangling index entries pointing to non-existent blocks, which can cause issues when trying to retrieve blocks by hash.

To fix this, DeleteBlockData should first retrieve the block header to get its hash, and then delete the corresponding index entry along with the other block data.

I've also added handling for ds.ErrNotFound when fetching the header, as the block might have already been pruned.

func (s *DefaultStore) DeleteBlockData(ctx context.Context, height uint64) error {
	// Get the header to retrieve the block hash for index deletion.
	header, err := s.GetHeader(ctx, height)
	if err != nil {
		// If block not found, it might have been already pruned.
		if errors.Is(err, ds.ErrNotFound) {
			return nil
		}
		return fmt.Errorf("failed to get header for block %d: %w", height, err)
	}

	hash := header.Hash()

	batch, err := s.db.Batch(ctx)
	if err != nil {
		return fmt.Errorf("failed to create a new batch for deleting block data: %w", err)
	}

	if err := batch.Delete(ctx, ds.NewKey(getHeaderKey(height))); err != nil {
		return fmt.Errorf("failed to delete header blob in batch: %w", err)
	}
	if err := batch.Delete(ctx, ds.NewKey(getDataKey(height))); err != nil {
		return fmt.Errorf("failed to delete data blob in batch: %w", err)
	}
	if err := batch.Delete(ctx, ds.NewKey(getSignatureKey(height))); err != nil {
		return fmt.Errorf("failed to delete signature of block blob in batch: %w", err)
	}
	if err := batch.Delete(ctx, ds.NewKey(getIndexKey(hash))); err != nil {
		return fmt.Errorf("failed to delete index key in batch: %w", err)
	}
	if err := batch.Commit(ctx); err != nil {
		return fmt.Errorf("failed to commit batch: %w", err)
	}

	return nil
}

Comment on lines +67 to +70
for i := startHeight; i < endHeight; i++ {
// Could ignore for errors like not found.
_ = s.DeleteBlockData(ctx, i)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Errors from s.DeleteBlockData are being ignored. The comment suggests that ErrNotFound could be ignored, but other errors (like issues with the underlying datastore) should be handled.

Ignoring these errors can hide serious problems and leave the pruning process in an incomplete state without any notification. The error should be propagated up to the AsyncPruner, which will log it.

With the recommended changes to DeleteBlockData in pkg/store/store.go, it will correctly handle ErrNotFound by returning nil, so any error returned from it will be significant.

if err := s.DeleteBlockData(ctx, i); err != nil {
			return err
		}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

3 participants