Skip to content

persist blockchain across sessions via save/load#56

Closed
aniket866 wants to merge 4 commits intoStabilityNexus:mainfrom
aniket866:persistence
Closed

persist blockchain across sessions via save/load#56
aniket866 wants to merge 4 commits intoStabilityNexus:mainfrom
aniket866:persistence

Conversation

@aniket866
Copy link
Copy Markdown
Contributor

@aniket866 aniket866 commented Mar 11, 2026

Addressed Issues:

Fixes #57

screen-capture.41.online-video-cutter.com.mp4

MiniChain Persistence Test — 6 Steps

Step 1 — Start the node

python main.py --port 9000

Note your address printed on screen.


Step 2 — Send coins

send <your_address> 30

Step 3 — Mine the block

mine

Expected output:

Block #1 mined and added (1 txs)

Step 4 — Check the chain

chain

Expected output:

Chain length: 2 blocks
  Block #0  hash=0000000000000000...  txs=0
  Block #1  hash=00003fa9...          txs=1

Step 5 — Quit (saves chain to disk)

quit

Expected output:

Chain saved to 'node_data' (2 blocks)

Step 6 — Restart and verify

python main.py --port 9000

Expected output:

Loaded existing chain (2 blocks) from 'node_data'

Then type:

chain

Same 2 blocks appear — persistence is working.


Reset (start fresh)

rmdir /s /q node_data

Screenshots/Recordings:

TODO: If applicable, add screenshots or recordings that demonstrate the interface before and after the changes.

Additional Notes:

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: Chatgpt

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • New Features
    • Added JSON persistence for the blockchain with save/load APIs and automatic load on startup and save on shutdown.
  • API
    • Persistence functions are now part of the package public API.
  • Tests
    • Added a comprehensive test suite verifying persistence, including chain, accounts, transactions, and contract storage.
  • Chores
    • Added node data directory to ignore list.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 11, 2026

Warning

Rate limit exceeded

@aniket866 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 39 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 098b688a-d1cc-4897-bf35-bbff62733edd

📥 Commits

Reviewing files that changed from the base of the PR and between 9f72f17 and d105c9a.

📒 Files selected for processing (1)
  • main.py

Walkthrough

Adds JSON-based persistence: new minichain/persistence.py implements save(blockchain, path) and load(path) to write/read blockchain.json and state.json; minichain/__init__.py exports save/load; main.py loads on startup and saves on shutdown; tests for persistence added.

Changes

Cohort / File(s) Summary
Public API Exposure
minichain/__init__.py
Exports save and load from minichain.persistence by adding them to __all__.
Persistence Implementation
minichain/persistence.py
New module providing save(blockchain, path=".") and load(path="."). Performs JSON read/write (blockchain.json, state.json), validation, block/transaction deserialization, state rehydration (including ContractMachine), and helper functions (_write_json, _read_json, _deserialize_block).
Node runtime persistence
main.py
Introduces DATA_DIR, imports save/load, attempts to load chain from disk on startup and saves chain on shutdown; logging and CLI message phrasing adjusted.
Persistence Tests
tests/test_persistence.py
New comprehensive test suite verifying file creation, chain/block integrity, account balances/nonces, transaction preservation, ability to add blocks after load, handling missing files, genesis-only chains, and contract storage persistence.
VCS ignore
.gitignore
Adds node_data/ to ignore list to exclude persisted data directory.

Sequence Diagram

sequenceDiagram
    participant Client as Client/User
    participant Persist as Persistence\n(save/load)
    participant FS as Filesystem
    participant Deser as Deserializer
    participant Blockchain as Blockchain\n(instance)

    Client->>Persist: save(blockchain, path)
    Persist->>FS: write "blockchain.json"
    Persist->>FS: write "state.json"
    FS-->>Persist: write ack

    Client->>Persist: load(path)
    Persist->>FS: read "blockchain.json"
    FS-->>Persist: chain JSON
    Persist->>FS: read "state.json"
    FS-->>Persist: state JSON
    Persist->>Deser: deserialize blocks & transactions
    Deser-->>Persist: reconstructed Blocks
    Persist->>Blockchain: instantiate and assign chain/state
    Blockchain-->>Persist: ready instance
    Persist-->>Client: return Blockchain
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Python Lang

Suggested reviewers

  • Zahnentferner

Poem

🐰 I hopped through files both near and far,
Saved blocks and state in a JSON jar,
Morning load brings chains anew,
Blocks and balances — all true! ✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the PR: implementing persistence for the blockchain across sessions using save/load functions.
Linked Issues check ✅ Passed All coding requirements from issue #57 are met: persistence module added, save/load APIs exposed, blockchain and state persisted to disk, load returns functional Blockchain instance, and fields preserved.
Out of Scope Changes check ✅ Passed All changes are directly related to persistence objectives: new persistence module, test suite, integration into main.py, and .gitignore update for node_data directory.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown
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: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@minichain/persistence.py`:
- Around line 75-88: The function currently validates raw_blocks but assigns
raw_accounts directly; add a validation step before creating blockchain.state to
ensure raw_accounts is a dict (and non-empty if desired), e.g. verify
isinstance(raw_accounts, dict) and raise a ValueError with context including
chain_path if the check fails; then proceed to create State via
State.__new__(State) and assign blockchain.state.accounts = raw_accounts and
blockchain.state.contract_machine = ContractMachine(blockchain.state) as before,
referencing the existing symbols raw_accounts, State, ContractMachine,
blockchain.state, and chain_path to locate the insertion point.
- Around line 79-81: Move the standard-library import "threading" out of the
local scope and place it at module-level with the other imports (so remove the
inline "import threading" inside the function) and update any references to
threading.Thread or threading.Lock in this module accordingly; specifically,
remove the local import in the function whereState and ContractMachine are used
and add a single top-level "import threading" next to the other imports so the
module uses the shared top-level threading name.
- Line 109: Remove the unnecessary explicit "r" mode when opening files: locate
the with open(filepath, "r", encoding="utf-8") as f usage in persistence.py (the
open call that reads the file into f) and change it to with open(filepath,
encoding="utf-8") as f so the default read mode is used and functionality
remains identical.

In `@tests/test_persistence.py`:
- Line 116: The test unpacks alice_pk and bob_pk from self._chain_with_tx() but
never uses them; change the unpacking to prefix unused variables with
underscores (e.g., bc, _alice_pk, _bob_pk) to signal they are intentionally
ignored and avoid lint warnings—update the tuple assignment where
self._chain_with_tx() is called in the test to use the underscore-prefixed
names.
- Line 71: Update the zip call in the test loop that iterates over bc.chain and
restored.chain to use strict=True so the comparison fails if lengths differ;
specifically modify the loop in tests/test_persistence.py (the for original,
loaded in zip(bc.chain, restored.chain) loop) to pass strict=True to zip() to
enforce equal-length iteration.
- Around line 24-25: The setUp method creates a temp directory (self.tmpdir) but
never removes it; add a tearDown method that checks for and removes self.tmpdir
(e.g., using shutil.rmtree) to clean up after each test. Implement tearDown in
the same test class that defines setUp, guard against missing/None self.tmpdir,
and handle exceptions (or ignore errors) so test teardown never fails.
- Around line 169-170: The test test_contract_storage_preserved currently only
asserts restored.state.get_account(contract_addr)["code"] equals code; add an
assertion to also verify the contract storage survived the save/load cycle by
fetching the account via restored.state.get_account(contract_addr) and asserting
that its storage (e.g. contract["storage"]["hits"] or the appropriate storage
key) equals 1, ensuring storage["hits"] is present and has the expected value
after restore.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66734a3a-c93b-4296-bffc-fe3db8634f61

📥 Commits

Reviewing files that changed from the base of the PR and between ff5e83a and 0e5eb65.

📒 Files selected for processing (3)
  • minichain/__init__.py
  • minichain/persistence.py
  • tests/test_persistence.py

Copy link
Copy Markdown
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

♻️ Duplicate comments (2)
minichain/persistence.py (2)

113-113: 🧹 Nitpick | 🔵 Trivial

Remove unnecessary mode argument.

The "r" mode is the default for open() and can be omitted.
,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/persistence.py` at line 113, Remove the redundant explicit read
mode in the open() call: change the call using open(filepath, "r",
encoding="utf-8") to open(filepath, encoding="utf-8") (the default mode is "r");
update the use found where the file is opened with the variable filepath so only
encoding is passed.

83-85: 🧹 Nitpick | 🔵 Trivial

Move threading import to module level.

The threading import is a standard library module with no circular import concerns. Moving it to the module level with other imports improves clarity and follows Python conventions.
,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@minichain/persistence.py` around lines 83 - 85, Move the local import of the
standard-library module threading to the module-level imports in
minichain/persistence.py (alongside other imports like State and
ContractMachine) to follow Python conventions; update any references to
threading in the file (e.g., inside the Persistence class or functions that
currently import it locally) to use the top-level import instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@main.py`:
- Around line 312-318: Replace the current exception logging in the save block
to preserve the traceback: inside the try/except around os.makedirs(DATA_DIR,
exist_ok=True) and save(chain, path=DATA_DIR), change the logger.error call that
references the exception variable to logger.exception so the stack trace for the
failure is recorded (keep the same message context mentioning DATA_DIR/chain
length); leave await network.stop() as-is.
- Around line 259-270: The current broad except in the chain load block masks
unexpected errors; update the try/except around load(path=DATA_DIR) to catch
only the expected exceptions (e.g., FileNotFoundError, ValueError, and
json.JSONDecodeError) and fall back to creating a new Blockchain() for those
cases, while letting other exceptions propagate; ensure json is imported if
using json.JSONDecodeError and keep the logger.warning call to report the
specific exception for the handled cases, referencing load(), Blockchain,
DATA_DIR, chain_file, and logger.

---

Duplicate comments:
In `@minichain/persistence.py`:
- Line 113: Remove the redundant explicit read mode in the open() call: change
the call using open(filepath, "r", encoding="utf-8") to open(filepath,
encoding="utf-8") (the default mode is "r"); update the use found where the file
is opened with the variable filepath so only encoding is passed.
- Around line 83-85: Move the local import of the standard-library module
threading to the module-level imports in minichain/persistence.py (alongside
other imports like State and ContractMachine) to follow Python conventions;
update any references to threading in the file (e.g., inside the Persistence
class or functions that currently import it locally) to use the top-level import
instead.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8890ee0-41ff-44a4-8bf4-b45c069b4164

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5eb65 and 8aa023b.

📒 Files selected for processing (3)
  • .gitignore
  • main.py
  • minichain/persistence.py

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@main.py`:
- Around line 333-336: The persistence layer is not crash-safe: the save() call
in main.py writes blockchain.json and state.json sequentially and can leave
partial/corrupt files if the process crashes; update minichain/persistence.py to
perform atomic writes by writing each output to a temporary file (e.g.,
blockchain.json.tmp / state.json.tmp) and then atomically replacing the target
using os.replace(), ensuring any directory creation and fsyncs as needed, and
update the save() usage (or its implementation) so callers like the save(chain,
path=DATA_DIR) invocation remain unchanged; if you prefer the demo route, add a
short note in the README indicating that corrupted node_data/ can be recovered
by deleting the directory.
- Around line 89-90: Remove the duplicate/inaccurate log: keep the accurate
logger.info call that uses mineable_txs and the emoji, and delete the earlier
logger.info that references pending_txs; update any nearby comments if needed to
reflect that the message reports the number of mineable (valid) transactions for
mined_block (references: logger.info, mined_block, pending_txs, mineable_txs).
- Line 23: Remove the unused import of the re module from main.py by deleting
the "import re" statement; ensure no other code references the re symbol and run
linters/tests to confirm no side effects.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bf2230ba-ef93-4589-893a-f6dd646c1414

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa023b and 9f72f17.

📒 Files selected for processing (1)
  • main.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.

[BUG]: the entire chain lived in memory and was lost on every restart

2 participants