Skip to content

[CI] Improve lazy-loading test - #3811

Merged
arturgontijo merged 13 commits into
masterfrom
artur/ci-fixes-lazy-loading
Jul 13, 2026
Merged

arturgontijo merged 13 commits into
masterfrom
artur/ci-fixes-lazy-loading

Conversation

@arturgontijo

@arturgontijo arturgontijo commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

What does it do?

Improves CI reliability for the lazy-loading (fork) test environment and de-flakes three dev test suites. These changes are infrastructure/test-only — there are no runtime or consensus changes and nothing here affects on-chain behavior.

Lazy-loading resilience (node/service/src/lazy_loading/**, node/cli*)

  • Added a persistent, content-addressed disk cache for lazy-loading RPC responses (state_cache.rs). State reads (storage, storage_hash, storage_keys_paged) are keyed by a blake2 hash of the request and served from disk on subsequent runs, so a warm cache turns network-bound startup into local reads.
  • Added a pinned fork block, scoped by a fingerprint of the remote endpoint, so repeated runs/CI retries fork from the same block and hit the cache instead of re-fetching from latest.
  • Made the RPC client resilient to a flaky upstream: exponential backoff with jitter between retries and a larger request timeout.
  • Made node startup configurable via new CLI flags: --lazy-loading-startup-delay (default preserved) and --lazy-loading-cache-path.

Moonwall config (test/moonwall.config.json)

  • Run the lazy-loading suite single-threaded to avoid concurrent node startups contending for CPU on CI runners (the root cause of the startup timeouts).
  • Wired the cache path, a zero startup delay, and a higher per-request retry count for CI.

Dev-test de-flakes

  • test-block-1 (D010101): wait for the eth RPC view to catch up to the freshly sealed block before asserting the block number, via a new reusable waitFor polling helper in test/helpers/common.ts.
  • test-balance-existential (D020301): drain the tx pool (sealUntilTxPoolEmpty) so submitted transfers are included before balance assertions.
  • test-eth-fee-history (D020901): poll eth_feeHistory until the asynchronously-populated baseFeePerGas cache is complete before asserting on it.

What important points should reviewers know?

  • Scope is CI/test infrastructure only; no runtime, storage, or API changes.
  • The lazy-loading disk cache is content-addressed and endpoint-fingerprinted, so it cannot silently serve stale state from a different endpoint; if a pinned block is unavailable it falls back to latest with a warning rather than failing.
  • The single-thread setting for the lazy-loading environment was the decisive fix for the CI timeouts; the caching/retry/startup-delay changes reduce startup time and insulate against upstream RPC flakiness on top of that.
  • waitFor is bounded (default ~5s) and does not throw on timeout, so the underlying assertions still fail loudly on a genuine regression rather than being masked.

Is there something left for follow-up PRs?

  • Other dev suites still hand-roll setTimeout polling loops; they could be migrated to the new waitFor helper.
  • The lazy-loading disk cache currently has no eviction/TTL; a size cap or cleanup step could be added if it grows large in long-lived CI caches.

What alternative implementations were considered?

  • Keeping the lazy-loading suite multi-threaded and only raising timeouts — rejected, because CPU contention from concurrent node startups still pushed nodes past Moonwall's startup deadline.
  • Mocking the upstream trace RPC instead of caching real responses — rejected as higher-maintenance and less representative than caching genuine state.

Are there relevant PRs or issues in other repositories (Substrate, Polkadot, Frontier, Cumulus)?

None.

What value does it bring to the blockchain users?

No direct user-facing change. It makes CI faster and far less flaky, which shortens the feedback loop and reduces spurious failures for contributors.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

@arturgontijo arturgontijo self-assigned this Jul 10, 2026
@arturgontijo arturgontijo added B0-silent Changes should not be mentioned in any release notes D2-notlive PR doesn't change runtime code (so can't be audited) not-breaking Does not need to be mentioned in breaking changes labels Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6969b184-b8c4-4789-80b0-db4151c7427b

📥 Commits

Reviewing files that changed from the base of the PR and between 078a0f4 and 7ff9b83.

📒 Files selected for processing (1)
  • test/suites/dev/moonbase/test-eth-rpc/test-eth-rpc-pending-transactions.ts

📝 Walkthrough

Walkthrough

Lazy-loading now supports configurable startup delay and persistent fork-state caching. RPC requests use cached immutable results and adaptive retries. Backend fork checkpoints can be reused and persisted, while development tests add polling and transaction-settlement synchronization.

Changes

Lazy-loading cache and runtime controls

Layer / File(s) Summary
CLI configuration and startup wiring
node/cli-opt/src/lib.rs, node/cli/src/cli.rs, node/cli/src/command.rs, node/service/src/lazy_loading/mod.rs
Adds startup-delay and cache-path configuration, wires both values into lazy-loading, and redacts the startup disclaimer’s RPC URL.
Persistent state cache
node/service/src/lazy_loading/state_cache.rs
Adds sharded content-addressed storage, atomic writes, URL helpers, and fingerprint-scoped pinned fork-block persistence.
Cached RPC requests and retry backoff
node/service/src/lazy_loading/rpc_client.rs
Adds optional response caching for storage RPCs, deterministic cache keys, and jittered exponential retry backoff.
Backend cache integration and fork resolution
node/service/src/lazy_loading/substrate_backend.rs
Initializes the cache, reuses or resolves fork checkpoints, persists automatic selections, and increases the RPC timeout.
Development test synchronization
test/helpers/common.ts, test/moonwall.config.json, test/suites/dev/common/test-block/test-block-1.ts, test/suites/dev/moonbase/*
Adds polling, transaction-settlement, fee-history, pending-transaction, and subscription-delivery synchronization, and configures lazy-loading cache and retry settings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RunCmd
  participant SubstrateBackend
  participant StateCache
  participant RPC
  RunCmd->>SubstrateBackend: provide lazy-loading options
  SubstrateBackend->>StateCache: load pinned fork block
  SubstrateBackend->>RPC: create cached RPC client
  RPC->>StateCache: read cached storage response
  RPC->>SubstrateBackend: return cached or fetched response
  SubstrateBackend->>StateCache: persist resolved fork block
Loading

Suggested labels: D2-notlive, D9-needsaudit👮

Suggested reviewers: librelois

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and correctly points to the lazy-loading CI/test changes, even if it is broader than the full scope.
Description check ✅ Passed The description matches the changeset and accurately summarizes the lazy-loading cache, CLI flags, and test de-flakes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch artur/ci-fixes-lazy-loading

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.

@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: 1

🧹 Nitpick comments (2)
test/suites/dev/moonbase/test-eth-fee/test-eth-fee-history.ts (1)

60-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reusing the existing sleep helper instead of inline setTimeout.

requestFeeHistory uses new Promise((resolve) => setTimeout(resolve, 100)) which is identical to the sleep(100) helper already exported from test/helpers/common.ts. Since this file already imports from the helpers barrel (sealUntilTxPoolEmpty at line 35), importing sleep would eliminate the duplication.

♻️ Optional refactor
 async function requestFeeHistory(
   blockCount: string | number,
   reward_percentiles: number[],
   expectedBaseFeeLength: number
 ): Promise<FeeHistory> {
   let result = (await customDevRpcRequest("eth_feeHistory", [
     blockCount,
     "latest",
     reward_percentiles,
   ])) as FeeHistory;
   for (let i = 0; i < 50 && result.baseFeePerGas.length < expectedBaseFeeLength; i++) {
-    await new Promise((resolve) => setTimeout(resolve, 100));
+    await sleep(100);
     result = (await customDevRpcRequest("eth_feeHistory", [
       blockCount,
       "latest",
       reward_percentiles,
     ])) as FeeHistory;
   }
   return result;
 }
🤖 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 `@test/suites/dev/moonbase/test-eth-fee/test-eth-fee-history.ts` around lines
60 - 84, Replace the inline setTimeout promise in requestFeeHistory with the
existing sleep helper. Add sleep to the helpers-barrel import alongside
sealUntilTxPoolEmpty, then await sleep(100) during polling.
node/service/src/lazy_loading/state_cache.rs (1)

106-124: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Pinned-fork-block write is not atomic, unlike put.

set_pinned_fork_block writes directly via fs::write, while put uses a temp-file + atomic rename to avoid partial reads from concurrent writers. A crash or concurrent access mid-write here could leave a truncated file; pinned_fork_block degrades gracefully (returns None on any parse failure) so this isn't data-corrupting, but it's inconsistent with the atomicity guarantee the rest of this cache provides.

Separately: this file persists the caller-supplied fingerprint string verbatim and unencrypted to disk — see related comment in substrate_backend.rs regarding what value is passed as fingerprint there.

♻️ Align with the atomic write pattern used in `put`
 pub fn set_pinned_fork_block(&self, fingerprint: &str, hash_bytes: &[u8]) {
-		let _ = fs::write(
-			self.pinned_fork_block_path(),
-			format!("{}\n0x{}\n", fingerprint, hex::encode(hash_bytes)),
-		);
+		let path = self.pinned_fork_block_path();
+		let tmp = path.with_extension(format!("tmp-{}", std::process::id()));
+		let contents = format!("{}\n0x{}\n", fingerprint, hex::encode(hash_bytes));
+		if fs::write(&tmp, contents).is_ok() {
+			if fs::rename(&tmp, &path).is_err() {
+				let _ = fs::remove_file(&tmp);
+			}
+		} else {
+			let _ = fs::remove_file(&tmp);
+		}
 	}
🤖 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 `@node/service/src/lazy_loading/state_cache.rs` around lines 106 - 124, Update
set_pinned_fork_block to use the same temporary-file-and-atomic-rename strategy
as put instead of writing directly with fs::write. Preserve the existing
serialized fingerprint and hash format, handle temporary-file
creation/write/rename failures consistently with put, and ensure incomplete
writes cannot be observed by pinned_fork_block.
🤖 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 `@node/service/src/lazy_loading/substrate_backend.rs`:
- Around line 1515-1570: Replace the raw
`lazy_loading_config.state_rpc.as_str()` used by `cache_fingerprint` with a
deterministic sanitized fingerprint that cannot contain credentials or API keys,
and use it consistently for `pinned_fork_block`, `set_pinned_fork_block`, and
the stale-pin warning in the checkpoint-resolution logic. Update the
lazy-loading startup disclaimer in `lazy_loading::` (around the relevant startup
logging code) to redact the displayed RPC endpoint as well, using existing
URL-redaction utilities where available.

---

Nitpick comments:
In `@node/service/src/lazy_loading/state_cache.rs`:
- Around line 106-124: Update set_pinned_fork_block to use the same
temporary-file-and-atomic-rename strategy as put instead of writing directly
with fs::write. Preserve the existing serialized fingerprint and hash format,
handle temporary-file creation/write/rename failures consistently with put, and
ensure incomplete writes cannot be observed by pinned_fork_block.

In `@test/suites/dev/moonbase/test-eth-fee/test-eth-fee-history.ts`:
- Around line 60-84: Replace the inline setTimeout promise in requestFeeHistory
with the existing sleep helper. Add sleep to the helpers-barrel import alongside
sealUntilTxPoolEmpty, then await sleep(100) during polling.
🪄 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: CHILL

Plan: Pro

Run ID: ec841cf8-554a-4948-8e8e-6e9d5a3efd2d

📥 Commits

Reviewing files that changed from the base of the PR and between 38fae69 and beca935.

📒 Files selected for processing (12)
  • node/cli-opt/src/lib.rs
  • node/cli/src/cli.rs
  • node/cli/src/command.rs
  • node/service/src/lazy_loading/mod.rs
  • node/service/src/lazy_loading/rpc_client.rs
  • node/service/src/lazy_loading/state_cache.rs
  • node/service/src/lazy_loading/substrate_backend.rs
  • test/helpers/common.ts
  • test/moonwall.config.json
  • test/suites/dev/common/test-block/test-block-1.ts
  • test/suites/dev/moonbase/test-balance/test-balance-existential.ts
  • test/suites/dev/moonbase/test-eth-fee/test-eth-fee-history.ts

Comment thread node/service/src/lazy_loading/substrate_backend.rs
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 10, 2026
@arturgontijo
arturgontijo requested a review from manuelmauro July 13, 2026 10:37
manuelmauro
manuelmauro previously approved these changes Jul 13, 2026

@manuelmauro manuelmauro 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.

LGTM!

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
@arturgontijo
arturgontijo merged commit 8c60515 into master Jul 13, 2026
40 checks passed
@arturgontijo
arturgontijo deleted the artur/ci-fixes-lazy-loading branch July 13, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B0-silent Changes should not be mentioned in any release notes D2-notlive PR doesn't change runtime code (so can't be audited) not-breaking Does not need to be mentioned in breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants