feat(maturity): RpcSafe / RpcFinalized strategies that follow the source node's block tags - #1330
DylanVerstraete wants to merge 3 commits into
Conversation
PR SummaryHigh Risk Overview Runtime & primitives: Off-chain maturity model: Attestor & archiver: Resolve on-chain strategy with Continuity / proof-gen: RPC errors: Unsupported block tags are treated as permanent (no reconnect retry), matching Reviewed by Cursor Bugbot for commit e666cff. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 91b862b. Configure here.
2e50f48 to
dbf9587
Compare
…econnect on an unserved tag Review follow-ups (#1330): - proof-gen: `block_confirmation_depth` pinned in config while the on-chain MaturityStrategy is RpcSafe/RpcFinalized now fails startup instead of warning. A fixed depth cannot reproduce a tag schedule, so the prover would confirm blocks the attestors have not attested. Depth-vs-depth disagreements keep the existing WARN. - continuity: `get_block_number_by_tag` preserves the typed `FailedToGetBlockByTag` cause (was stringified) and `run()` returns it without the reconnect-and-retry loop: a node answering `null` for a tag is a permanent property of that node, and reconnecting only churned the shared client for every other in-flight request. Classifier `eth::anyhow_chain_is_unsupported_block_tag` with tests.
…econnect on an unserved tag Review follow-ups (#1330): - proof-gen: `block_confirmation_depth` pinned in config while the on-chain MaturityStrategy is RpcSafe/RpcFinalized now fails startup instead of warning. A fixed depth cannot reproduce a tag schedule, so the prover would confirm blocks the attestors have not attested. Depth-vs-depth disagreements keep the existing WARN. - continuity: `get_block_number_by_tag` preserves the typed `FailedToGetBlockByTag` cause (was stringified) and `run()` returns it without the reconnect-and-retry loop: a node answering `null` for a tag is a permanent property of that node, and reconnecting only churned the shared client for every other in-flight request. Classifier `eth::anyhow_chain_is_unsupported_block_tag` with tests.
2d360ff to
83d62e5
Compare
|
Rebased onto usc-dev; see above. |
Overview
Labels (1 changes)
-org.opencontainers.image.created=2026-06-27T04:19:04.617438+00:00
+org.opencontainers.image.created=2026-08-17T09:02:45.677319+00:00
org.opencontainers.image.description=The Ubuntu container image maintained by Canonical
Ubuntu is a Debian-based Linux operating system that runs from the desktop to the cloud, to all your internet connected things.
It is the world's most popular operating system across public clouds and OpenStack clouds.
It is the number one platform for containers; from Docker to Kubernetes to LXD, Ubuntu can run your containers at scale.
Fast, secure and simple, Ubuntu powers millions of PCs worldwide.
org.opencontainers.image.title=ubuntu
org.opencontainers.image.version=26.04Policies (2 improved, 0 worsened)
Packages and Vulnerabilities (47 package changes and 15 vulnerability changes)
Changes for packages of type
|
| Package | Versiongluwa/creditcoin3:latest |
Versiongluwa/creditcoin3:latest |
|
|---|---|---|---|
| ♾️ | base-files | 14ubuntu6.1 |
14ubuntu6.2 |
| ♾️ | bsdutils | 1:2.41.3-3ubuntu2 |
1:2.41.3-3ubuntu2.2 |
| ♾️ | curl | 8.18.0-1ubuntu2.3 |
8.18.0-1ubuntu2.5 |
| ♾️ | diffutils | 1:3.12-1 |
1:3.12-1ubuntu0.1 |
| ♾️ | gnu-coreutils | 9.7-3ubuntu2 |
9.7-3ubuntu2.1 |
| ♾️ | gpgv | 2.4.8-4ubuntu3 |
2.4.8-4ubuntu3.1 |
| ♾️ | libattr1 | 1:2.5.2-4 |
1:2.5.2-4ubuntu0.1 |
| ♾️ | libaudit-common | 1:4.1.2-1build1 |
1:4.1.2-1ubuntu0.1 |
| ♾️ | libaudit1 | 1:4.1.2-1build1 |
1:4.1.2-1ubuntu0.1 |
| ♾️ | libblkid1 | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | libbz2-1.0 | 1.0.8-6build2 |
1.0.8-6ubuntu0.1 |
| ♾️ | libc-bin | 2.43-2ubuntu2.3 |
2.43-2ubuntu2.4 |
| ♾️ | libc-gconv-modules-extra | 2.43-2ubuntu2.3 |
2.43-2ubuntu2.4 |
| ♾️ | libc6 | 2.43-2ubuntu2.3 |
2.43-2ubuntu2.4 |
| ♾️ | libcurl4t64 | 8.18.0-1ubuntu2.3 |
8.18.0-1ubuntu2.5 |
| ♾️ | libgcrypt20 | 1.12.0-2ubuntu1 |
1.12.0-2ubuntu1.1 |
| ♾️ | libmount1 | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | libpam-modules | 1.7.0-5ubuntu3.1 |
1.7.0-5ubuntu3.2 |
| ♾️ | libpam-modules-bin | 1.7.0-5ubuntu3.1 |
1.7.0-5ubuntu3.2 |
| ♾️ | libpam-runtime | 1.7.0-5ubuntu3.1 |
1.7.0-5ubuntu3.2 |
| ♾️ | libpam0g | 1.7.0-5ubuntu3.1 |
1.7.0-5ubuntu3.2 |
| ♾️ | libpq5 | 18.4-0ubuntu0.26.04.1 |
18.6-0ubuntu0.26.04.1 |
| ♾️ | libsmartcols1 | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | libssh2-1t64 | 1.11.1-1ubuntu0.26.04.3 |
1.11.1-1ubuntu0.26.04.4 |
| ♾️ | libssl3t64 | 3.5.5-1ubuntu3.3 |
3.5.5-1ubuntu3.5 |
| ♾️ | libsystemd0 | 259.5-0ubuntu3.3 |
259.5-0ubuntu3.4 |
| ♾️ | libudev1 | 259.5-0ubuntu3.3 |
259.5-0ubuntu3.4 |
| ♾️ | libuuid1 | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | login | 1:4.16.0-2+really2.41.3-3ubuntu2 |
1:4.16.0-2+really2.41.3-3ubuntu2.2 |
| ♾️ | mount | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | openssl | 3.5.5-1ubuntu3.3 |
3.5.5-1ubuntu3.5 |
| ♾️ | openssl-provider-legacy | 3.5.5-1ubuntu3.3 |
3.5.5-1ubuntu3.5 |
| ♾️ | perl-base | 5.40.1-7ubuntu0.1 |
5.40.1-7ubuntu0.3 |
| ♾️ | util-linux | 2.41.3-3ubuntu2 |
2.41.3-3ubuntu2.2 |
| ♾️ | zlib1g | 1:1.3.dfsg+really1.3.1-1ubuntu3 |
1:1.3.dfsg+really1.3.1-1ubuntu3.1 |
Changes for packages of type golang (6 changes)
Changes for packages of type npm (6 changes)
| Package | Versiongluwa/creditcoin3:latest |
Versiongluwa/creditcoin3:latest |
|
|---|---|---|---|
| ♾️ | @types/node | 26.1.2 |
22.7.5 |
| ♾️ | node-gyp | 13.0.1 |
13.0.2 |
| ♾️ | picomatch | 4.0.5 |
4.0.7 |
| ♾️ | undici | 8.10.0 |
8.10.2 |
| ♾️ | undici-types | 8.3.0 |
6.21.0 |
| ♾️ | ws | 8.21.2 |
8.21.3 |
… source node's block tags
The existing maturity strategies are all fixed block offsets: `EvmSafe` and
`EvmFinalized` are the Ethereum-epoch approximations (32 / 64 blocks) and mean
64 s / 128 s on a 2 s rollup, where they say nothing about L1 posting. A fixed
count also cannot follow a stalled batcher.
This adds two on-chain strategy strings, `RpcSafe` and `RpcFinalized`, that the
off-chain components resolve through `eth_getBlockByNumber("safe" | "finalized")`
on the source node. On Ethereum that tracks justification / finality; on OP-Stack
and Arbitrum rollups it tracks L1 batch inclusion / L1 finality. The existing
strategies keep their exact behaviour, so no live chain changes schedule.
- primitives: `MaturityStrategy::{RpcSafe, RpcFinalized}`, `RpcBlockTag`,
`rpc_tag()`; pallet validator accepts the new strings (runtime wasm changes,
no spec bump here).
- eth: `Maturity { FixedLag, Tag }` with `mature_height`, and
`Client::get_block_number_by_tag` walking fallback providers like block fetches.
- streams: tip and roots streams take `maturity` instead of `finalization_lag`
and resolve it per head; the roots stream fetches the whole newly mature range
so a `safe` head that jumps by a batch is handled.
- attestor / archiver: resolve the on-chain strategy via
`stream_eth::maturity_from_strategy`.
- continuity / proof-gen: `confirmation_tag` makes the prover confirm blocks on
the same boundary the attestors attest on; the not-confirmed error reports the
effective window.
Verified with mock-RPC tests and live on Base Sepolia (Chainstack): attested
heights stayed 2-12 blocks below the node's `safe` head and never above it,
advancing in bursts as batches posted to L1.
…d provider `ArchiverEthProvider` forwards every live-node call to its ETH fallback but inherited the erroring default for `get_block_number_by_tag`, so a proof server configured with an `archiver_url` on an `RpcSafe` / `RpcFinalized` chain would fail `get_confirmed_last_block`. Forward it like `get_last_block`, teach the mock provider fixed `safe` / `finalized` heights, and cover both the tag-based confirmed tip and the archiver forwarding with tests.
…econnect on an unserved tag Review follow-ups (#1330): - proof-gen: `block_confirmation_depth` pinned in config while the on-chain MaturityStrategy is RpcSafe/RpcFinalized now fails startup instead of warning. A fixed depth cannot reproduce a tag schedule, so the prover would confirm blocks the attestors have not attested. Depth-vs-depth disagreements keep the existing WARN. - continuity: `get_block_number_by_tag` preserves the typed `FailedToGetBlockByTag` cause (was stringified) and `run()` returns it without the reconnect-and-retry loop: a node answering `null` for a tag is a permanent property of that node, and reconnecting only churned the shared client for every other in-flight request. Classifier `eth::anyhow_chain_is_unsupported_block_tag` with tests.
83d62e5 to
e666cff
Compare

Why
Every maturity strategy today is a fixed block offset.
EvmSafe/EvmFinalizedare the Ethereum-epoch approximations (32 / 64 blocks), which on a 2 s rollup such as Base mean 64 s / 128 s and say nothing about whether the block was posted to L1. A fixed count also cannot follow a stalled batcher, and the OP docs notesafecan trail for hours in that case.What
Two new on-chain strategy strings,
RpcSafeandRpcFinalized, resolved off-chain to the source node'ssafe/finalizedblock tags. Chain-agnostic on purpose: Ethereum, OP-Stack and Arbitrum nodes all serve these tags with their own correct meaning.EvmSafe/EvmFinalized/FixedDelaykeep their exact behaviour, so nothing changes for registered chains.MaturityStrategy::{RpcSafe, RpcFinalized},RpcBlockTag,rpc_tag();is_valid_maturity_strategyaccepts the strings. This changes the runtime wasm; no spec bump in this PR (release-owned), socheck-versionis expected red.Maturity { FixedLag(u64), Tag(BlockTag) }withmature_height, andClient::get_block_number_by_tagusing the same fallback-provider walk as block fetches.tipandrootstakematurityinstead offinalization_lagand resolve it per head. The roots stream now fetches the wholenext_unfetched..=maturerange, so asafehead that jumps by a full batch is handled; a failed tag lookup is logged and retried on the next head, never guessed.stream_eth::maturity_from_strategy.ContinuityConfig::confirmation_tagandEthRpcProvider::get_block_number_by_tagmake the prover confirm blocks on exactly the boundary the attestors attest on.BlockNotOnSourceChainnow reports the effective window (tip - confirmed) instead of the configured depth, which is0under a tag policy.Verification
New tests:
common/eth/tests/block_tag.rs(tag → number,null→ not found, clamp to observed head, fallback provider), primitives parse/accessor tests, pallet accept/reject cases, resolver test instream_eth. All suites of the touched crates pass; clippy-D warnings, fmt, taplo and cargo-machete clean;cargo check -p creditcoin3-runtimepasses.Live on Base Sepolia (Chainstack), 3 attestors, local dev node, chain flipped to
RpcSafe:safeAttested heights never exceeded
safe; they advance in bursts as batches post to L1 (~4–5 min on Base Sepolia). The proof server served a block 72 behindsafe(61 continuity roots) and returnedBlockNotOnSourceChainfor blocks above it.Notes for operators
RpcSafe/RpcFinalizedneeds the runtime upgrade first (the pallet validator rejects unknown strings). For the live test above the storage value was written directly on a dev node.eth_getBlockByNumber(tag, false)per source head per component.Since approval: rebased onto
usc-dev(picked up #1320, #1326, #1335, #1343); no code changes.