Skip to content

feat(settlement): persist block → proof-request mapping in the DB + RPC - #631

Merged
kariy merged 2 commits into
mainfrom
feat/settlement-block-proof-mapping
Jul 6, 2026
Merged

kariy merged 2 commits into
mainfrom
feat/settlement-block-proof-mapping

Conversation

@kariy

@kariy kariy commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Records which proof settled each block, so a settled block can be traced back to its proof (e.g. on the Succinct explorer), and exposes it over JSON-RPC. Builds on the settlement observability work (proof-cost metrics, request/tx-hash logging).

Settlement proves a whole block range per batch (one proof per batch), so every block in a settled batch maps to that batch's single proof (per-block keying → O(1) lookup by any block number).

Prover-agnostic by design

The stored value is a ProofId (katana-primitives): an opaque, prover-defined identifier. The TEE/SP1 backend fills it with the Succinct prover-network request ID, but the storage / provider / RPC layers stay agnostic. The prover type is a node-level config constant, so it is not stored per block — it can be inferred from config. A future proving backend supplies its own opaque id with zero changes below the backend.

Changes

  • SDK (amd-sev-snp-attestation-sdk, merged as PR #2, rev 4ce5017): RawProof retains the network request_id instead of discarding it after the cost lookup.
  • Threaded up as Option<ProofId> through ProvingBackend::provesettle_batch (mock / off-network → None).
  • New per-block SettlementProofs table (block → ProofId); best-effort write beside the settled-block checkpoint, so a DB failure never stalls settlement.
  • SettlementProof{Provider,Writer} provider traits + impls over the db and forked providers.
  • katana_getBlockProof(block){ block, proofId } (proofId as 0x-hex), or null if the block was not settled with a proof.
  • Separate commit: pin sp1_sdk::network::prover=info in the default log filter so the prover-network submit log is captured for every SP1 proof.

Testing

  • Unit tests: ProofId codec round-trip, table registration, provider write/read round-trip, and persist_block_proofs. All green.
  • Full katana binary compiles; clippy clean on the changed crates.
  • End-to-end (real Succinct prover) not run locally — covered by unit tests.

🤖 Generated with Claude Code

…lter

Pin sp1_sdk::network::prover=info in DEFAULT_LOG_FILTER so the prover-network
submit log ("Created request <id> in transaction <tx>") is captured for every
SP1 settlement proof, independent of the trailing global default. Adds a test
that the default filter parses (a malformed directive otherwise only fails at
node startup).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: 8d93675 Previous: 89628fa Ratio
FinalityStatus/compress 1 ns/iter (± 0) 0 ns/iter (± 0) +∞

This comment was automatically generated by workflow using github-action-benchmark.

CC: @kariy

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codec benchmark diff vs main

Benchmark Baseline (ns) Current (ns) Δ
CompiledClass(fixture)/compress 2626367 2665983 +1.51%
CompiledClass(fixture)/decompress 2773110 2815640 +1.53%
ExecutionCheckpoint/compress 33 32 -3.03%
ExecutionCheckpoint/decompress 26 23 -11.54%
PruningCheckpoint/compress 33 32 -3.03%
PruningCheckpoint/decompress 25 23 -8.00%
VersionedHeader/compress 635 654 +2.99%
VersionedHeader/decompress 903 840 -6.98%
StoredBlockBodyIndices/compress 78 76 -2.56%
StoredBlockBodyIndices/decompress 39 34 -12.82%
StorageEntry/compress 172 143 -16.86%
StorageEntry/decompress 160 139 -13.13%
ContractNonceChange/compress 173 146 -15.61%
ContractNonceChange/decompress 304 238 -21.71%
ContractClassChange/compress 216 188 -12.96%
ContractClassChange/decompress 312 259 -16.99%
ContractStorageEntry/compress 169 153 -9.47%
ContractStorageEntry/decompress 375 312 -16.80%
GenericContractInfo/compress 129 132 +2.33%
GenericContractInfo/decompress 106 101 -4.72%
Felt/compress 90 80 -11.11%
Felt/decompress 63 52 -17.46%
BlockHash/compress 89 80 -10.11%
BlockHash/decompress 62 51 -17.74%
TxHash/compress 91 80 -12.09%
TxHash/decompress 62 53 -14.52%
ClassHash/compress 89 81 -8.99%
ClassHash/decompress 63 52 -17.46%
CompiledClassHash/compress 90 80 -11.11%
CompiledClassHash/decompress 63 51 -19.05%
BlockNumber/compress 51 46 -9.80%
BlockNumber/decompress 24 24 +0.00%
TxNumber/compress 51 46 -9.80%
TxNumber/decompress 25 24 -4.00%
FinalityStatus/compress 0 1 +Infinity%
FinalityStatus/decompress 11 11 +0.00%
TypedTransactionExecutionInfo/compress 15126 17735 +17.25%
TypedTransactionExecutionInfo/decompress 3738 3635 -2.76%
VersionedContractClass/compress 372 382 +2.69%
VersionedContractClass/decompress 803 785 -2.24%
MigratedCompiledClassHash/compress 171 145 -15.20%
MigratedCompiledClassHash/decompress 157 138 -12.10%
ContractInfoChangeList/compress 1402 1388 -1.00%
ContractInfoChangeList/decompress 2250 2135 -5.11%
BlockChangeList/compress 606 608 +0.33%
BlockChangeList/decompress 911 857 -5.93%
ReceiptEnvelope/compress 26478 29861 +12.78%
ReceiptEnvelope/decompress 6478 6088 -6.02%
TrieDatabaseValue/compress 148 165 +11.49%
TrieDatabaseValue/decompress 249 211 -15.26%
TrieHistoryEntry/compress 270 288 +6.67%
TrieHistoryEntry/decompress 286 246 -13.99%

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Runner: AMD EPYC 7763 64-Core Processor (4 cores) · 15Gi RAM

Record which proof settled each block so a settled block can be traced back to
its proof (e.g. on the Succinct explorer), and expose it over JSON-RPC.

- SDK (amd-sev-snp-attestation-sdk 4ce5017): surface the network request_id on
  RawProof instead of discarding it after the cost lookup.
- ProofId (katana-primitives): a prover-agnostic, opaque proof identifier. The
  TEE/SP1 backend fills it with the Succinct request_id; the storage, provider,
  and RPC layers stay agnostic. The prover type is a node-level config constant,
  so it is not stored per block. Threaded through ProvingBackend::prove ->
  settle_batch.
- New per-block SettlementProofs table (block -> ProofId); every block in a
  settled batch maps to that batch's single proof. Best-effort write beside the
  settled-block checkpoint, so a DB failure never stalls settlement.
- SettlementProof{Provider,Writer} provider traits + impls over the db and
  forked providers.
- katana_getBlockProof(block) RPC returning { block, proofId } (proofId as
  0x-hex), or null if the block was not settled with a proof.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kariy
kariy force-pushed the feat/settlement-block-proof-mapping branch from 8bacb7d to 8d93675 Compare July 6, 2026 14:00
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.50000% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.07%. Comparing base (9bde0ae) to head (8d93675).
⚠️ Report is 486 commits behind head on main.

Files with missing lines Patch % Lines
crates/settlement/src/backend/tee/prover.rs 0.00% 11 Missing ⚠️
crates/settlement/src/service.rs 71.42% 8 Missing ⚠️
crates/rpc/rpc-server/src/settlement.rs 0.00% 6 Missing ⚠️
...torage/provider/provider/src/providers/fork/mod.rs 0.00% 6 Missing ⚠️
crates/settlement/src/backend/tee/mod.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #631      +/-   ##
==========================================
- Coverage   73.32%   68.07%   -5.25%     
==========================================
  Files         209      334     +125     
  Lines       23132    46717   +23585     
==========================================
+ Hits        16961    31802   +14841     
- Misses       6171    14915    +8744     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kariy
kariy merged commit 5f81a1f into main Jul 6, 2026
19 of 21 checks passed
@kariy
kariy deleted the feat/settlement-block-proof-mapping branch July 6, 2026 16:17
kariy added a commit that referenced this pull request Jul 20, 2026
…ter restart

A crash or restart between proving and settling discarded the prepared
payload, forcing a fresh (paid) SP1 proving round for the identical
range. The proof itself still exists on the prover network though — so
persist just its network reference and recover it instead.

- The moment proving yields a ProofId, record it as a PendingBatchProof
  (range + id) in a new singleton PendingSettlementProofs table —
  before update_state submission. Cleared when the batch settles.
- On an in-memory miss, prepare_batch resolves a matching persisted
  reference via the new ProvingBackend::recover, which fetches the
  fulfilled proof from the network (SDK-side: the new
  Program::recover_proof — sp1_sdk wait_proof by request id, same
  plumbing as gen_raw_proof minus the paid request; SDK rev bump) and
  rebuilds the payload from a freshly built attestation. Only
  range-derived fields enter the payload, so a rebuilt attestation +
  recovered proof is byte-equivalent in everything on-chain validation
  checks. Mock proving reports recovery as unsupported (it is free
  anyway); any recovery failure falls back to fresh proving.
- Escape hatch for externally invalidated proofs: an update_state that
  REVERTS in execution (e.g. a TEE-registry trust-root rotation between
  proving and submission) drops the payload and its persisted
  reference, so the retry proves fresh. Pre-execution failures (fees,
  nonce, transport) keep both — that's the incident case where reuse is
  the whole point.

Table addition is version-neutral (same as SettlementProofs in #631).
New proof_reuse tests cover restart recovery, unsupported/failed
recovery fallback, stale-range references, and reference lifetime
across failed submissions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kariy added a commit that referenced this pull request Jul 20, 2026
…it (#639)

## Why

When `update_state` submission fails after proving succeeded (fees,
nonce, transient RPC), the settle loop retried the **whole pipeline** —
including a fresh, paid SP1 proving round — every ~60s for the identical
block range. Production incident (mainnet enclave, 2026-07-17): the
settlement account was short on STRK for the submission fee, and the
node burned ~0.4 PROVE per retry (~24 PROVE/hour) re-proving range
96–105 in a loop that could never succeed. A node restart had the same
cost: the in-flight proof died with the process even though the prover
network still held it.

## What

**Prove once, retry the submission** (`Worker::settle_batch`): the
payload is obtained at most once per attempt and lives as a local scoped
to it; an inner loop retries only the `update_state` submission (own
backoff, failure metrics, cursor-idempotency re-read,
shutdown-responsive via the run loop's oneshot receiver).
Reuse-while-unchanged, spend-on-success, drop-on-cursor-move, and
drop-on-execution-revert are all control flow rather than cache state.
Terminal conditions (prove failure, execution revert, on-chain cursor
moved) return `Err` to the run loop, which backs off and recomputes the
range. `PiltoverClient::update_state` takes `&PiltoverInput` (the
generated bindings don't derive `Clone`).

**Recovery across restarts (persisted network reference):** the moment
proving yields a `ProofId`, it is persisted as a `PendingBatchProof`
(range + id) in a new singleton `PendingSettlementProofs` table —
*before* submission — and cleared when the batch settles. On startup of
an attempt, a matching reference is resolved through the new
`ProvingBackend::recover`, which fetches the fulfilled proof from the
prover network (SDK-side: new `Program::recover_proof` — `sp1_sdk`
`wait_proof` by request id, the same plumbing as `gen_raw_proof` minus
the paid request; SDK rev bump to
cartridge-gg/amd-sev-snp-attestation-sdk@2685854) and rebuilds the
payload from a freshly built attestation. Only range-derived fields
enter the payload, so a rebuilt attestation + recovered proof is
equivalent in everything on-chain validation checks. Mock proving
reports recovery as unsupported (it's free anyway); recovery failure
(expired/unknown id) falls back to fresh proving.

**Escape hatch:** an `update_state` that *reverts in execution* (e.g. a
TEE-registry trust-root rotation invalidated the proof between proving
and submission) drops the payload **and** its persisted reference, so
the next attempt proves fresh — retry-with-same-proof only applies to
pre-execution failures (fees, nonce, transport), the incident class
where it's wanted.

Reuse is sound because the payload is validity-stable for a fixed
historical range — `report_data` is a pure function of the settled
range, and Piltover validates the proof against the range-derived
commitment, not any timestamp embedded at proving time. The
`PendingSettlementProofs` table addition is version-neutral (same as
`SettlementProofs` in #631). `proof_generation_seconds` now records only
actual proving rounds.

**Operator-visible log change:** transient submission failures emit
`Failed to submit state update; will retry with the same proof.`;
`Failed to settle block range; will retry.` now marks terminal attempt
failures only. Docs updated accordingly.

## Tests

The `proof_reuse` suite drives `settle_batch`'s real submission-retry
loop under a paused tokio clock against an unreachable Piltover endpoint
(the incident shape), shut down after bounded virtual time: one prove
across many submission retries with the persisted reference surviving,
restart recovery from the reference, unsupported/failed recovery
fallback to fresh proving, stale-range reference skip, prove-failure
early return, and reference clearing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.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.

1 participant