-
-
Notifications
You must be signed in to change notification settings - Fork 20
feat: Implement StateJournal and in-memory snapshots for state rollback #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4e3387a
91a0e87
f1384db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,9 @@ def __init__(self, genesis_path="genesis.json"): | |
| self.state = State() | ||
| self.chain_id = "minichain-default" | ||
| self._lock = threading.RLock() | ||
| import collections | ||
| from .node_config import MAX_STATE_SNAPSHOTS | ||
| self._state_snapshots = collections.deque(maxlen=MAX_STATE_SNAPSHOTS) | ||
| self._create_genesis_block(genesis_path) | ||
|
|
||
| def _create_genesis_block(self, genesis_path): | ||
|
|
@@ -131,6 +134,7 @@ def _create_genesis_block(self, genesis_path): | |
|
|
||
| # Snapshot the state exactly after genesis allocation for clean reorg rebuilds | ||
| self._genesis_state_snapshot = self.state.snapshot() | ||
| self._state_snapshots.append((genesis_block.hash, self.state.snapshot())) | ||
|
|
||
| @property | ||
| def last_block(self): | ||
|
|
@@ -237,7 +241,9 @@ def add_block(self, block): | |
| self.current_target = new_target | ||
| self.avg_block_time = new_avg | ||
| self.chain.append(block) | ||
|
|
||
|
|
||
| self._state_snapshots.append((block.hash, self.state.snapshot())) | ||
|
|
||
| return ValidationStatus.VALID | ||
|
|
||
| def resolve_conflicts(self, new_chain_list) -> tuple[bool, list]: | ||
|
|
@@ -293,12 +299,35 @@ def resolve_conflicts(self, new_chain_list) -> tuple[bool, list]: | |
|
|
||
| temp_state = State() | ||
| temp_state.chain_id = self.chain_id | ||
| temp_state.restore(self._genesis_state_snapshot) | ||
|
|
||
| fork_base_hash = self.chain[fork_idx - 1].hash if fork_idx > 0 else None | ||
|
|
||
| temp_target = proposed_chain[0].target | ||
| temp_avg_block_time = self.target_block_time | ||
|
|
||
| snapshot_found = None | ||
| if fork_base_hash: | ||
| for h, snap in self._state_snapshots: | ||
| if h == fork_base_hash: | ||
| snapshot_found = snap | ||
| break | ||
|
|
||
| if snapshot_found is not None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't |
||
| logger.info("Reorg optimization: Restoring state from in-memory snapshot at block %s", fork_idx - 1) | ||
| temp_state.restore(snapshot_found) | ||
|
|
||
| # Fast forward target and avg_block_time without executing state | ||
| for i in range(1, fork_idx): | ||
| block_time = proposed_chain[i].timestamp - proposed_chain[i-1].timestamp | ||
| temp_avg_block_time = self.alpha * block_time + (1 - self.alpha) * temp_avg_block_time | ||
| temp_target = self._next_target(temp_target, temp_avg_block_time) | ||
|
|
||
| start_idx = fork_idx | ||
| else: | ||
| temp_state.restore(self._genesis_state_snapshot) | ||
| start_idx = 1 | ||
|
|
||
| for i in range(1, len(proposed_chain)): | ||
| for i in range(start_idx, len(proposed_chain)): | ||
| status, temp_target, temp_avg_block_time = self._apply_block( | ||
| proposed_chain[i - 1], proposed_chain[i], temp_state, temp_target, temp_avg_block_time | ||
| ) | ||
|
|
@@ -317,6 +346,9 @@ def resolve_conflicts(self, new_chain_list) -> tuple[bool, list]: | |
| self.state = temp_state | ||
| self.current_target = temp_target | ||
| self.avg_block_time = temp_avg_block_time | ||
|
|
||
|
|
||
| # Repopulate snapshots for the new chain tip | ||
| self._state_snapshots.append((self.last_block.hash, self.state.snapshot())) | ||
|
Comment on lines
+350
to
+351
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift Rebuild the bounded snapshot deque after a reorganization. This code appends only the new tip and retains snapshots from the old branch. After a deep reorganization, a later shallow reorganization on the new branch cannot find its fork-point snapshot and falls back to replay from genesis. Collect snapshots for the retained portion of 🤖 Prompt for AI Agents |
||
|
|
||
| logger.info("Reorg successful! Switched to new chain tip: Block %s", self.last_block.index) | ||
| return True, orphans | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
Authenticate the incoming genesis block before using its target.
When
fork_idx == 0,proposed_chain[0]comes fromnew_chain_list, but the validation loop starts at block 1. The code checks only the incoming object's mutablehashfield. It can therefore use a different genesis target or state root with the local genesis snapshot. Recompute and compare the complete genesis header, or replace the incoming genesis object withself.chain[0]before using it.Also applies to: 325-330
🤖 Prompt for AI Agents