Skip to content

Conversation

@m2ux
Copy link
Contributor

@m2ux m2ux commented Jan 15, 2026

Summary

Implement wallet state caching to eliminate genesis replay bottleneck in the toolkit. This feature dramatically reduces startup time for subsequent toolkit runs by persisting LedgerState across sessions.

Key changes:

  • Add WalletStateStorage trait and WalletStateCache types for cache abstraction
  • Implement RedbBackend for file-based persistence (single-instance)
  • Implement PostgresBackend for multi-instance deployments
  • Add cache_helpers module for LedgerContext serialization/deserialization
  • Add --wallet-cache CLI argument (disabled by default)
  • Integrate caching in build_context_with_cache() function

Usage:

# Enable file-based caching
midnight-node-toolkit --wallet-cache=redb:wallet_cache.db generate-txs ...

# Enable PostgreSQL caching
midnight-node-toolkit --wallet-cache="postgres://user:pass@localhost/db" generate-txs ...

# Or via environment variable
MN_WALLET_CACHE=redb:wallet.db midnight-node-toolkit generate-txs ...

How it works:

  1. On first run: Process all blocks from genesis, save LedgerState to cache
  2. On subsequent runs: Restore from cache, only replay new blocks since checkpoint
  3. Cache is keyed by chain_id (block 1 hash) + wallet_id (derived from seeds)

Test plan

  • Unit tests for InMemory storage
  • Unit tests for RedbBackend (roundtrip, persistence, overwrite, delete)
  • Unit tests for WalletCacheConfig parsing
  • cargo check passes
  • cargo fmt applied
  • E2E test with live network (manual verification)
  • Performance benchmarking on long-running network

Checklist

  • Changes have been tested locally
  • All commits follow conventional commits format
  • Code formatted with cargo fmt

Ticket: https://shielded.atlassian.net/browse/PM-21139

@m2ux m2ux self-assigned this Jan 15, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 2026

kics-logo

KICS version: v2.1.16

Category Results
CRITICAL CRITICAL 0
HIGH HIGH 0
MEDIUM MEDIUM 123
LOW LOW 12
INFO INFO 85
TRACE TRACE 0
TOTAL TOTAL 220
Metric Values
Files scanned placeholder 28
Files parsed placeholder 28
Files failed to scan placeholder 0
Total executed queries placeholder 73
Queries failed to execute placeholder 0
Execution time placeholder 7

m2ux added 7 commits January 15, 2026 12:36
Introduce wallet state caching abstraction to eliminate genesis replay
on toolkit startup. This commit adds:

- WalletStateStorage trait for pluggable cache backends
- WalletStateCache and WalletSnapshot types for serialization
- InMemory implementation for testing
- Placeholder files for Redb and PostgreSQL backends

The cache is keyed by chain_id (block 1 hash) and wallet_id (hash of
wallet public keys) to ensure correct cache matching.

Refs: PM-21139
Add persistent WalletStateStorage backend using redb embedded database:

- RedbBackend stores wallet cache in local file (wallet_state_cache table)
- BSON serialization via Serde wrapper (same pattern as FetchStorage)
- Supports sharing database file with FetchStorage via from_database()
- Includes comprehensive tests for roundtrip, delete, overwrite, persistence

The Redb backend enables single-instance toolkit deployments to persist
wallet state across sessions.

Refs: PM-21139
Add persistent WalletStateStorage backend using PostgreSQL:

- PostgresBackend stores wallet cache in wallet_state_cache table
- BSON serialization (consistent with FetchStorage pattern)
- Supports sharing connection pool with FetchStorage via with_pool()
- Separate block_height column for efficient height-only queries
- Includes created_at/updated_at timestamps for observability

The PostgreSQL backend enables multi-instance toolkit deployments to
share wallet state cache across processes.

Refs: PM-21139
Add cache_helpers module with functions to serialize/deserialize
LedgerContext state for wallet state caching:

- serialize_ledger_state/deserialize_ledger_state using mn_ledger_serialize
- create_cache_from_context: captures LedgerState and wallet seed hashes
- restore_context_from_cache: restores LedgerContext from cached state
- Wallet state is initialized fresh and rebuilt during block replay

The key optimization is caching LedgerState (full blockchain state), which
is the main reconstruction bottleneck. Wallet-specific state (shielded coins,
dust) is quickly rebuilt by replaying only blocks since the checkpoint.

Refs: PM-21139
Add --wallet-cache CLI argument and WalletCacheConfig enum:

- WalletCacheConfig enum with Disabled/InMemory/Redb/Postgres variants
- FromStr implementation for parsing CLI arguments
- wallet_cache_config parser function in cli_parsers
- --wallet-cache arg on Source struct (default: disabled, env: MN_WALLET_CACHE)

The wallet cache is disabled by default to maintain backward compatibility.
Users can enable caching with --wallet-cache=redb:wallet.db or similar.

Refs: PM-21139
Add build_context_with_cache function for cache-enabled context building:

- Attempts to restore LedgerContext from cache if available
- Only replays blocks since the cached checkpoint
- Saves updated cache after processing new blocks
- Supports Redb and PostgreSQL backends via WalletCacheConfig

Helper functions:
- compute_wallet_id_for_seeds: derive wallet identity from seeds
- save_context_to_cache: persist context state to configured backend

The caching is opt-in via the --wallet-cache CLI argument. When enabled,
subsequent runs will skip replaying already-processed blocks.

Refs: PM-21139
Add comprehensive unit tests for wallet state caching:

- WalletCacheConfig parsing tests (disabled, inmemory, redb, postgres)
- InMemory storage tests (cache miss, hit, delete)
- RedbBackend tests (roundtrip, delete, overwrite, persistence)
- cache_helpers tests (block context roundtrip, seed hashing)

Note: Integration tests requiring a running node are deferred to
the e2e test suite.

Refs: PM-21139
Copy link
Contributor

@ozgb ozgb left a comment

Choose a reason for hiding this comment

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

With this implementation, serializing/deserializing entire wallets states could lead to reduced performance on long-running networks as these states will get very large, and would be unwieldy in the case of loading many wallets from cache

An alternative would be using ledger storage - this would mean we're not duplicating data, and would improves read and write speeds and memory usage when loading and saving items from/to the cache.

The disadvantage of using ledger storage would be that each toolkit instance must have exclusive access to it's own ledger storage backend.

The implementation in this PR has an advantage of simplicity compared to the ledger storage route - to reduce the code size, I'd suggest re-using the fetch cache instead of adding a new separate wallet cache

m2ux added 4 commits January 19, 2026 14:23
- Move wallet_state_storage module into fetcher/wallet_state_cache
- Add WalletStateCaching trait implemented by InMemory, RedbBackend,
  and PostgresBackend
- Add zstd compression for cached wallet state in file/postgres backends
- Remove separate --wallet-cache CLI argument (now uses --fetch-cache)
- Replace all expects with proper error handling (log warnings, return
  None for reads, early return for writes)
- Net reduction of ~850 lines through code reuse
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.

3 participants