Proving v0: SP1 guest and host for one Igneum block, real-block fixtures, versioned ProofSystem trait, WSL2 package for the RTX 5090 run
proving/igneum-prove: core (port of igneum-exec at fb33069 as the block statement), program (SP1 v6.8.1 guest), host (execute, core, compressed; ProofSystem trait with the stub and the SP1 implementation), export (cuts a block out of igneum_exportSegments and checks every state root against the node's). Fixtures block-78-increment and block-56-transfers from the 3-node simnet. proving/windows-wsl2: SETUP-PROVER.bat, setup-wsl.sh, PROVE-BLOCK.bat, make-package.sh. docs/plans/proving-v0.md: the devnet v4 shard plan, what tonight's proof shows and does not, the morning acceptance line. Mac CPU baseline (block 78: 626 k cycles, core 22.0 s and 7.3 MB, compressed 55.7 s and 1.27 MB, both verified) appended to docs/bench-log.md, left uncommitted because that file carries another agent's pending changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
176965595b
commit
326953e0ac
31 changed files with 10348 additions and 0 deletions
41
docs/plans/proving-v0.md
Normal file
41
docs/plans/proving-v0.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Proving v0 and the devnet v4 shard plan
|
||||
|
||||
3 October 2026. Code in `proving/` (SP1 v6.8.1 guest and host, the versioned `ProofSystem` trait, two real-block fixtures, the Windows WSL2 package). Status words follow `docs/spec/00-overview.md` 0.2. Nothing here is a mainnet number.
|
||||
|
||||
## What tonight's single-block proof is
|
||||
|
||||
The guest (`proving/igneum-prove/program`) re-executes one Igneum chain block with the execution layer's own rules: the transaction decoder is the node's crate (`igneum-evm-types`), the executor is a port of `igneum-exec` at commit fb33069 (rewards by rule, the nonce-rule skip, two-dimensional gas with the prototype pgas table, fee flows with the 80/20 developer split, the registry population on CREATE, the state root over every non-empty account through alloy-trie). It commits the chain id, the block number and hash, a commitment to the ordered transactions, the pre-state root, the post-state root, the receipts root, gas and pgas used, and the executed and skipped counts (`igneum_prove_core::ProveOutput`, 200 bytes, fixed layout).
|
||||
|
||||
The fixtures are blocks that happened: `proving/fixtures/block-78-increment.json` (the `increment(5)` call on the Counter contract, one executed transaction and one duplicate skipped by the nonce rule, 10 accounts) and `block-56-transfers.json` (the three funding transfers, 5 accounts), cut by `igneum-prove-export` from `tools/evm-smoke/seq.json`, the `igneum_exportSegments` dump of the 3-node simnet run. The exporter replays all 79 segments from genesis through the port and refuses to write unless every segment's state root equals the node's; it did (Measured, 3 October 2026, final root `0x5b18b3a5...`). So the proof statement is "the node's executor, run twice", which is what design 2.1 asks for, up to the duplicate of the executor that the port is until the executor becomes the no-network library of design 2.1.
|
||||
|
||||
The host runs the statement natively first and refuses to prove a fixture whose expected roots it does not reproduce, then runs SP1 in three modes behind the trait (`proof_system.rs`): execute (cycle count, no proof), core (`prove_shard`), compressed (`aggregate` over one shard). Every proof is verified and its public values are compared with the native run. `SP1_PROVER=cuda` selects the GPU prover through the same binary (feature `cuda`).
|
||||
|
||||
## What it does not show
|
||||
|
||||
| Not shown | Why | Where it goes |
|
||||
|---|---|---|
|
||||
| Shard time at `S_p` pgas on a 12 GB card (the phase 2 gate, ledger P1, overclaim 27) | the fixture blocks carry 600 and 1,488 pgas, not a shard's budget | devnet v4 shard plan below, 3060-class card |
|
||||
| MPT witnesses | the whole in-memory devnet state (5 to 10 accounts) is the input; a real shard gets the touched accounts, slots and trie nodes (design 5.1), and the trie-proof share of pgas (R3) is unmeasured | witness generator in the node, v4 |
|
||||
| Recursion over shards and over the chain (segment N verifies N-1) | v0 aggregates one shard: SP1's compress stage on the block | v4 aggregator |
|
||||
| The Groth16 or Plonk wrapper for light clients (ledger P3, overclaim 25) | not run; needs SP1's circuit artifacts and a measurement on consumer hardware | R4, phase 2 benchmark |
|
||||
| The prover's payout key in the statement (ledger P12) | `ShardWitness.prover` is carried, not committed | guest change, v4 |
|
||||
| pgas calibration (R1) | the table is the prototype; tonight's cycles per EVM gas are one data point | calibrate per opcode in SP1 with three input sizes |
|
||||
|
||||
## The shard plan for devnet v4 (Designed)
|
||||
|
||||
1. **Cut.** The native executor emits per segment the transaction boundaries with cumulative gas and pgas and the state root at each boundary (design 5.1). The planner cuts at boundaries so each shard's pgas is at most `S_p`; one transaction above `S_p` is one shard proven with continuations (SP1 checkpoints its own execution; approximate). The plan is a pure function of the trace, so every node computes the same shard list and the same ids `(segment hash, shard index)`.
|
||||
2. **Statement per shard.** From pre-root `r_i` and the committed transaction list of shard i, running the executor yields `r_{i+1}` and receipts root `h_i`, with the executed set and the prover's payout key as public outputs (P12). The witness (touched accounts, slots, MPT nodes, block environment) is produced by any full node from its own execution and served over the proving gossip; it is not consensus data. Tonight's guest is this statement with the whole state as the witness and one shard per block.
|
||||
3. **Claim and sortition (spec 7.2).** Eligible provers are the vote keys above dust (100 blue blocks in the 30-day window). For each shard, draws `r_n = H("igneum-shard/" || epoch_seed || shard_id || n) mod N` over the window's blue blocks pick 8 distinct keys by weight. They hold the shard for 10 DAA seconds from the moment the segment is executed: a valid proof by one of them, included in a block in the window, earns the shard's part of the pool; after the window anyone's first valid proof earns it. No claim and no bond (ledger P8, P9). Parameters 8 and 10 s are set on the devnet from the shard-time distribution across three prover speeds (R7, fastest prover under 25% of shards; one key versus 1,000 keys of equal weight win the same number, F17).
|
||||
4. **Aggregation by recursion (design 5.3).** Shard proofs of a segment fold into one segment proof (SP1 compress, recursion over the shard proofs, not over the witness as v0 does); the segment proof for N also verifies the segment proof for N-1 so one proof attests the chain of state and a client keeps only the latest. The aggregator is anyone, paid the aggregator share. The wrapper to bn254 for the bridge verifier and phones is measured, not assumed (R4).
|
||||
5. **Proof records and the veto (design 5.4, 5.5, spec 7.2 item 5).** A block carries zero or more `ProofRecord {version, segment, pre_root, post_root, receipts, provers, aggregator, proof}`; full nodes verify the proof natively (a compressed proof, tonight's verify time is the first number for that) and run the native-execution veto: the block is invalid if the record names a chain block off the carrying block's own selected-parent chain or if `post_root` or `receipts` differ from the native execution of that segment along that chain. The test is relative to the carrying block's past, never to the validating node's current chain (ledger P11). A forged proof from a soundness bug is therefore rejected by every full node; the damage is bounded to light clients.
|
||||
6. **Devnet v4 run.** Nodes emit shard plans; miners run the host as a prover service that takes gossiped witnesses and returns shard proofs; a block producer includes records. Acceptance for the execution layer's A5 and A6 (`docs/design/execution-layer.md` 9.2): records within 60 s of 95% of segments, a wrong `post_root` makes the block invalid on every other node.
|
||||
|
||||
## The Mac CPU baseline (Measured, 3 October 2026, `docs/bench-log.md`)
|
||||
|
||||
block-78-increment on the Apple M5 Max CPU under load, SP1 v6.8.1: 626,246 cycles (14 per EVM gas), core proof 22.0 s and 7.3 MB (verify 0.16 s), compressed proof 55.7 s and 1.27 MB (verify 0.03 s), both verified and both reproducing the node's post-state root `0x5b18b3a5...`. block-56-transfers: 549,469 cycles. These are fixed-overhead numbers for a block far below one SP1 shard, not throughput.
|
||||
|
||||
## The morning run
|
||||
|
||||
On the Windows PC through WSL2 (`proving/windows-wsl2/`): SETUP-PROVER.bat (reboot once), SETUP-PROVER.bat again, pause mining, PROVE-BLOCK.bat. It proves `block-78-increment` on the RTX 5090 (execute, core, compressed, each verified) and then a core proof on the CPU, prints the RESULT lines and uploads the log to the intake. Acceptance line: **one Igneum devnet block proven on the RTX 5090 and verified, time and size recorded** (the RESULT lines go to `docs/bench-log.md` next to the Mac CPU baseline).
|
||||
|
||||
Two uncertainties the run settles. First, whether SP1's GPU prover runs under WSL2 at all with the 5090 (Blackwell, compute capability 12.0; the documentation lists 8.0 or higher and 24 GB of VRAM, so it should; the server binary is prebuilt for Linux x86_64 and the SDK downloads it, and the WSL CUDA driver path is the usual failure point). Second, how long the compressed proof takes on one consumer card for a 1,488-pgas block: this is the first point on the curve that sets `S_p`, and no shard has been proven on any card before this (ledger P1).
|
||||
18
proving/README.md
Normal file
18
proving/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# proving
|
||||
|
||||
Igneum proving v0 (3 October 2026): the first SP1 proof of an Igneum block. Plan and scope: `docs/plans/proving-v0.md`.
|
||||
|
||||
- `igneum-prove/`: Cargo workspace. `core` (the block statement, a port of the execution layer's executor), `program` (the SP1 guest), `host` (execute, core, compressed modes; the versioned `ProofSystem` trait with the stub and the SP1 implementation in `host/src/proof_system.rs`), `export` (cuts a real block out of an `igneum_exportSegments` dump and checks the port against the node's state roots).
|
||||
- `fixtures/`: real simnet blocks, `block-78-increment.json` (the Counter `increment(5)` call) and `block-56-transfers.json` (three transfers).
|
||||
- `windows-wsl2/`: SETUP-PROVER.bat, PROVE-BLOCK.bat and the Linux scripts for the project lead's PC; `make-package.sh` builds the zip.
|
||||
|
||||
Build and run on a machine with the SP1 toolchain (`curl -L https://sp1up.succinct.xyz | bash && sp1up`):
|
||||
|
||||
```
|
||||
cd proving/igneum-prove
|
||||
cargo build --release -p igneum-prove-export
|
||||
./target/release/igneum-prove-export ../../tools/evm-smoke/seq.json 78 ../fixtures/block-78-increment.json
|
||||
cargo build --release -p igneum-prove-host # add --features igneum-prove-host/cuda on Linux x86_64 with an NVIDIA card
|
||||
./target/release/igneum-prove-host ../fixtures/block-78-increment.json --mode execute # native check + cycle count
|
||||
SP1_PROVER=cuda ./target/release/igneum-prove-host ../fixtures/block-78-increment.json --mode all --out results.json
|
||||
```
|
||||
316
proving/fixtures/block-56-transfers.json
Normal file
316
proving/fixtures/block-56-transfers.json
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
{
|
||||
"format": "igneum-prove-fixture-v0",
|
||||
"source": "tools/evm-smoke/seq.json, the igneum_exportSegments export of the 3-node simnet run of 3 October 2026 (execution-layer commit fb33069), replayed from genesis through igneum-prove-core; block 56 holds the three funding transfers",
|
||||
"block": {
|
||||
"chain_id": 4463,
|
||||
"env": {
|
||||
"number": 56,
|
||||
"hash": "0xc47bacc89a3318140e66912717b958057bdbe72c337162a0667a0bbf44670a22",
|
||||
"parent_hash": "0x2bcabda995747c4221658d177995a64c94bc92202b02b8ef83b8bfd9cdb5709a",
|
||||
"timestamp": 1791064849,
|
||||
"miner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"prevrandao": "0x17919bfdc85b8b923bd674ac408b5ed0644e22fe2c7a8e19d9fc2197388270ee",
|
||||
"base_fee_exec": 1000000000,
|
||||
"base_fee_proving": 1000000000
|
||||
},
|
||||
"block_hashes": [
|
||||
[
|
||||
0,
|
||||
"0x3c19f19d3d77bfc2f367482eb7f28aa907ca5d31470c7d7722a68f44436236d5"
|
||||
],
|
||||
[
|
||||
1,
|
||||
"0x4a86d6afa55fa5a073e43a5f5d3ac423edcfa6f7815690eb23f9a090b20395bc"
|
||||
],
|
||||
[
|
||||
2,
|
||||
"0x07b68c40a8a8eb6826442d9cca7dbd08882a0c02130ea6ee7eaf7f729e081ea3"
|
||||
],
|
||||
[
|
||||
3,
|
||||
"0xf15ad0bcceb161e14082f4163c44a8134dd587f192c8dd1737db00c6db24fa1b"
|
||||
],
|
||||
[
|
||||
4,
|
||||
"0x22212b7c5792ff6743f48caa1ba1f0587772723cb7ae1572485bb73e42242c16"
|
||||
],
|
||||
[
|
||||
5,
|
||||
"0x32968867f32f5ad1580f834fbad5cdc07ab96df4d3346b4251893a5d3bb719b6"
|
||||
],
|
||||
[
|
||||
6,
|
||||
"0xa1548b817ef1c597ad91064886d3a00b86ec5aa57889d085881329d584ed3214"
|
||||
],
|
||||
[
|
||||
7,
|
||||
"0x52f35b7d71efaa3181660b0e0689e19be5996dfd0b52bba0518dc8ab099be968"
|
||||
],
|
||||
[
|
||||
8,
|
||||
"0xcb602a65d6788ea471038522506dfbec15775183d0fb552f7d0ab03ea95315ef"
|
||||
],
|
||||
[
|
||||
9,
|
||||
"0xa7a0015c122f749591ae5c4b6fae8a035cba2e3c0f4500d6c915a61f4f534c74"
|
||||
],
|
||||
[
|
||||
10,
|
||||
"0x3efdfcdc9fac9b0df2eb6fadae9deaada5abbcc3a2436164b3b061743f4c2aa5"
|
||||
],
|
||||
[
|
||||
11,
|
||||
"0x49c9f846adc3f61685d0075efa3d05aa29fcb2dad02d9e6f415508fa2adc23f1"
|
||||
],
|
||||
[
|
||||
12,
|
||||
"0xe084f75979bb4a34958883d0b599262c7719669119c88b3ce015500473ecac91"
|
||||
],
|
||||
[
|
||||
13,
|
||||
"0x9653df5f568099056e6d33bb79364dcb4c9760383b5f4f208bb9a14db1c156c4"
|
||||
],
|
||||
[
|
||||
14,
|
||||
"0xf65af06e2edf1ac8443992bd5affbf5f244a3b2ca2b572b6a7bea5ae1b1b9e60"
|
||||
],
|
||||
[
|
||||
15,
|
||||
"0xa7b1e86b1a99ab38f127637a4fdaf25ee1c18ceb294ef136f4bbf3c53321e29a"
|
||||
],
|
||||
[
|
||||
16,
|
||||
"0xac94a12363cf3e35e43c8a7aed2982c0f4445328c3349aa5cab232ad20f24600"
|
||||
],
|
||||
[
|
||||
17,
|
||||
"0xd97973cba8dbac8b5396738bef29e467459311305de6236af3a601916bf1a299"
|
||||
],
|
||||
[
|
||||
18,
|
||||
"0x345a0f4a3ca21c93f5a6b3bb951f189952a2b784ecebad164e1160ab376f1527"
|
||||
],
|
||||
[
|
||||
19,
|
||||
"0x1907dccf859a86a20eb9513067dfe4c1177b2ce642d82fe9a5242d0e64203fa9"
|
||||
],
|
||||
[
|
||||
20,
|
||||
"0x99bdf35872ec2463997ecbb94bc6cecf680988ad699b39b5c1649ffa266f4f15"
|
||||
],
|
||||
[
|
||||
21,
|
||||
"0xc25536e978e34a83736ef5e87a5cf062500a51fde4df9100d97e04e304f91654"
|
||||
],
|
||||
[
|
||||
22,
|
||||
"0x20359d647025b3ea156c71cf0007d5a39f23c5068f0ee6435b1cb6ca335181bb"
|
||||
],
|
||||
[
|
||||
23,
|
||||
"0xdc00a977d7ab0de248df07e386a08a3f0aa783d8fca5cff812a9f6c0afa84b61"
|
||||
],
|
||||
[
|
||||
24,
|
||||
"0x0387da8a2db5a44b05b1d162b2f2e25b6a060c285e1cb0492786a4110563442a"
|
||||
],
|
||||
[
|
||||
25,
|
||||
"0xce9c2c622ef827835d61481601d6c7474049b1edf4fd50ac37dca99f3e5d3724"
|
||||
],
|
||||
[
|
||||
26,
|
||||
"0x91be3e06f9c1f04fd204b0beb565cfab959ad68e8a07bc13d441cd20857a89aa"
|
||||
],
|
||||
[
|
||||
27,
|
||||
"0xa594a87329e8851172922cbe32a66090a65643d05f288a2d5e6e05e7ed1b5ffe"
|
||||
],
|
||||
[
|
||||
28,
|
||||
"0x438e92d3336e081c0267e25cf6d43adaf4d75e2f20774cb27840e3e2962e25e8"
|
||||
],
|
||||
[
|
||||
29,
|
||||
"0xac8d447d138ea8f010fc962da698356659f6c441a5688e32701960e8b16a9cb0"
|
||||
],
|
||||
[
|
||||
30,
|
||||
"0xeb78fbb06f42d7f8d759175d047b0dc293a56a9222028b2643bfb9ffc91fe3fd"
|
||||
],
|
||||
[
|
||||
31,
|
||||
"0x2016f52e3826d3d5907d42b6d649c6dd3440d7411f457c2b116b42e615f3bbf2"
|
||||
],
|
||||
[
|
||||
32,
|
||||
"0x7af660fa94b6e49b36a4d269180257825ba18f48e7e3c30cafb416bc9ce3ab12"
|
||||
],
|
||||
[
|
||||
33,
|
||||
"0xba45eec95775faf276121cfe8d3a692669a8bbfd39150938ea9511e90cce8b80"
|
||||
],
|
||||
[
|
||||
34,
|
||||
"0x1aaf31c0fe17670291225b49f0de8fae2a98f36eadafac443727bd106f77391b"
|
||||
],
|
||||
[
|
||||
35,
|
||||
"0x6fa7a464f1db6e720e134916cdaff8ec67bcd60a2a82d1e2aa2f534a07cc73a4"
|
||||
],
|
||||
[
|
||||
36,
|
||||
"0x0cb1b93c07ed8bf63bf31d123693c6dbfaf064277d66c254c2939c1e2921d8b8"
|
||||
],
|
||||
[
|
||||
37,
|
||||
"0x667183be90efad8b5bff455105274d7658d7c9c67baf4ab79520fcd62e33f27e"
|
||||
],
|
||||
[
|
||||
38,
|
||||
"0x99443a3696a07ee284b2adc97f28ba26b9883f5e4d50f71aa34f63ded0aa5758"
|
||||
],
|
||||
[
|
||||
39,
|
||||
"0xc89aabcb55b61130db52f88d5af03695c36d42ce5f5b1952c0c2a7f3142199ce"
|
||||
],
|
||||
[
|
||||
40,
|
||||
"0x63810d97fe61474ba493d0110349c1ebd39b0d61f8c19c6e65e889dffbe1d4d5"
|
||||
],
|
||||
[
|
||||
41,
|
||||
"0xe64882be5742249b50f8c2e119d6accb5ab9fe0a154978adadc14c20b1caba6e"
|
||||
],
|
||||
[
|
||||
42,
|
||||
"0xba4d1aed4f8f7a8e40ed4b8b9cb9951d9f62649f878d5ec8f55995cdd2d0ec2d"
|
||||
],
|
||||
[
|
||||
43,
|
||||
"0x6dc5c802d8a7a93a97d0623a649bbc67e38555b7d5a0c377c9e17c9279b58de8"
|
||||
],
|
||||
[
|
||||
44,
|
||||
"0x4a27daba79ed1ab3b40a6779cbffeb3d67911757f84f4f88ff0bd27572e1f46a"
|
||||
],
|
||||
[
|
||||
45,
|
||||
"0x7ba691e30c75946d7cd64f1f01eb9f94b0008e36eba673c6451ad393718bcbab"
|
||||
],
|
||||
[
|
||||
46,
|
||||
"0x82386a81343d8dfde805e65467a7877402218706a1bcc1da51de39957e231246"
|
||||
],
|
||||
[
|
||||
47,
|
||||
"0x65280d664a84f064129bef2f8373b914e50a9217fd00b09c51cb5e0ef5f257b9"
|
||||
],
|
||||
[
|
||||
48,
|
||||
"0x63a0982dda5fa15ed9c80c605557074714623ec147d7df0a3bd7972c11064505"
|
||||
],
|
||||
[
|
||||
49,
|
||||
"0x28fbf32967958aa4cd52dc8566bc32c64ca82ef827bcd83c02b45c38497ec37a"
|
||||
],
|
||||
[
|
||||
50,
|
||||
"0xcf06be7d297655c02c6f4a80dfa4b456a9686e4646c9c69d8b3c9b7fe9e074fc"
|
||||
],
|
||||
[
|
||||
51,
|
||||
"0x112543d5da2a348f6d626eeb7d2fa45476908d413f28feb64596f4587ca6e5dd"
|
||||
],
|
||||
[
|
||||
52,
|
||||
"0x3eb5dd755cdab699c0c38e325503e0b676c200237ab2a7cbe938cb12375db7dc"
|
||||
],
|
||||
[
|
||||
53,
|
||||
"0x8c7d74fa5d4cf80d23ba0376dafe5970bd30fdc5bfc278374b265aba4f15517d"
|
||||
],
|
||||
[
|
||||
54,
|
||||
"0x17eabdf92dfb31db69542e9895cfe59091445b4dbf456a9c87f8a27efa4efac0"
|
||||
],
|
||||
[
|
||||
55,
|
||||
"0x2bcabda995747c4221658d177995a64c94bc92202b02b8ef83b8bfd9cdb5709a"
|
||||
]
|
||||
],
|
||||
"rewards": [
|
||||
[
|
||||
"0x062c1140fbe5b04868ddd71c2993895d9381eec0",
|
||||
"0x2331846aef26d800"
|
||||
],
|
||||
[
|
||||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"0x2331846aef26d800"
|
||||
],
|
||||
[
|
||||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"0x2331846aef26d800"
|
||||
]
|
||||
],
|
||||
"proving_pool_credit": "0x1a65234cb54b4c00",
|
||||
"blocks": [
|
||||
{
|
||||
"miner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"blue": true,
|
||||
"txs": [
|
||||
"0x02f87482116f80843b9aca0084ee6b2800826324949965507d1a55bcc2695c58ba16fb37d819b0a4dc884563918244f4000080c080a00b405923e70a41d0838b38c1da30c365d1155108427959f6b5bde63267d8656aa05a55d368f8f8733b56e8a6249de42748eef8199affdf9bcf84d1fe4fd6d0286a",
|
||||
"0x02f87482116f01843b9aca0084ee6b28008263249415d34aaf54267db7d7c367839aaf71a00a2c6a65884563918244f4000080c001a04e7d3f775d1ee2cf39ba90383fdfafcb420dedf6e50bb5a7e553ffc3b3213550a059d4a0f7875b5ced1f3418df3ba594aa4ad69bfa96ae672123f31a292adc8617",
|
||||
"0x02f87482116f02843b9aca0084ee6b28008263249401d47ee52ebca6d0b0beac0f124acde471e0968a884563918244f4000080c080a04ee4d5c75e7066fe6825cfc51e4363a6c9b1391d9f6614751cb03e1b1346b840a03472a39140cb420fdb6136185d003154b5bc2aea956ba2cf4373d77ec14ad698"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pre_state": [
|
||||
{
|
||||
"address": "0x0000000000000000000000000000000000000210",
|
||||
"nonce": 1,
|
||||
"balance": "0x0",
|
||||
"code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063aa67735414610043578063dea5c2e014610058578063fe7e05d51461009f575b5f5ffd5b6100566100513660046101e0565b6100ca565b005b610083610066366004610211565b6001600160a01b039081165f908152600160205260409020541690565b6040516001600160a01b03909116815260200160405180910390f35b6100836100ad366004610211565b6001600160a01b039081165f908152602081905260409020541690565b336001600160a01b03831614806100f957506001600160a01b038281165f908152600160205260409020541633145b6101635760405162461bcd60e51b815260206004820152603160248201527f446576656c6f70657252656769737472793a206e6f7420746865206163636f75604482015270373a1037b91034ba399031b932b0ba37b960791b606482015260840160405180910390fd5b6001600160a01b038281165f818152602081815260409182902080546001600160a01b031916948616948517905590513381527fa47563c41dab010f91a8ef9dc7ac2bcdfa0ef2af697e575048e71e6eec60dda3910160405180910390a35050565b80356001600160a01b03811681146101db575f5ffd5b919050565b5f5f604083850312156101f1575f5ffd5b6101fa836101c5565b9150610208602084016101c5565b90509250929050565b5f60208284031215610221575f5ffd5b61022a826101c5565b939250505056fea2646970667358221220cbf48f5aa6f911f83b3c2b09adf8c418f5da6fc530c3d5db9d4ba0be24c4bf5864736f6c63430008250033",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x0000000000000000000000000000000000000220",
|
||||
"nonce": 0,
|
||||
"balance": "0x37878e392363d4000",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x062c1140fbe5b04868ddd71c2993895d9381eec0",
|
||||
"nonce": 0,
|
||||
"balance": "0x4ac5a33ceab8a0800",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x3c5f681b84cd6be1c07dd0c7c68701e9510f8359",
|
||||
"nonce": 0,
|
||||
"balance": "0x41f9f6c2a3e437000",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"nonce": 0,
|
||||
"balance": "0x515e9f03438d0c800",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"pre_state_root": "0xc9e0d33ba639d7144f8ffc6950ec21afb5933c88f61a1a1bdd6a957d5ca4025c",
|
||||
"post_state_root": "0x68cacfd393b00ead784a69b10d57a3e2dd57858029df107b529487a49f393b50",
|
||||
"receipts_root": "0x25e6b7af647c519a27cc13276a1e6abc46154b51414d174b072698df1f6c19df",
|
||||
"gas_used": 63000,
|
||||
"pgas_used": 600,
|
||||
"executed": 3,
|
||||
"skipped": 0,
|
||||
"node_state_root": "0x68cacfd393b00ead784a69b10d57a3e2dd57858029df107b529487a49f393b50"
|
||||
}
|
||||
}
|
||||
443
proving/fixtures/block-78-increment.json
Normal file
443
proving/fixtures/block-78-increment.json
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
{
|
||||
"format": "igneum-prove-fixture-v0",
|
||||
"source": "tools/evm-smoke/seq.json, the igneum_exportSegments export of the 3-node simnet run of 3 October 2026 (execution-layer commit fb33069), replayed from genesis through igneum-prove-core; block 78 holds the increment(5) call on the Counter contract",
|
||||
"block": {
|
||||
"chain_id": 4463,
|
||||
"env": {
|
||||
"number": 78,
|
||||
"hash": "0x417c3cf92d45e6b6461daee2ffc7cd3311bfb18501f401d52b99f474b81cb84c",
|
||||
"parent_hash": "0xa57b5b2dfd917e18d10c9720ddcab77aaf260b747fdfd25ec7d25ce5b284caae",
|
||||
"timestamp": 1791064884,
|
||||
"miner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"prevrandao": "0xefa9c955f70c306850ec7d550e834d5aed613b0584e56174c6f4c1974cf99486",
|
||||
"base_fee_exec": 1000000000,
|
||||
"base_fee_proving": 1000000000
|
||||
},
|
||||
"block_hashes": [
|
||||
[
|
||||
0,
|
||||
"0x3c19f19d3d77bfc2f367482eb7f28aa907ca5d31470c7d7722a68f44436236d5"
|
||||
],
|
||||
[
|
||||
1,
|
||||
"0x4a86d6afa55fa5a073e43a5f5d3ac423edcfa6f7815690eb23f9a090b20395bc"
|
||||
],
|
||||
[
|
||||
2,
|
||||
"0x07b68c40a8a8eb6826442d9cca7dbd08882a0c02130ea6ee7eaf7f729e081ea3"
|
||||
],
|
||||
[
|
||||
3,
|
||||
"0xf15ad0bcceb161e14082f4163c44a8134dd587f192c8dd1737db00c6db24fa1b"
|
||||
],
|
||||
[
|
||||
4,
|
||||
"0x22212b7c5792ff6743f48caa1ba1f0587772723cb7ae1572485bb73e42242c16"
|
||||
],
|
||||
[
|
||||
5,
|
||||
"0x32968867f32f5ad1580f834fbad5cdc07ab96df4d3346b4251893a5d3bb719b6"
|
||||
],
|
||||
[
|
||||
6,
|
||||
"0xa1548b817ef1c597ad91064886d3a00b86ec5aa57889d085881329d584ed3214"
|
||||
],
|
||||
[
|
||||
7,
|
||||
"0x52f35b7d71efaa3181660b0e0689e19be5996dfd0b52bba0518dc8ab099be968"
|
||||
],
|
||||
[
|
||||
8,
|
||||
"0xcb602a65d6788ea471038522506dfbec15775183d0fb552f7d0ab03ea95315ef"
|
||||
],
|
||||
[
|
||||
9,
|
||||
"0xa7a0015c122f749591ae5c4b6fae8a035cba2e3c0f4500d6c915a61f4f534c74"
|
||||
],
|
||||
[
|
||||
10,
|
||||
"0x3efdfcdc9fac9b0df2eb6fadae9deaada5abbcc3a2436164b3b061743f4c2aa5"
|
||||
],
|
||||
[
|
||||
11,
|
||||
"0x49c9f846adc3f61685d0075efa3d05aa29fcb2dad02d9e6f415508fa2adc23f1"
|
||||
],
|
||||
[
|
||||
12,
|
||||
"0xe084f75979bb4a34958883d0b599262c7719669119c88b3ce015500473ecac91"
|
||||
],
|
||||
[
|
||||
13,
|
||||
"0x9653df5f568099056e6d33bb79364dcb4c9760383b5f4f208bb9a14db1c156c4"
|
||||
],
|
||||
[
|
||||
14,
|
||||
"0xf65af06e2edf1ac8443992bd5affbf5f244a3b2ca2b572b6a7bea5ae1b1b9e60"
|
||||
],
|
||||
[
|
||||
15,
|
||||
"0xa7b1e86b1a99ab38f127637a4fdaf25ee1c18ceb294ef136f4bbf3c53321e29a"
|
||||
],
|
||||
[
|
||||
16,
|
||||
"0xac94a12363cf3e35e43c8a7aed2982c0f4445328c3349aa5cab232ad20f24600"
|
||||
],
|
||||
[
|
||||
17,
|
||||
"0xd97973cba8dbac8b5396738bef29e467459311305de6236af3a601916bf1a299"
|
||||
],
|
||||
[
|
||||
18,
|
||||
"0x345a0f4a3ca21c93f5a6b3bb951f189952a2b784ecebad164e1160ab376f1527"
|
||||
],
|
||||
[
|
||||
19,
|
||||
"0x1907dccf859a86a20eb9513067dfe4c1177b2ce642d82fe9a5242d0e64203fa9"
|
||||
],
|
||||
[
|
||||
20,
|
||||
"0x99bdf35872ec2463997ecbb94bc6cecf680988ad699b39b5c1649ffa266f4f15"
|
||||
],
|
||||
[
|
||||
21,
|
||||
"0xc25536e978e34a83736ef5e87a5cf062500a51fde4df9100d97e04e304f91654"
|
||||
],
|
||||
[
|
||||
22,
|
||||
"0x20359d647025b3ea156c71cf0007d5a39f23c5068f0ee6435b1cb6ca335181bb"
|
||||
],
|
||||
[
|
||||
23,
|
||||
"0xdc00a977d7ab0de248df07e386a08a3f0aa783d8fca5cff812a9f6c0afa84b61"
|
||||
],
|
||||
[
|
||||
24,
|
||||
"0x0387da8a2db5a44b05b1d162b2f2e25b6a060c285e1cb0492786a4110563442a"
|
||||
],
|
||||
[
|
||||
25,
|
||||
"0xce9c2c622ef827835d61481601d6c7474049b1edf4fd50ac37dca99f3e5d3724"
|
||||
],
|
||||
[
|
||||
26,
|
||||
"0x91be3e06f9c1f04fd204b0beb565cfab959ad68e8a07bc13d441cd20857a89aa"
|
||||
],
|
||||
[
|
||||
27,
|
||||
"0xa594a87329e8851172922cbe32a66090a65643d05f288a2d5e6e05e7ed1b5ffe"
|
||||
],
|
||||
[
|
||||
28,
|
||||
"0x438e92d3336e081c0267e25cf6d43adaf4d75e2f20774cb27840e3e2962e25e8"
|
||||
],
|
||||
[
|
||||
29,
|
||||
"0xac8d447d138ea8f010fc962da698356659f6c441a5688e32701960e8b16a9cb0"
|
||||
],
|
||||
[
|
||||
30,
|
||||
"0xeb78fbb06f42d7f8d759175d047b0dc293a56a9222028b2643bfb9ffc91fe3fd"
|
||||
],
|
||||
[
|
||||
31,
|
||||
"0x2016f52e3826d3d5907d42b6d649c6dd3440d7411f457c2b116b42e615f3bbf2"
|
||||
],
|
||||
[
|
||||
32,
|
||||
"0x7af660fa94b6e49b36a4d269180257825ba18f48e7e3c30cafb416bc9ce3ab12"
|
||||
],
|
||||
[
|
||||
33,
|
||||
"0xba45eec95775faf276121cfe8d3a692669a8bbfd39150938ea9511e90cce8b80"
|
||||
],
|
||||
[
|
||||
34,
|
||||
"0x1aaf31c0fe17670291225b49f0de8fae2a98f36eadafac443727bd106f77391b"
|
||||
],
|
||||
[
|
||||
35,
|
||||
"0x6fa7a464f1db6e720e134916cdaff8ec67bcd60a2a82d1e2aa2f534a07cc73a4"
|
||||
],
|
||||
[
|
||||
36,
|
||||
"0x0cb1b93c07ed8bf63bf31d123693c6dbfaf064277d66c254c2939c1e2921d8b8"
|
||||
],
|
||||
[
|
||||
37,
|
||||
"0x667183be90efad8b5bff455105274d7658d7c9c67baf4ab79520fcd62e33f27e"
|
||||
],
|
||||
[
|
||||
38,
|
||||
"0x99443a3696a07ee284b2adc97f28ba26b9883f5e4d50f71aa34f63ded0aa5758"
|
||||
],
|
||||
[
|
||||
39,
|
||||
"0xc89aabcb55b61130db52f88d5af03695c36d42ce5f5b1952c0c2a7f3142199ce"
|
||||
],
|
||||
[
|
||||
40,
|
||||
"0x63810d97fe61474ba493d0110349c1ebd39b0d61f8c19c6e65e889dffbe1d4d5"
|
||||
],
|
||||
[
|
||||
41,
|
||||
"0xe64882be5742249b50f8c2e119d6accb5ab9fe0a154978adadc14c20b1caba6e"
|
||||
],
|
||||
[
|
||||
42,
|
||||
"0xba4d1aed4f8f7a8e40ed4b8b9cb9951d9f62649f878d5ec8f55995cdd2d0ec2d"
|
||||
],
|
||||
[
|
||||
43,
|
||||
"0x6dc5c802d8a7a93a97d0623a649bbc67e38555b7d5a0c377c9e17c9279b58de8"
|
||||
],
|
||||
[
|
||||
44,
|
||||
"0x4a27daba79ed1ab3b40a6779cbffeb3d67911757f84f4f88ff0bd27572e1f46a"
|
||||
],
|
||||
[
|
||||
45,
|
||||
"0x7ba691e30c75946d7cd64f1f01eb9f94b0008e36eba673c6451ad393718bcbab"
|
||||
],
|
||||
[
|
||||
46,
|
||||
"0x82386a81343d8dfde805e65467a7877402218706a1bcc1da51de39957e231246"
|
||||
],
|
||||
[
|
||||
47,
|
||||
"0x65280d664a84f064129bef2f8373b914e50a9217fd00b09c51cb5e0ef5f257b9"
|
||||
],
|
||||
[
|
||||
48,
|
||||
"0x63a0982dda5fa15ed9c80c605557074714623ec147d7df0a3bd7972c11064505"
|
||||
],
|
||||
[
|
||||
49,
|
||||
"0x28fbf32967958aa4cd52dc8566bc32c64ca82ef827bcd83c02b45c38497ec37a"
|
||||
],
|
||||
[
|
||||
50,
|
||||
"0xcf06be7d297655c02c6f4a80dfa4b456a9686e4646c9c69d8b3c9b7fe9e074fc"
|
||||
],
|
||||
[
|
||||
51,
|
||||
"0x112543d5da2a348f6d626eeb7d2fa45476908d413f28feb64596f4587ca6e5dd"
|
||||
],
|
||||
[
|
||||
52,
|
||||
"0x3eb5dd755cdab699c0c38e325503e0b676c200237ab2a7cbe938cb12375db7dc"
|
||||
],
|
||||
[
|
||||
53,
|
||||
"0x8c7d74fa5d4cf80d23ba0376dafe5970bd30fdc5bfc278374b265aba4f15517d"
|
||||
],
|
||||
[
|
||||
54,
|
||||
"0x17eabdf92dfb31db69542e9895cfe59091445b4dbf456a9c87f8a27efa4efac0"
|
||||
],
|
||||
[
|
||||
55,
|
||||
"0x2bcabda995747c4221658d177995a64c94bc92202b02b8ef83b8bfd9cdb5709a"
|
||||
],
|
||||
[
|
||||
56,
|
||||
"0xc47bacc89a3318140e66912717b958057bdbe72c337162a0667a0bbf44670a22"
|
||||
],
|
||||
[
|
||||
57,
|
||||
"0x451fbd60110eaeecdf0ef5f399c0e3352e7a04971448b2948a85b06a9a0aab5e"
|
||||
],
|
||||
[
|
||||
58,
|
||||
"0x360d73b39245a01b0dd8300489c04cf526ca7860399fd2fbab481b4bf7ecc495"
|
||||
],
|
||||
[
|
||||
59,
|
||||
"0x02c501085419451f98ab959e9f4d81e3cec33fe2e01df625b7756fee8e1b063f"
|
||||
],
|
||||
[
|
||||
60,
|
||||
"0xf749bb570097506145da99ca604d30ca8ffa30fec69f40ed97c3cd82697b2fdb"
|
||||
],
|
||||
[
|
||||
61,
|
||||
"0x2a89cc4503e960d857e5e6ff6667d74fc9ceef2fa1ba96d18d81181926f45a75"
|
||||
],
|
||||
[
|
||||
62,
|
||||
"0x4752cae73f132a855d680030534ecb1f45f669e3598ed7650358afc80a3ff572"
|
||||
],
|
||||
[
|
||||
63,
|
||||
"0xc09c9329e5431602f7987b7a73177e77182772e89d0bb5660d51008679221097"
|
||||
],
|
||||
[
|
||||
64,
|
||||
"0x656c3bd2bf3a828459050082c51d682ff6aba8ace7d1f6a71f2c8151297f485e"
|
||||
],
|
||||
[
|
||||
65,
|
||||
"0x21a0eabaaba10ad93debd9e363a875f85d2b24e659dd0394df21d851cc85dddb"
|
||||
],
|
||||
[
|
||||
66,
|
||||
"0x2ddd732f1cd58a29ec77b9177528eca6f71e8dd7e574f2209e2bd7e6cd8fd910"
|
||||
],
|
||||
[
|
||||
67,
|
||||
"0x9b0953b96462134bd10814e4f85d32ba98150b806ab8a38446d152ea5005e6d1"
|
||||
],
|
||||
[
|
||||
68,
|
||||
"0x6aaebdd184a2a23dea401205365f087c6475a109ac41a7763332597eccc41ab6"
|
||||
],
|
||||
[
|
||||
69,
|
||||
"0xd6d577bb49cec21156aa8f7181c66aff4c5011a82670ac4bc9ad13b10f8b9d2b"
|
||||
],
|
||||
[
|
||||
70,
|
||||
"0xf97bea836c07920267b83a58b68e710e6946ce6d46a10a40eef686c0599947cd"
|
||||
],
|
||||
[
|
||||
71,
|
||||
"0x90969bca7a983625ba847636b7087caa8af68d14f393074805ec07922e57cda8"
|
||||
],
|
||||
[
|
||||
72,
|
||||
"0xcd83aa7b18e9c801bd0108e725e092c4e8328dc7682b6e10b0a40191fa1e4c69"
|
||||
],
|
||||
[
|
||||
73,
|
||||
"0x7fa89842fb72e9b33a8261e63b7bd49575ce081b88dae44b0f207ef1667d2b39"
|
||||
],
|
||||
[
|
||||
74,
|
||||
"0xe3c4ed7402b4b8ba68bd70fbad7f59ff905d68844c5f23703930816f2889c908"
|
||||
],
|
||||
[
|
||||
75,
|
||||
"0x53f6b445d9472b515d7287cebb83e1b6a81641e795abc96a0a95f175eee5165a"
|
||||
],
|
||||
[
|
||||
76,
|
||||
"0xd6a2b28a2b75d47d75da3a05edfc7cb1c9b066f08bfec3e7b6b51187b665b332"
|
||||
],
|
||||
[
|
||||
77,
|
||||
"0xa57b5b2dfd917e18d10c9720ddcab77aaf260b747fdfd25ec7d25ce5b284caae"
|
||||
]
|
||||
],
|
||||
"rewards": [
|
||||
[
|
||||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"0x2332d4a618475c00"
|
||||
],
|
||||
[
|
||||
"0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"0x2332d4a618475c00"
|
||||
]
|
||||
],
|
||||
"proving_pool_credit": "0x11996a4f8e11d800",
|
||||
"blocks": [
|
||||
{
|
||||
"miner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"blue": true,
|
||||
"txs": [
|
||||
"0x02f89082116f13843b9aca00848321560082d56e94e0eebd35b952c9c73a187eda3d669d9bcfd7900680a47cf5dab00000000000000000000000000000000000000000000000000000000000000005c080a0027d6634fab11f3ffbe398ceae5d862bc285d9e510d38359bc2363785f9afcb1a04581db396ff63fbb16bdc5a07244b0143a2420029654ec1612b71bc1097b6a6d",
|
||||
"0x02f9036882116f12843b9aca00848321560083034dad8080b9030d608060405234801561000f575f5ffd5b506040516102cd3803806102cd83398101604081905261002e916100bc565b6001600160a01b0382161561009a57604051632a99dcd560e21b81523060048201526001600160a01b03828116602483015283169063aa677354906044015f604051808303815f87803b158015610083575f5ffd5b505af1158015610095573d5f5f3e3d5ffd5b505050505b50506100ed565b80516001600160a01b03811681146100b7575f5ffd5b919050565b5f5f604083850312156100cd575f5ffd5b6100d6836100a1565b91506100e4602084016100a1565b90509250929050565b6101d3806100fa5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c806306661abd14610043578063671513191461005d5780637cf5dab014610070575b5f5ffd5b61004b5f5481565b60405190815260200160405180910390f35b61004b61006b366004610161565b610083565b61004b61007e366004610161565b6100ca565b5f805b828110156100c457604080516020810184905290810182905260600160408051601f1981840301815291905280516020909101209150600101610086565b50919050565b5f5f821161010e5760405162461bcd60e51b815260206004820152600d60248201526c436f756e7465723a207a65726f60981b604482015260640160405180910390fd5b815f5f82825461011e9190610178565b90915550505f5460405190815233907f38ac789ed44572701765277c4d0970f2db1c1a571ed39e84358095ae4eaa54209060200160405180910390a250505f5490565b5f60208284031215610171575f5ffd5b5035919050565b8082018082111561019757634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220550fbe02c34bbf880b632f2e9e937266b869dc777da392b2cfceaf158da2014364736f6c6343000825003300000000000000000000000000000000000000000000000000000000000002100000000000000000000000002546bcd3c84621e976d8185a91a922ae77ecec30c001a084ccbedd483534368d451b6f9b1ebdd938fc1cc0d14984240ee82854b1d055fea015c4522b9d0890001d11d99d70806a8e93fb3aeb7b9ade5d67d209c752f4670b"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pre_state": [
|
||||
{
|
||||
"address": "0x0000000000000000000000000000000000000210",
|
||||
"nonce": 1,
|
||||
"balance": "0x0",
|
||||
"code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063aa67735414610043578063dea5c2e014610058578063fe7e05d51461009f575b5f5ffd5b6100566100513660046101e0565b6100ca565b005b610083610066366004610211565b6001600160a01b039081165f908152600160205260409020541690565b6040516001600160a01b03909116815260200160405180910390f35b6100836100ad366004610211565b6001600160a01b039081165f908152602081905260409020541690565b336001600160a01b03831614806100f957506001600160a01b038281165f908152600160205260409020541633145b6101635760405162461bcd60e51b815260206004820152603160248201527f446576656c6f70657252656769737472793a206e6f7420746865206163636f75604482015270373a1037b91034ba399031b932b0ba37b960791b606482015260840160405180910390fd5b6001600160a01b038281165f818152602081815260409182902080546001600160a01b031916948616948517905590513381527fa47563c41dab010f91a8ef9dc7ac2bcdfa0ef2af697e575048e71e6eec60dda3910160405180910390a35050565b80356001600160a01b03811681146101db575f5ffd5b919050565b5f5f604083850312156101f1575f5ffd5b6101fa836101c5565b9150610208602084016101c5565b90509250929050565b5f60208284031215610221575f5ffd5b61022a826101c5565b939250505056fea2646970667358221220cbf48f5aa6f911f83b3c2b09adf8c418f5da6fc530c3d5db9d4ba0be24c4bf5864736f6c63430008250033",
|
||||
"storage": [
|
||||
[
|
||||
"0x5d14761ae7502be0a39e716a4f4c071c7679f4eb4abc6b568bc3a7e6b0c208e3",
|
||||
"0x2546bcd3c84621e976d8185a91a922ae77ecec30"
|
||||
],
|
||||
[
|
||||
"0xa670c65b3143c90414bb1c7f0b07f7e6a551f6171fc45b8ac1c3d0700b9ee0e6",
|
||||
"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x0000000000000000000000000000000000000220",
|
||||
"nonce": 0,
|
||||
"balance": "0x4f2d3cdffa6b5c400",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x01d47ee52ebca6d0b0beac0f124acde471e0968a",
|
||||
"nonce": 16,
|
||||
"balance": "0x456bd3e6f3208000",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x062c1140fbe5b04868ddd71c2993895d9381eec0",
|
||||
"nonce": 0,
|
||||
"balance": "0x699184ec59401a800",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65",
|
||||
"nonce": 18,
|
||||
"balance": "0x462eed5971c12000",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x2546bcd3c84621e976d8185a91a922ae77ecec30",
|
||||
"nonce": 0,
|
||||
"balance": "0x1d10f53c63c5",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x3c5f681b84cd6be1c07dd0c7c68701e9510f8359",
|
||||
"nonce": 0,
|
||||
"balance": "0x6bc5b770edda11400",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
|
||||
"nonce": 3,
|
||||
"balance": "0x5a5b41ce6d7a8b800",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
|
||||
"nonce": 19,
|
||||
"balance": "0x4486d3ef4107c400",
|
||||
"code": "0x",
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
"address": "0xe0eebd35b952c9c73a187eda3d669d9bcfd79006",
|
||||
"nonce": 1,
|
||||
"balance": "0x0",
|
||||
"code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c806306661abd14610043578063671513191461005d5780637cf5dab014610070575b5f5ffd5b61004b5f5481565b60405190815260200160405180910390f35b61004b61006b366004610161565b610083565b61004b61007e366004610161565b6100ca565b5f805b828110156100c457604080516020810184905290810182905260600160408051601f1981840301815291905280516020909101209150600101610086565b50919050565b5f5f821161010e5760405162461bcd60e51b815260206004820152600d60248201526c436f756e7465723a207a65726f60981b604482015260640160405180910390fd5b815f5f82825461011e9190610178565b90915550505f5460405190815233907f38ac789ed44572701765277c4d0970f2db1c1a571ed39e84358095ae4eaa54209060200160405180910390a250505f5490565b5f60208284031215610171575f5ffd5b5035919050565b8082018082111561019757634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220550fbe02c34bbf880b632f2e9e937266b869dc777da392b2cfceaf158da2014364736f6c63430008250033",
|
||||
"storage": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"pre_state_root": "0x4e1a7b269c47c71e358118eed382ac4664194b44e85b43dc4ccdc3869dab75bd",
|
||||
"post_state_root": "0x5b18b3a58f6c1d21b22caad4a1dbd9ee8a6394a02db2220255e556a9d4f90878",
|
||||
"receipts_root": "0x9efcc914e2358eb95f50b110aeb57ba17341cb89f831a4f0e322d6aeef2e8e9c",
|
||||
"gas_used": 45354,
|
||||
"pgas_used": 1488,
|
||||
"executed": 1,
|
||||
"skipped": 1,
|
||||
"node_state_root": "0x5b18b3a58f6c1d21b22caad4a1dbd9ee8a6394a02db2220255e556a9d4f90878"
|
||||
}
|
||||
}
|
||||
2
proving/igneum-prove/.gitignore
vendored
Normal file
2
proving/igneum-prove/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
*.bin
|
||||
7603
proving/igneum-prove/Cargo.lock
generated
Normal file
7603
proving/igneum-prove/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
40
proving/igneum-prove/Cargo.toml
Normal file
40
proving/igneum-prove/Cargo.toml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Igneum proving v0: an SP1 program that re-executes one Igneum chain block and a host that proves it.
|
||||
# Crate versions are pinned to what the execution layer (vendor/igneum-node-exec, branch execution-layer,
|
||||
# commit fb33069) and SP1 v6.8.1 (24 Sep 2026) use. Change one, re-run the export and the native check.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["core", "program", "host", "export"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "ISC"
|
||||
|
||||
[workspace.dependencies]
|
||||
igneum-prove-core = { path = "core" }
|
||||
# The execution layer's own transaction decoder (alloy only, no kaspa dependency), so the guest and the node
|
||||
# cannot disagree about what a well-formed transaction is.
|
||||
igneum-evm-types = { path = "../../vendor/igneum-node-exec/igneum/evm-types" }
|
||||
|
||||
revm = { version = "=43.0.3", default-features = false, features = ["std", "serde", "optional_balance_check", "optional_no_base_fee", "optional_block_gas_limit", "optional_eip3607", "optional_priority_fee_check"] }
|
||||
alloy-primitives = { version = "=1.7.3", default-features = false, features = ["std", "rlp", "serde", "k256"] }
|
||||
alloy-consensus = { version = "=2.5.0", default-features = false, features = ["std", "k256"] }
|
||||
alloy-eips = { version = "=2.5.0", default-features = false, features = ["std"] }
|
||||
alloy-rlp = { version = "=0.3.16", default-features = false }
|
||||
alloy-trie = { version = "=0.9.8", default-features = false, features = ["std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
bincode = "=1.3.3"
|
||||
hex = "0.4.3"
|
||||
anyhow = "1.0"
|
||||
|
||||
sp1-zkvm = "=6.8.1"
|
||||
sp1-sdk = "=6.8.1"
|
||||
sp1-build = "=6.8.1"
|
||||
|
||||
# SP1's accelerated crates (https://docs.succinct.xyz/docs/sp1/optimizing-programs/precompiles). Outside the zkVM
|
||||
# they fall back to the upstream code, so the host and the export compute the same bytes natively.
|
||||
# alloy-primitives 1.7 hashes with sha3 0.11 (its keccak256 backend when neither tiny-keccak nor asm-keccak is on).
|
||||
[patch.crates-io]
|
||||
sha3 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha3", tag = "patch-sha3-0.11.0-sp1-6.0.0" }
|
||||
k256 = { git = "https://github.com/sp1-patches/elliptic-curves", tag = "patch-k256-13.4-sp1-6.2.0" }
|
||||
16
proving/igneum-prove/core/Cargo.toml
Normal file
16
proving/igneum-prove/core/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "igneum-prove-core"
|
||||
description = "Igneum proving v0: the block statement (re-execute one chain block with revm, state root and receipts root as outputs) shared by the guest, the host and the exporter"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
igneum-evm-types.workspace = true
|
||||
revm.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-eips.workspace = true
|
||||
alloy-rlp.workspace = true
|
||||
alloy-trie.workspace = true
|
||||
serde.workspace = true
|
||||
11
proving/igneum-prove/core/src/config.rs
Normal file
11
proving/igneum-prove/core/src/config.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//! Consensus constants of the execution layer, devnet v3 values (mirror of igneum-exec `config.rs` at fb33069).
|
||||
|
||||
use alloy_primitives::{address, Address};
|
||||
|
||||
pub const BLOCK_EXECUTION_GAS_LIMIT: u64 = 30_000_000;
|
||||
pub const BLOCK_PROVING_GAS_LIMIT: u64 = 30_000_000;
|
||||
pub const DEVELOPER_REGISTRY_ADDRESS: Address = address!("0000000000000000000000000000000000000210");
|
||||
pub const PROVING_POOL_ADDRESS: Address = address!("0000000000000000000000000000000000000220");
|
||||
pub const BLOCKHASH_WINDOW: u64 = 256;
|
||||
pub const INTRINSIC_PGAS_PER_TX: u64 = 200;
|
||||
pub const DEVELOPER_SHARE_PERCENT: u128 = 20;
|
||||
303
proving/igneum-prove/core/src/executor.rs
Normal file
303
proving/igneum-prove/core/src/executor.rs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
//! Segment execution, ported from igneum-exec `executor.rs` at fb33069 (design sections 1 to 4): rewards by
|
||||
//! rule, every block's transactions in sequence order under the Igneum environment, two-dimensional gas, fee
|
||||
//! flows and the nonce-rule skip; the state root and the receipts root as outputs.
|
||||
|
||||
use crate::config::*;
|
||||
use crate::fixture::{BlockFixture, FixtureEnv};
|
||||
use crate::pgas::{developer_shares, IgneumInspector};
|
||||
use crate::registry::{address_from_word, payee_slot};
|
||||
use crate::state::{DbRef, IgneumDb};
|
||||
use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom, Transaction, TxEnvelope};
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::{Address, Bloom, Log, B256, U256};
|
||||
use igneum_evm_types::DecodedTx;
|
||||
use revm::context::result::{EVMError, ExecutionResult, InvalidTransaction};
|
||||
use revm::context::{BlockEnv, CfgEnv, Context, TxEnv};
|
||||
use revm::handler::MainBuilder;
|
||||
use revm::inspector::InspectEvm;
|
||||
use revm::primitives::hardfork::SpecId;
|
||||
use revm::MainContext;
|
||||
use revm::{DatabaseCommit, ExecuteEvm};
|
||||
|
||||
fn cfg(chain_id: u64) -> CfgEnv {
|
||||
let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN);
|
||||
cfg.chain_id = chain_id;
|
||||
cfg
|
||||
}
|
||||
|
||||
fn block_env(env: &FixtureEnv, beneficiary: Address) -> BlockEnv {
|
||||
BlockEnv {
|
||||
number: U256::from(env.number),
|
||||
beneficiary,
|
||||
timestamp: U256::from(env.timestamp),
|
||||
gas_limit: BLOCK_EXECUTION_GAS_LIMIT,
|
||||
basefee: env.base_fee_exec,
|
||||
difficulty: U256::ZERO,
|
||||
prevrandao: Some(env.prevrandao),
|
||||
// BLOBBASEFEE 1, no blobs (design section 3)
|
||||
blob_excess_gas_and_price: Some(revm::context_interface::block::BlobExcessGasAndPrice::new(0, 3_338_477)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tx_env(tx: &DecodedTx) -> TxEnv {
|
||||
let e = &tx.envelope;
|
||||
let (gas_price, priority) = match e {
|
||||
TxEnvelope::Legacy(_) | TxEnvelope::Eip2930(_) => (e.max_fee_per_gas(), None),
|
||||
_ => (e.max_fee_per_gas(), e.max_priority_fee_per_gas()),
|
||||
};
|
||||
TxEnv {
|
||||
tx_type: tx.tx_type(),
|
||||
caller: tx.sender,
|
||||
gas_limit: e.gas_limit(),
|
||||
gas_price,
|
||||
kind: e.kind(),
|
||||
value: e.value(),
|
||||
data: e.input().clone(),
|
||||
nonce: e.nonce(),
|
||||
chain_id: e.chain_id(),
|
||||
access_list: e.access_list().cloned().unwrap_or_default(),
|
||||
gas_priority_fee: priority,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn effective_gas_price(tx: &DecodedTx, base_fee: u128) -> u128 {
|
||||
let max_fee = tx.max_fee_per_gas();
|
||||
match &tx.envelope {
|
||||
TxEnvelope::Legacy(_) | TxEnvelope::Eip2930(_) => max_fee,
|
||||
_ => max_fee.min(base_fee + tx.max_priority_fee_per_gas()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Why a transaction was skipped (state-dependent faults of design 1.5); kept for the exporter's report.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Skip {
|
||||
Undecodable(String),
|
||||
NonceTooLow { expected: u64, got: u64 },
|
||||
NonceTooHigh { expected: u64, got: u64 },
|
||||
InsufficientFunds,
|
||||
FeeCapBelowBaseFee,
|
||||
BlockProvingBudget,
|
||||
BlockExecutionBudget,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
fn skip_reason_from(err: &EVMError<std::convert::Infallible>) -> Skip {
|
||||
match err {
|
||||
EVMError::Transaction(t) => match t {
|
||||
InvalidTransaction::NonceTooLow { tx, state } => Skip::NonceTooLow { expected: *state, got: *tx },
|
||||
InvalidTransaction::NonceTooHigh { tx, state } => Skip::NonceTooHigh { expected: *state, got: *tx },
|
||||
InvalidTransaction::LackOfFundForMaxFee { .. } => Skip::InsufficientFunds,
|
||||
InvalidTransaction::GasPriceLessThanBasefee => Skip::FeeCapBelowBaseFee,
|
||||
other => Skip::Other(other.to_string()),
|
||||
},
|
||||
other => Skip::Other(other.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// One executed transaction's receipt fields, enough for the receipts root and the exporter's report.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutedReceipt {
|
||||
pub tx_hash: B256,
|
||||
pub status: bool,
|
||||
pub gas_used: u64,
|
||||
pub pgas_used: u64,
|
||||
pub cumulative_gas_used: u64,
|
||||
pub logs: Vec<Log>,
|
||||
pub logs_bloom: Bloom,
|
||||
pub tx_type: u8,
|
||||
pub over_budget: bool,
|
||||
}
|
||||
|
||||
pub struct BlockOutcome {
|
||||
pub executed: Vec<ExecutedReceipt>,
|
||||
pub skipped: Vec<(B256, Skip)>,
|
||||
pub gas_used: u64,
|
||||
pub pgas_used: u64,
|
||||
pub state_root: B256,
|
||||
pub receipts_root: B256,
|
||||
}
|
||||
|
||||
/// Executes one chain block's segment on `db` (design 1.2): the rewards of the fixture, then every block's
|
||||
/// transactions in sequence order, then the roots as outputs. `db` is the pre-state on entry and the post-state
|
||||
/// on return.
|
||||
pub fn execute_block(db: &mut IgneumDb, f: &BlockFixture) -> BlockOutcome {
|
||||
let env = &f.env;
|
||||
let base_fee_exec = env.base_fee_exec as u128;
|
||||
let base_fee_proving = env.base_fee_proving as u128;
|
||||
|
||||
for (miner, wei) in &f.rewards {
|
||||
db.add_balance(*miner, *wei);
|
||||
}
|
||||
db.add_balance(PROVING_POOL_ADDRESS, f.proving_pool_credit);
|
||||
|
||||
let mut executed: Vec<ExecutedReceipt> = Vec::new();
|
||||
let mut skipped: Vec<(B256, Skip)> = Vec::new();
|
||||
let mut cumulative_gas = 0u64;
|
||||
let mut segment_pgas = 0u64;
|
||||
|
||||
for b in &f.blocks {
|
||||
let mut block_pgas = 0u64;
|
||||
let mut block_gas = 0u64;
|
||||
for raw in &b.txs {
|
||||
let tx = match igneum_evm_types::decode_and_check(raw, f.chain_id) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
skipped.push((alloy_primitives::keccak256(raw), Skip::Undecodable(e.to_string())));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
block_pgas = block_pgas.saturating_add(INTRINSIC_PGAS_PER_TX);
|
||||
segment_pgas = segment_pgas.saturating_add(INTRINSIC_PGAS_PER_TX);
|
||||
if block_pgas > BLOCK_PROVING_GAS_LIMIT {
|
||||
skipped.push((tx.hash, Skip::BlockProvingBudget));
|
||||
continue;
|
||||
}
|
||||
if block_gas.saturating_add(tx.gas_limit()) > BLOCK_EXECUTION_GAS_LIMIT {
|
||||
skipped.push((tx.hash, Skip::BlockExecutionBudget));
|
||||
continue;
|
||||
}
|
||||
|
||||
let price = effective_gas_price(&tx, base_fee_exec);
|
||||
let budget = tx.wei_budget();
|
||||
let inspector = IgneumInspector::new(tx.intrinsic_gas, price, base_fee_proving, budget);
|
||||
let (result, state, insp) = {
|
||||
let ctx = Context::mainnet().with_db(DbRef(db)).with_cfg(cfg(f.chain_id)).with_block(block_env(env, b.miner));
|
||||
let mut evm = ctx.build_mainnet_with_inspector(inspector);
|
||||
let result = evm.inspect_one_tx(tx_env(&tx));
|
||||
let state = evm.finalize();
|
||||
let insp = evm.inspector.clone();
|
||||
(result, state, insp)
|
||||
};
|
||||
let result = match result {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
skipped.push((tx.hash, skip_reason_from(&e)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let pgas_used = insp.pgas.saturating_add(INTRINSIC_PGAS_PER_TX);
|
||||
if block_pgas.saturating_add(insp.pgas) > BLOCK_PROVING_GAS_LIMIT {
|
||||
skipped.push((tx.hash, Skip::BlockProvingBudget));
|
||||
continue;
|
||||
}
|
||||
block_pgas = block_pgas.saturating_add(insp.pgas);
|
||||
segment_pgas = segment_pgas.saturating_add(insp.pgas);
|
||||
|
||||
let (status, gas_used, logs) = match &result {
|
||||
ExecutionResult::Success { gas, logs, .. } => (true, gas.tx_gas_used(), logs.clone()),
|
||||
ExecutionResult::Revert { gas, logs, .. } => (false, gas.tx_gas_used(), logs.clone()),
|
||||
ExecutionResult::Halt { gas, logs, .. } => (false, gas.tx_gas_used(), logs.clone()),
|
||||
};
|
||||
|
||||
// Fee flows (design 4.1 and 4.4), exactly as the node applies them.
|
||||
let (status, gas_used, logs) = if insp.over_budget {
|
||||
let gas_used = tx.gas_limit();
|
||||
let burned_exec = (gas_used as u128) * base_fee_exec;
|
||||
let burned_proving = ((pgas_used as u128) * base_fee_proving).min(budget.saturating_sub(burned_exec));
|
||||
let tip_total = budget.saturating_sub(burned_exec + burned_proving);
|
||||
db.bump_nonce(tx.sender);
|
||||
db.sub_balance(tx.sender, U256::from(budget));
|
||||
let shares = developer_shares(tip_total, &insp.attributions, |code| registered_payee(db, code));
|
||||
let dev_total: u128 = shares.iter().map(|(_, w)| *w).sum();
|
||||
db.add_balance(b.miner, U256::from(tip_total - dev_total));
|
||||
for (payee, wei) in &shares {
|
||||
if let Some(p) = payee {
|
||||
db.add_balance(*p, U256::from(*wei));
|
||||
}
|
||||
}
|
||||
(false, gas_used, Vec::new())
|
||||
} else {
|
||||
db.commit(state);
|
||||
let burned_proving = (pgas_used as u128) * base_fee_proving;
|
||||
db.sub_balance(tx.sender, U256::from(burned_proving));
|
||||
let tip_total = (gas_used as u128) * (price - base_fee_exec);
|
||||
let shares = developer_shares(tip_total, &insp.attributions, |code| registered_payee(db, code));
|
||||
let dev_total: u128 = shares.iter().map(|(_, w)| *w).sum();
|
||||
db.sub_balance(b.miner, U256::from(dev_total));
|
||||
for (payee, wei) in &shares {
|
||||
if let Some(p) = payee {
|
||||
db.add_balance(*p, U256::from(*wei));
|
||||
}
|
||||
}
|
||||
(status, gas_used, logs)
|
||||
};
|
||||
|
||||
block_gas = block_gas.saturating_add(gas_used);
|
||||
cumulative_gas = cumulative_gas.saturating_add(gas_used);
|
||||
let mut bloom = Bloom::default();
|
||||
bloom.accrue_logs(&logs);
|
||||
executed.push(ExecutedReceipt {
|
||||
tx_hash: tx.hash,
|
||||
status,
|
||||
gas_used,
|
||||
pgas_used,
|
||||
cumulative_gas_used: cumulative_gas,
|
||||
logs,
|
||||
logs_bloom: bloom,
|
||||
tx_type: tx.tx_type(),
|
||||
over_budget: insp.over_budget,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let state_root = db.state_root();
|
||||
let receipts_root = receipts_root(&executed);
|
||||
BlockOutcome { executed, skipped, gas_used: cumulative_gas, pgas_used: segment_pgas, state_root, receipts_root }
|
||||
}
|
||||
|
||||
pub fn registered_payee(db: &IgneumDb, code_address: Address) -> Option<Address> {
|
||||
let word = db.storage(DEVELOPER_REGISTRY_ADDRESS, payee_slot(code_address));
|
||||
if word.is_zero() {
|
||||
None
|
||||
} else {
|
||||
Some(address_from_word(word))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receipts_root(executed: &[ExecutedReceipt]) -> B256 {
|
||||
alloy_trie::root::ordered_trie_root_with_encoder(executed, |r, buf| {
|
||||
let receipt = Receipt { status: r.status.into(), cumulative_gas_used: r.cumulative_gas_used, logs: r.logs.clone() };
|
||||
let with_bloom = ReceiptWithBloom { receipt, logs_bloom: r.logs_bloom };
|
||||
let envelope = match r.tx_type {
|
||||
1 => ReceiptEnvelope::Eip2930(with_bloom),
|
||||
2 => ReceiptEnvelope::Eip1559(with_bloom),
|
||||
_ => ReceiptEnvelope::Legacy(with_bloom),
|
||||
};
|
||||
envelope.encode_2718(buf);
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads the fixture's pre-state and block hashes into a fresh database.
|
||||
pub fn load_pre_state(f: &BlockFixture) -> IgneumDb {
|
||||
let mut db = IgneumDb::new();
|
||||
for a in &f.pre_state {
|
||||
db.insert_account(a.address, a.nonce, a.balance, &a.code, &a.storage);
|
||||
}
|
||||
for (n, h) in &f.block_hashes {
|
||||
db.push_block_hash(*n, *h);
|
||||
}
|
||||
db
|
||||
}
|
||||
|
||||
/// The whole statement: pre-state root, execution, outputs. Used by the guest and by the host's native check.
|
||||
pub fn prove_statement(f: &BlockFixture) -> crate::output::ProveOutput {
|
||||
let mut db = load_pre_state(f);
|
||||
let pre_state_root = db.state_root();
|
||||
let out = execute_block(&mut db, f);
|
||||
crate::output::ProveOutput {
|
||||
chain_id: f.chain_id,
|
||||
number: f.env.number,
|
||||
block_hash: f.env.hash,
|
||||
tx_commitment: f.tx_commitment(),
|
||||
pre_state_root,
|
||||
post_state_root: out.state_root,
|
||||
receipts_root: out.receipts_root,
|
||||
gas_used: out.gas_used,
|
||||
pgas_used: out.pgas_used,
|
||||
executed: out.executed.len() as u32,
|
||||
skipped: out.skipped.len() as u32,
|
||||
}
|
||||
}
|
||||
100
proving/igneum-prove/core/src/fixture.rs
Normal file
100
proving/igneum-prove/core/src/fixture.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
//! The fixture: one real chain block as the execution layer exported it, with the pre-state it ran on.
|
||||
//! JSON on disk (`proving/fixtures/*.json`, human readable, hex strings); the same structs go to the guest as
|
||||
//! bincode through `ProveInput`.
|
||||
|
||||
use alloy_primitives::{Address, Bytes, B256, U256};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const FORMAT: &str = "igneum-prove-fixture-v0";
|
||||
|
||||
/// The chain block's environment (design section 3), as the node computed it.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct FixtureEnv {
|
||||
pub number: u64,
|
||||
pub hash: B256,
|
||||
pub parent_hash: B256,
|
||||
pub timestamp: u64,
|
||||
pub miner: Address,
|
||||
pub prevrandao: B256,
|
||||
/// Base fees in wei per unit, both dimensions (fit in u64 on the devnet; the executor widens to u128).
|
||||
pub base_fee_exec: u64,
|
||||
pub base_fee_proving: u64,
|
||||
}
|
||||
|
||||
/// One block of the segment in sequence order: its miner (the beneficiary of its transactions) and its
|
||||
/// transactions in body order. `blue` blocks earned a reward (carried separately in `rewards`).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct IncludingBlock {
|
||||
pub miner: Address,
|
||||
pub blue: bool,
|
||||
pub txs: Vec<Bytes>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct AccountFixture {
|
||||
pub address: Address,
|
||||
pub nonce: u64,
|
||||
pub balance: U256,
|
||||
pub code: Bytes,
|
||||
/// Non-zero storage, (slot, value), sorted by slot.
|
||||
pub storage: Vec<(U256, U256)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct BlockFixture {
|
||||
pub chain_id: u64,
|
||||
pub env: FixtureEnv,
|
||||
/// (chain height, chain block hash) for BLOCKHASH, the last up to 256 chain blocks before this one.
|
||||
pub block_hashes: Vec<(u64, B256)>,
|
||||
/// Rewards by rule for this segment (design 1.1): 80% of the subsidy to each blue block's miner, in wei.
|
||||
pub rewards: Vec<(Address, U256)>,
|
||||
/// The 20% proving-pool share credited to the pool escrow for this segment, in wei.
|
||||
pub proving_pool_credit: U256,
|
||||
pub blocks: Vec<IncludingBlock>,
|
||||
pub pre_state: Vec<AccountFixture>,
|
||||
}
|
||||
|
||||
/// What the native run says the proof must reproduce.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Expected {
|
||||
pub pre_state_root: B256,
|
||||
pub post_state_root: B256,
|
||||
pub receipts_root: B256,
|
||||
pub gas_used: u64,
|
||||
pub pgas_used: u64,
|
||||
pub executed: u32,
|
||||
pub skipped: u32,
|
||||
/// The state root the node itself reported for this segment (from the export). Must equal `post_state_root`.
|
||||
pub node_state_root: B256,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Fixture {
|
||||
pub format: String,
|
||||
pub source: String,
|
||||
pub block: BlockFixture,
|
||||
pub expected: Expected,
|
||||
}
|
||||
|
||||
/// What the guest reads: the block fixture alone. The expected values stay on the host.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ProveInput {
|
||||
pub block: BlockFixture,
|
||||
}
|
||||
|
||||
impl BlockFixture {
|
||||
/// keccak256 over the raw transactions in sequence order with their including miners: the commitment the
|
||||
/// proof makes to its ordered input.
|
||||
pub fn tx_commitment(&self) -> B256 {
|
||||
let mut buf = Vec::new();
|
||||
for b in &self.blocks {
|
||||
buf.extend_from_slice(b.miner.as_slice());
|
||||
buf.push(b.blue as u8);
|
||||
for t in &b.txs {
|
||||
buf.extend_from_slice(&(t.len() as u32).to_be_bytes());
|
||||
buf.extend_from_slice(t);
|
||||
}
|
||||
}
|
||||
alloy_primitives::keccak256(buf)
|
||||
}
|
||||
}
|
||||
30
proving/igneum-prove/core/src/lib.rs
Normal file
30
proving/igneum-prove/core/src/lib.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//! Igneum proving v0: the statement one SP1 proof makes about one chain block.
|
||||
//!
|
||||
//! Statement. Given a chain block's environment, the ordered transaction list of its segment (per including
|
||||
//! block, in sequence order), the rewards the consensus rules credit, and the pre-state (every account the
|
||||
//! executor can touch, here the whole in-memory devnet state), running the Igneum executor yields the post-state
|
||||
//! root and the receipts root. The guest commits both plus a commitment to the inputs (section `output`).
|
||||
//!
|
||||
//! This crate is a port of `vendor/igneum-node-exec/igneum/exec/src/{executor,state,pgas,registry,config}.rs`
|
||||
//! at commit fb33069 (branch execution-layer, 3 October 2026) with the kaspa types removed so it compiles for
|
||||
//! the zkVM target. The decoder is the execution layer's own crate (`igneum-evm-types`). Every rule is the
|
||||
//! node's rule: rewards by rule, the nonce-rule skip, two-dimensional gas with the prototype pgas table, fee
|
||||
//! flows (base fees burned, proving charge burned, 80/20 tip with the developer split per call frame), the
|
||||
//! registry population on CREATE, the state root over every non-empty account (EIP-161) through alloy-trie.
|
||||
//! The exporter (`export/`) checks the port against the node by replaying the simnet's export from genesis
|
||||
//! and comparing every segment's state root; design 2.1 ("the executor is a library with no network
|
||||
//! dependency, used by the node, the shard planner, the prover's witness generator") is where this duplicate
|
||||
//! goes away.
|
||||
|
||||
pub mod config;
|
||||
pub mod executor;
|
||||
pub mod fixture;
|
||||
pub mod output;
|
||||
pub mod pgas;
|
||||
pub mod registry;
|
||||
pub mod state;
|
||||
|
||||
pub use executor::{execute_block, BlockOutcome};
|
||||
pub use fixture::{AccountFixture, BlockFixture, Expected, Fixture, FixtureEnv, IncludingBlock, ProveInput};
|
||||
pub use output::ProveOutput;
|
||||
pub use state::IgneumDb;
|
||||
60
proving/igneum-prove/core/src/output.rs
Normal file
60
proving/igneum-prove/core/src/output.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//! The public values of the proof, in one fixed byte layout so a verifier in any language can read them.
|
||||
|
||||
use alloy_primitives::B256;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ProveOutput {
|
||||
pub chain_id: u64,
|
||||
pub number: u64,
|
||||
pub block_hash: B256,
|
||||
pub tx_commitment: B256,
|
||||
pub pre_state_root: B256,
|
||||
pub post_state_root: B256,
|
||||
pub receipts_root: B256,
|
||||
pub gas_used: u64,
|
||||
pub pgas_used: u64,
|
||||
pub executed: u32,
|
||||
pub skipped: u32,
|
||||
}
|
||||
|
||||
impl ProveOutput {
|
||||
pub const LEN: usize = 8 + 8 + 32 * 5 + 8 + 8 + 4 + 4;
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut v = Vec::with_capacity(Self::LEN);
|
||||
v.extend_from_slice(&self.chain_id.to_be_bytes());
|
||||
v.extend_from_slice(&self.number.to_be_bytes());
|
||||
v.extend_from_slice(self.block_hash.as_slice());
|
||||
v.extend_from_slice(self.tx_commitment.as_slice());
|
||||
v.extend_from_slice(self.pre_state_root.as_slice());
|
||||
v.extend_from_slice(self.post_state_root.as_slice());
|
||||
v.extend_from_slice(self.receipts_root.as_slice());
|
||||
v.extend_from_slice(&self.gas_used.to_be_bytes());
|
||||
v.extend_from_slice(&self.pgas_used.to_be_bytes());
|
||||
v.extend_from_slice(&self.executed.to_be_bytes());
|
||||
v.extend_from_slice(&self.skipped.to_be_bytes());
|
||||
v
|
||||
}
|
||||
|
||||
pub fn from_bytes(b: &[u8]) -> Option<Self> {
|
||||
if b.len() != Self::LEN {
|
||||
return None;
|
||||
}
|
||||
let u64_at = |i: usize| u64::from_be_bytes(b[i..i + 8].try_into().unwrap());
|
||||
let u32_at = |i: usize| u32::from_be_bytes(b[i..i + 4].try_into().unwrap());
|
||||
let b256_at = |i: usize| B256::from_slice(&b[i..i + 32]);
|
||||
Some(Self {
|
||||
chain_id: u64_at(0),
|
||||
number: u64_at(8),
|
||||
block_hash: b256_at(16),
|
||||
tx_commitment: b256_at(48),
|
||||
pre_state_root: b256_at(80),
|
||||
post_state_root: b256_at(112),
|
||||
receipts_root: b256_at(144),
|
||||
gas_used: u64_at(176),
|
||||
pgas_used: u64_at(184),
|
||||
executed: u32_at(192),
|
||||
skipped: u32_at(196),
|
||||
})
|
||||
}
|
||||
}
|
||||
266
proving/igneum-prove/core/src/pgas.rs
Normal file
266
proving/igneum-prove/core/src/pgas.rs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
//! Mirror of igneum-exec `pgas.rs` at fb33069 (the prototype pgas table and the per-frame developer attribution).
|
||||
//! Proving gas (design 4.2) and the per-frame developer attribution (design 4.5), both measured by one revm
|
||||
//! inspector while the native execution runs.
|
||||
//!
|
||||
//! The table below is a PROTOTYPE: shapes follow the design's table (fixed per plain opcode, per word for copies
|
||||
//! and logs, per trie access for state opcodes, per input block for precompiles) with placeholder magnitudes.
|
||||
//! The design requires every entry to be calibrated in the reference prover (SP1) with three input sizes before
|
||||
//! the public testnet (R1). Unit: 1 pgas = 1,000 reference-prover cycles, rounded up.
|
||||
|
||||
use crate::registry::{address_from_word, creator_slot, payee_slot, word_from_address};
|
||||
use alloy_primitives::{Address, U256};
|
||||
use revm::context_interface::{ContextTr, JournalTr};
|
||||
use revm::inspector::Inspector;
|
||||
use revm::interpreter::interpreter_types::Jumps;
|
||||
use revm::interpreter::{CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Interpreter};
|
||||
|
||||
/// Fixed pgas of one opcode plus a per-word (32-byte) component read from the stack where the shape needs it.
|
||||
pub fn opcode_pgas(op: u8, interp: &Interpreter) -> u64 {
|
||||
// Stack items from the top: peek(0) is the top. Sizes are clamped so a hostile operand cannot overflow.
|
||||
let word_count = |depth: usize| -> u64 {
|
||||
match interp.stack.peek(depth) {
|
||||
Ok(v) => {
|
||||
let n: u64 = v.try_into().unwrap_or(u64::MAX);
|
||||
n.min(1 << 24).div_ceil(32)
|
||||
}
|
||||
Err(_) => 0,
|
||||
}
|
||||
};
|
||||
match op {
|
||||
// KECCAK256: fixed plus per 136-byte block, approximated per word
|
||||
0x20 => 30 + 6 * word_count(1),
|
||||
// State reads and writes: the largest per-op entries (one MPT path each)
|
||||
0x31 | 0x3b | 0x3f => 100, // BALANCE, EXTCODESIZE, EXTCODEHASH
|
||||
0x3c => 100 + 2 * word_count(3), // EXTCODECOPY
|
||||
0x54 => 200, // SLOAD
|
||||
0x55 => 300, // SSTORE
|
||||
0x5c => 20, // TLOAD
|
||||
0x5d => 20, // TSTORE
|
||||
0x47 => 20, // SELFBALANCE
|
||||
0x40 => 20, // BLOCKHASH
|
||||
// Memory and copies: per word
|
||||
0x51 | 0x52 | 0x53 => 2, // MLOAD, MSTORE, MSTORE8
|
||||
0x37 | 0x39 | 0x3e | 0x5e => 2 + word_count(2), // CALLDATACOPY, CODECOPY, RETURNDATACOPY, MCOPY
|
||||
0xa0..=0xa4 => 10 + word_count(1) + 5 * (op - 0xa0) as u64, // LOGn
|
||||
0xf3 | 0xfd => 2 + word_count(1), // RETURN, REVERT
|
||||
// EXP: per byte of exponent
|
||||
0x0a => {
|
||||
let bytes = interp.stack.peek(1).map(|e| (256 - e.leading_zeros()).div_ceil(8) as u64).unwrap_or(0);
|
||||
10 + 5 * bytes
|
||||
}
|
||||
// Arithmetic heavier than add
|
||||
0x02..=0x09 | 0x0b => 3, // MUL..MULMOD, SIGNEXTEND
|
||||
// Calls and creates: the frame itself (the callee's work is metered when it runs)
|
||||
0xf1 | 0xf2 | 0xf4 | 0xfa => 100,
|
||||
0xf0 => 300,
|
||||
0xf5 => 300 + 6 * word_count(2),
|
||||
0xff => 200,
|
||||
// Control flow
|
||||
0x56 | 0x57 => 2,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Precompile pgas by address and input length (design 4.2 shapes, prototype magnitudes).
|
||||
pub fn precompile_pgas(address: Address, input_len: usize, input: &[u8]) -> Option<u64> {
|
||||
let words = (input_len as u64).div_ceil(32);
|
||||
let last = address.as_slice()[19];
|
||||
if address.as_slice()[..19].iter().any(|b| *b != 0) {
|
||||
return None;
|
||||
}
|
||||
Some(match last {
|
||||
0x01 => 3_000, // ecrecover: one secp256k1 recovery (zkVM precompile)
|
||||
0x02 => 60 + 12 * words, // sha256
|
||||
0x03 => 600 + 120 * words, // ripemd160: no zkVM precompile
|
||||
0x04 => 15 + 3 * words, // identity
|
||||
0x05 => 1_000 + 10 * input_len as u64, // modexp: pure big-number arithmetic, expected to be the costliest per gas
|
||||
0x06 => 500, // ecadd
|
||||
0x07 => 6_000, // ecmul
|
||||
0x08 => 45_000 + 34_000 * (input_len as u64 / 192), // ecpairing per pair
|
||||
0x09 => {
|
||||
let rounds = if input.len() >= 4 { u32::from_be_bytes([input[0], input[1], input[2], input[3]]) as u64 } else { 0 };
|
||||
100 + rounds.min(1 << 20)
|
||||
}
|
||||
0x0a => 50_000, // point evaluation: absent by design, charged if ever reached
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// One open call frame while execution runs.
|
||||
#[derive(Debug, Clone)]
|
||||
struct OpenFrame {
|
||||
/// The account whose code runs: the callee for CALL and STATICCALL, the code address for DELEGATECALL and
|
||||
/// CALLCODE (design 4.5), the new contract for CREATE.
|
||||
code_address: Address,
|
||||
/// Execution gas the parent had spent when it opened this frame, minus the gas it forwarded, so the running
|
||||
/// total across the stack counts every unit once.
|
||||
parent_spent_adjusted: u64,
|
||||
/// Execution gas spent by this frame's own opcodes (children subtracted at their end).
|
||||
own_gas_so_far: u64,
|
||||
last_seen_spent: u64,
|
||||
}
|
||||
|
||||
/// Per-frame attribution of execution gas, closed frames.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FrameAttribution {
|
||||
pub code_address: Address,
|
||||
pub own_gas: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct IgneumInspector {
|
||||
/// Proving gas metered so far (opcodes plus precompiles), without the intrinsic per-transaction pgas.
|
||||
pub pgas: u64,
|
||||
/// Execution gas spent before the first frame opened (the intrinsic gas), supplied by the executor.
|
||||
pub intrinsic_gas: u64,
|
||||
/// Price per execution gas unit the sender pays (`f_e + tip`), the proving base fee, and the signed budget.
|
||||
pub exec_price: u128,
|
||||
pub proving_base_fee: u128,
|
||||
pub budget_wei: u128,
|
||||
/// Set when the running charge crossed the budget and the current frame was halted (design 4.1).
|
||||
pub over_budget: bool,
|
||||
pub attributions: Vec<FrameAttribution>,
|
||||
/// Successful CREATE and CREATE2 frames as (creator, new contract), in order.
|
||||
pub creates: Vec<(Address, Address)>,
|
||||
stack: Vec<OpenFrame>,
|
||||
/// Gas spent in the frame that is executing right now, as of the last step seen.
|
||||
current_spent: u64,
|
||||
}
|
||||
|
||||
impl IgneumInspector {
|
||||
pub fn new(intrinsic_gas: u64, exec_price: u128, proving_base_fee: u128, budget_wei: u128) -> Self {
|
||||
Self { intrinsic_gas, exec_price, proving_base_fee, budget_wei, ..Default::default() }
|
||||
}
|
||||
|
||||
/// Execution gas spent by the whole transaction so far, every unit counted once (design 4.1 running charge).
|
||||
fn running_gas(&self, current_spent: u64) -> u64 {
|
||||
self.intrinsic_gas + self.stack.iter().map(|f| f.parent_spent_adjusted).sum::<u64>() + current_spent
|
||||
}
|
||||
|
||||
fn running_charge(&self, current_spent: u64) -> u128 {
|
||||
(self.running_gas(current_spent) as u128) * self.exec_price + (self.pgas as u128) * self.proving_base_fee
|
||||
}
|
||||
|
||||
fn open(&mut self, code_address: Address, forwarded_gas: u64) {
|
||||
let parent_spent_adjusted = if self.stack.is_empty() { 0 } else { self.current_spent.saturating_sub(forwarded_gas) };
|
||||
self.stack.push(OpenFrame { code_address, parent_spent_adjusted, own_gas_so_far: 0, last_seen_spent: 0 });
|
||||
self.current_spent = 0;
|
||||
}
|
||||
|
||||
fn close(&mut self, spent_by_frame: u64) {
|
||||
if let Some(frame) = self.stack.pop() {
|
||||
self.attributions.push(FrameAttribution { code_address: frame.code_address, own_gas: frame.own_gas_so_far });
|
||||
// The parent's spent at the CALL step included the forwarded limit; what the child did not use comes
|
||||
// back to the parent, so the parent's own count is unaffected here. Restore the parent's view.
|
||||
self.current_spent = frame.parent_spent_adjusted.saturating_add(spent_by_frame);
|
||||
if let Some(parent) = self.stack.last_mut() {
|
||||
parent.last_seen_spent = self.current_spent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTX: ContextTr> Inspector<CTX> for IgneumInspector {
|
||||
fn step(&mut self, interp: &mut Interpreter, _ctx: &mut CTX) {
|
||||
let op = interp.bytecode.opcode();
|
||||
self.pgas = self.pgas.saturating_add(opcode_pgas(op, interp));
|
||||
let spent = interp.gas.total_gas_spent();
|
||||
if let Some(frame) = self.stack.last_mut() {
|
||||
// Gas of the previous instruction of this frame lands here (spent moved since the last look).
|
||||
frame.own_gas_so_far = frame.own_gas_so_far.saturating_add(spent.saturating_sub(frame.last_seen_spent));
|
||||
frame.last_seen_spent = spent;
|
||||
}
|
||||
self.current_spent = spent;
|
||||
if self.running_charge(spent) > self.budget_wei {
|
||||
self.over_budget = true;
|
||||
interp.halt_oog();
|
||||
}
|
||||
}
|
||||
|
||||
fn step_end(&mut self, interp: &mut Interpreter, _ctx: &mut CTX) {
|
||||
let spent = interp.gas.total_gas_spent();
|
||||
if let Some(frame) = self.stack.last_mut() {
|
||||
frame.own_gas_so_far = frame.own_gas_so_far.saturating_add(spent.saturating_sub(frame.last_seen_spent));
|
||||
frame.last_seen_spent = spent;
|
||||
}
|
||||
self.current_spent = spent;
|
||||
}
|
||||
|
||||
fn call(&mut self, _ctx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
|
||||
let code_address = match inputs.scheme {
|
||||
CallScheme::Call | CallScheme::StaticCall => inputs.target_address,
|
||||
CallScheme::CallCode | CallScheme::DelegateCall => inputs.bytecode_address,
|
||||
};
|
||||
// Precompile frames are charged here (they run no opcodes); their attribution share burns (design 4.5).
|
||||
let input_bytes: Vec<u8> = inputs.input.bytes(_ctx).to_vec();
|
||||
if let Some(p) = precompile_pgas(inputs.bytecode_address, input_bytes.len(), &input_bytes) {
|
||||
self.pgas = self.pgas.saturating_add(p);
|
||||
}
|
||||
self.open(code_address, inputs.gas_limit);
|
||||
None
|
||||
}
|
||||
|
||||
fn call_end(&mut self, _ctx: &mut CTX, _inputs: &CallInputs, outcome: &mut CallOutcome) {
|
||||
self.close(outcome.result.gas.total_gas_spent());
|
||||
}
|
||||
|
||||
fn create(&mut self, ctx: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
|
||||
// The new address is known from the creator's nonce (CREATE) or salt (CREATE2) before the frame runs.
|
||||
let nonce = ctx.journal_mut().load_account(inputs.caller()).map(|a| a.info.nonce).unwrap_or(0);
|
||||
let new = inputs.created_address(nonce);
|
||||
self.open(new, inputs.gas_limit());
|
||||
None
|
||||
}
|
||||
|
||||
fn create_end(&mut self, ctx: &mut CTX, inputs: &CreateInputs, outcome: &mut CreateOutcome) {
|
||||
self.close(outcome.result.gas.total_gas_spent());
|
||||
if let (true, Some(new)) = (outcome.result.is_ok(), outcome.address) {
|
||||
self.creates.push((inputs.caller(), new));
|
||||
// Design 4.5, populated by rule: creator recorded, payee inherited from the creator when it has one.
|
||||
let registry = crate::config::DEVELOPER_REGISTRY_ADDRESS;
|
||||
let journal = ctx.journal_mut();
|
||||
if journal.load_account(registry).is_ok() {
|
||||
let _ = journal.sstore(registry, creator_slot(new), word_from_address(inputs.caller()));
|
||||
let inherited = journal.sload(registry, payee_slot(inputs.caller())).map(|v| v.data).unwrap_or(U256::ZERO);
|
||||
if inherited != U256::ZERO {
|
||||
let existing = journal.sload(registry, payee_slot(new)).map(|v| v.data).unwrap_or(U256::ZERO);
|
||||
if existing == U256::ZERO {
|
||||
let _ = journal.sstore(registry, payee_slot(new), inherited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the developer share of a transaction's tip (design 4.5): 20% of the tip, divided among frames by own
|
||||
/// execution gas, each part to the payee registered for the frame's code address, unregistered parts burned.
|
||||
/// Returns (payee or None for burn, wei) per frame with a non-zero part.
|
||||
pub fn developer_shares(
|
||||
tip_total_wei: u128,
|
||||
attributions: &[FrameAttribution],
|
||||
payee_of: impl Fn(Address) -> Option<Address>,
|
||||
) -> Vec<(Option<Address>, u128)> {
|
||||
let dev_total = tip_total_wei * crate::config::DEVELOPER_SHARE_PERCENT / 100;
|
||||
let gas_total: u128 = attributions.iter().map(|a| a.own_gas as u128).sum();
|
||||
if dev_total == 0 || gas_total == 0 {
|
||||
return if dev_total > 0 { vec![(None, dev_total)] } else { vec![] };
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
let mut assigned = 0u128;
|
||||
for a in attributions {
|
||||
let part = dev_total * (a.own_gas as u128) / gas_total;
|
||||
if part > 0 {
|
||||
out.push((payee_of(a.code_address), part));
|
||||
assigned += part;
|
||||
}
|
||||
}
|
||||
if dev_total > assigned {
|
||||
out.push((None, dev_total - assigned)); // rounding remainder burns
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn address_from_u256(word: U256) -> Address {
|
||||
address_from_word(word)
|
||||
}
|
||||
28
proving/igneum-prove/core/src/registry.rs
Normal file
28
proving/igneum-prove/core/src/registry.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//! The `DeveloperRegistry` storage layout (mirror of igneum-exec `registry.rs` at fb33069): slot 0
|
||||
//! `mapping(address => address) payee`, slot 1 `mapping(address => address) creator`.
|
||||
|
||||
use alloy_primitives::{keccak256, Address, B256, U256};
|
||||
|
||||
fn mapping_slot(key: Address, slot: u64) -> U256 {
|
||||
let mut buf = [0u8; 64];
|
||||
buf[12..32].copy_from_slice(key.as_slice());
|
||||
buf[56..64].copy_from_slice(&slot.to_be_bytes());
|
||||
U256::from_be_bytes(keccak256(buf).0)
|
||||
}
|
||||
|
||||
pub fn payee_slot(account: Address) -> U256 {
|
||||
mapping_slot(account, 0)
|
||||
}
|
||||
|
||||
pub fn creator_slot(account: Address) -> U256 {
|
||||
mapping_slot(account, 1)
|
||||
}
|
||||
|
||||
pub fn address_from_word(word: U256) -> Address {
|
||||
let bytes: B256 = B256::from(word.to_be_bytes::<32>());
|
||||
Address::from_slice(&bytes[12..32])
|
||||
}
|
||||
|
||||
pub fn word_from_address(address: Address) -> U256 {
|
||||
U256::from_be_slice(address.as_slice())
|
||||
}
|
||||
189
proving/igneum-prove/core/src/state.rs
Normal file
189
proving/igneum-prove/core/src/state.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
//! In-memory Ethereum state with the MPT state root as an output (mirror of igneum-exec `state.rs` at fb33069).
|
||||
|
||||
use crate::config::BLOCKHASH_WINDOW;
|
||||
use alloy_primitives::{keccak256, Address, B256, U256};
|
||||
use alloy_trie::TrieAccount;
|
||||
use revm::bytecode::Bytecode;
|
||||
use revm::database::{AccountState, CacheDB, EmptyDB};
|
||||
use revm::primitives::KECCAK_EMPTY;
|
||||
use revm::state::{AccountInfo, EvmState};
|
||||
use revm::{Database, DatabaseCommit};
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::convert::Infallible;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IgneumDb {
|
||||
pub cache: CacheDB<EmptyDB>,
|
||||
pub block_hashes: VecDeque<(u64, B256)>,
|
||||
}
|
||||
|
||||
impl Default for IgneumDb {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl IgneumDb {
|
||||
pub fn new() -> Self {
|
||||
Self { cache: CacheDB::new(EmptyDB::default()), block_hashes: VecDeque::new() }
|
||||
}
|
||||
|
||||
pub fn push_block_hash(&mut self, number: u64, hash: B256) {
|
||||
self.block_hashes.push_back((number, hash));
|
||||
while self.block_hashes.len() as u64 > BLOCKHASH_WINDOW {
|
||||
self.block_hashes.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a full account (code and storage included) as the fixture carries it.
|
||||
pub fn insert_account(&mut self, address: Address, nonce: u64, balance: U256, code: &[u8], storage: &[(U256, U256)]) {
|
||||
let info = if code.is_empty() {
|
||||
AccountInfo { nonce, balance, ..Default::default() }
|
||||
} else {
|
||||
let mut info = AccountInfo::from_bytecode(Bytecode::new_raw(code.to_vec().into()));
|
||||
info.nonce = nonce;
|
||||
info.balance = balance;
|
||||
info
|
||||
};
|
||||
self.cache.insert_account_info(address, info);
|
||||
for (slot, value) in storage {
|
||||
let _ = self.cache.insert_account_storage(address, *slot, *value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account(&self, address: Address) -> Option<&AccountInfo> {
|
||||
self.cache.cache.accounts.get(&address).filter(|a| a.account_state != AccountState::NotExisting).map(|a| &a.info)
|
||||
}
|
||||
|
||||
pub fn balance(&self, address: Address) -> U256 {
|
||||
self.account(address).map(|a| a.balance).unwrap_or(U256::ZERO)
|
||||
}
|
||||
|
||||
pub fn nonce(&self, address: Address) -> u64 {
|
||||
self.account(address).map(|a| a.nonce).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn code(&self, address: Address) -> Vec<u8> {
|
||||
match self.account(address) {
|
||||
Some(info) if info.code_hash != KECCAK_EMPTY => info
|
||||
.code
|
||||
.as_ref()
|
||||
.map(|c| c.original_bytes().to_vec())
|
||||
.or_else(|| self.cache.cache.contracts.get(&info.code_hash).map(|c| c.original_bytes().to_vec()))
|
||||
.unwrap_or_default(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage(&self, address: Address, slot: U256) -> U256 {
|
||||
self.cache.cache.accounts.get(&address).and_then(|a| a.storage.get(&slot).copied()).unwrap_or(U256::ZERO)
|
||||
}
|
||||
|
||||
/// Non-zero storage of an account, sorted by slot.
|
||||
pub fn storage_of(&self, address: Address) -> Vec<(U256, U256)> {
|
||||
let mut out: Vec<(U256, U256)> = self
|
||||
.cache
|
||||
.cache
|
||||
.accounts
|
||||
.get(&address)
|
||||
.map(|a| a.storage.iter().filter(|(_, v)| !v.is_zero()).map(|(k, v)| (*k, *v)).collect())
|
||||
.unwrap_or_default();
|
||||
out.sort();
|
||||
out
|
||||
}
|
||||
|
||||
pub fn add_balance(&mut self, address: Address, wei: U256) {
|
||||
if wei.is_zero() {
|
||||
return;
|
||||
}
|
||||
let entry = self.cache.cache.accounts.entry(address).or_default();
|
||||
if entry.account_state == AccountState::NotExisting {
|
||||
entry.account_state = AccountState::None;
|
||||
}
|
||||
entry.info.balance = entry.info.balance.saturating_add(wei);
|
||||
}
|
||||
|
||||
pub fn sub_balance(&mut self, address: Address, wei: U256) {
|
||||
if wei.is_zero() {
|
||||
return;
|
||||
}
|
||||
if let Some(entry) = self.cache.cache.accounts.get_mut(&address) {
|
||||
entry.info.balance = entry.info.balance.saturating_sub(wei);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bump_nonce(&mut self, address: Address) {
|
||||
let entry = self.cache.cache.accounts.entry(address).or_default();
|
||||
if entry.account_state == AccountState::NotExisting {
|
||||
entry.account_state = AccountState::None;
|
||||
}
|
||||
entry.info.nonce += 1;
|
||||
}
|
||||
|
||||
/// Ethereum state root over every existing, non-empty account (EIP-161), keccak-keyed MPT through alloy-trie.
|
||||
pub fn state_root(&self) -> B256 {
|
||||
let mut accounts: BTreeMap<B256, TrieAccount> = BTreeMap::new();
|
||||
for (address, acc) in self.cache.cache.accounts.iter() {
|
||||
if acc.account_state == AccountState::NotExisting {
|
||||
continue;
|
||||
}
|
||||
let info = &acc.info;
|
||||
let has_storage = acc.storage.values().any(|v| !v.is_zero());
|
||||
if info.balance.is_zero() && info.nonce == 0 && info.code_hash == KECCAK_EMPTY && !has_storage {
|
||||
continue;
|
||||
}
|
||||
let storage_root = alloy_trie::root::storage_root_unhashed(
|
||||
acc.storage.iter().filter(|(_, v)| !v.is_zero()).map(|(k, v)| (B256::from(k.to_be_bytes::<32>()), *v)),
|
||||
);
|
||||
accounts.insert(keccak256(address), TrieAccount { nonce: info.nonce, balance: info.balance, storage_root, code_hash: info.code_hash });
|
||||
}
|
||||
alloy_trie::root::state_root(accounts)
|
||||
}
|
||||
|
||||
/// Every account address in the state, sorted.
|
||||
pub fn addresses(&self) -> Vec<Address> {
|
||||
let mut v: Vec<Address> = self.cache.cache.accounts.iter().filter(|(_, a)| a.account_state != AccountState::NotExisting).map(|(a, _)| *a).collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for IgneumDb {
|
||||
type Error = Infallible;
|
||||
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Infallible> {
|
||||
Ok(self.cache.basic(address).unwrap())
|
||||
}
|
||||
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Infallible> {
|
||||
Ok(self.cache.code_by_hash(code_hash).unwrap())
|
||||
}
|
||||
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Infallible> {
|
||||
Ok(self.cache.storage(address, index).unwrap())
|
||||
}
|
||||
fn block_hash(&mut self, number: u64) -> Result<B256, Infallible> {
|
||||
Ok(self.block_hashes.iter().find(|(n, _)| *n == number).map(|(_, h)| *h).unwrap_or(B256::ZERO))
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseCommit for IgneumDb {
|
||||
fn commit(&mut self, changes: EvmState) {
|
||||
self.cache.commit(changes)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DbRef<'a>(pub &'a mut IgneumDb);
|
||||
|
||||
impl Database for DbRef<'_> {
|
||||
type Error = Infallible;
|
||||
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Infallible> {
|
||||
self.0.basic(address)
|
||||
}
|
||||
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Infallible> {
|
||||
self.0.code_by_hash(code_hash)
|
||||
}
|
||||
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Infallible> {
|
||||
self.0.storage(address, index)
|
||||
}
|
||||
fn block_hash(&mut self, number: u64) -> Result<B256, Infallible> {
|
||||
self.0.block_hash(number)
|
||||
}
|
||||
}
|
||||
13
proving/igneum-prove/export/Cargo.toml
Normal file
13
proving/igneum-prove/export/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "igneum-prove-export"
|
||||
description = "Replays the execution layer's igneum_exportSegments JSON from genesis through igneum-prove-core, checks every state root against the node's, and writes one chain block as a proving fixture"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
igneum-prove-core.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
serde_json.workspace = true
|
||||
hex.workspace = true
|
||||
anyhow.workspace = true
|
||||
159
proving/igneum-prove/export/src/main.rs
Normal file
159
proving/igneum-prove/export/src/main.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
//! Usage: igneum-prove-export <seq.json> <block number> <out.json> [--source "text"]
|
||||
//!
|
||||
//! `seq.json` is the output of `igneum_exportSegments` (tools/evm-smoke writes it). The exporter rebuilds the
|
||||
//! genesis state (the DeveloperRegistry at its fixed address, nothing else), replays every segment through
|
||||
//! `igneum_prove_core::execute_block` and compares the state root after each with the node's `stateRoot`.
|
||||
//! Any mismatch is fatal: the port would not be the node's executor. At the requested block it snapshots the
|
||||
//! pre-state, runs the block, and writes the fixture with the expected roots.
|
||||
|
||||
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use igneum_prove_core::config::DEVELOPER_REGISTRY_ADDRESS;
|
||||
use igneum_prove_core::{execute_block, AccountFixture, BlockFixture, Expected, Fixture, FixtureEnv, IgneumDb, IncludingBlock};
|
||||
use serde_json::Value;
|
||||
|
||||
fn addr(v: &Value) -> Result<Address> {
|
||||
Ok(v.as_str().context("address")?.parse()?)
|
||||
}
|
||||
fn b256(v: &Value) -> Result<B256> {
|
||||
Ok(v.as_str().context("hash")?.parse()?)
|
||||
}
|
||||
fn u256(v: &Value) -> Result<U256> {
|
||||
Ok(v.as_str().context("u256")?.parse()?)
|
||||
}
|
||||
fn bytes(v: &Value) -> Result<Vec<u8>> {
|
||||
Ok(hex::decode(v.as_str().context("hex")?.trim_start_matches("0x"))?)
|
||||
}
|
||||
|
||||
/// Rebuilds the segment's including blocks from the export, which lists executed transactions in sequence
|
||||
/// order with their including miner and skipped copies after them. Consecutive transactions with the same
|
||||
/// including miner form one block; the blue flag is not in the export and is irrelevant to execution (rewards
|
||||
/// are carried separately), so every reconstructed block is marked blue.
|
||||
fn blocks_of(seg: &Value) -> Result<Vec<IncludingBlock>> {
|
||||
let mut items: Vec<(&Value, usize)> = seg["txs"].as_array().context("txs")?.iter().enumerate().map(|(i, t)| (t, i)).collect();
|
||||
items.sort_by_key(|(t, i)| (t["sequence"].as_u64().map(|s| s as usize).unwrap_or(usize::MAX), *i));
|
||||
let mut blocks: Vec<IncludingBlock> = Vec::new();
|
||||
for (t, _) in items {
|
||||
let raw = bytes(&t["raw"])?;
|
||||
if raw.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let miner = match t["miner"].as_str() {
|
||||
Some(m) => m.parse()?,
|
||||
None => blocks.last().map(|b| b.miner).unwrap_or(Address::ZERO),
|
||||
};
|
||||
match blocks.last_mut() {
|
||||
Some(b) if b.miner == miner => b.txs.push(Bytes::from(raw)),
|
||||
_ => blocks.push(IncludingBlock { miner, blue: true, txs: vec![Bytes::from(raw)] }),
|
||||
}
|
||||
}
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
fn fixture_of(export: &Value, segments: &[Value], n: usize, hashes: &[(u64, B256)], db: &IgneumDb) -> Result<BlockFixture> {
|
||||
let seg = &segments[n];
|
||||
let chain_id = export["chainId"].as_u64().context("chainId")?;
|
||||
let parent_hash = if n == 0 { B256::ZERO } else { b256(&segments[n - 1]["hash"])? };
|
||||
let env = FixtureEnv {
|
||||
number: seg["number"].as_u64().context("number")?,
|
||||
hash: b256(&seg["hash"])?,
|
||||
parent_hash,
|
||||
timestamp: seg["timestamp"].as_u64().context("timestamp")?,
|
||||
miner: addr(&seg["miner"])?,
|
||||
prevrandao: b256(&seg["prevrandao"])?,
|
||||
base_fee_exec: seg["baseFeeExec"].as_str().context("baseFeeExec")?.parse()?,
|
||||
base_fee_proving: seg["baseFeeProving"].as_str().context("baseFeeProving")?.parse()?,
|
||||
};
|
||||
let rewards = seg["rewards"].as_array().context("rewards")?.iter().map(|r| Ok((addr(&r["address"])?, u256(&r["wei"])?))).collect::<Result<Vec<_>>>()?;
|
||||
let proving_pool_credit: U256 = seg["provingPoolCredit"].as_str().context("provingPoolCredit")?.parse()?;
|
||||
let pre_state = db
|
||||
.addresses()
|
||||
.into_iter()
|
||||
.map(|a| AccountFixture { address: a, nonce: db.nonce(a), balance: db.balance(a), code: Bytes::from(db.code(a)), storage: db.storage_of(a) })
|
||||
.filter(|a| !(a.balance.is_zero() && a.nonce == 0 && a.code.is_empty() && a.storage.is_empty()))
|
||||
.collect();
|
||||
Ok(BlockFixture {
|
||||
chain_id,
|
||||
env,
|
||||
block_hashes: hashes.iter().rev().take(256).rev().copied().collect(),
|
||||
rewards,
|
||||
proving_pool_credit,
|
||||
blocks: blocks_of(seg)?,
|
||||
pre_state,
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 4 {
|
||||
bail!("usage: igneum-prove-export <seq.json> <block number> <out.json> [--source text]");
|
||||
}
|
||||
let export: Value = serde_json::from_str(&std::fs::read_to_string(&args[1])?)?;
|
||||
let want: u64 = args[2].parse()?;
|
||||
let out_path = &args[3];
|
||||
let source = args.iter().position(|a| a == "--source").and_then(|i| args.get(i + 1)).cloned().unwrap_or_else(|| format!("{} (igneum_exportSegments), block {}", args[1], want));
|
||||
|
||||
let segments = export["segments"].as_array().context("segments")?.clone();
|
||||
let registry_code = bytes(&export["registryCode"])?;
|
||||
let mut db = IgneumDb::new();
|
||||
// The node's genesis inserts the registry through revm's `AccountInfo::from_bytecode`, which gives a contract
|
||||
// account nonce 1 (EIP-161), so the registry starts at nonce 1.
|
||||
db.insert_account(DEVELOPER_REGISTRY_ADDRESS, 1, U256::ZERO, ®istry_code, &[]);
|
||||
|
||||
let mut hashes: Vec<(u64, B256)> = Vec::new();
|
||||
let mut fixture: Option<Fixture> = None;
|
||||
let mut checked = 0usize;
|
||||
for (n, seg) in segments.iter().enumerate() {
|
||||
let number = seg["number"].as_u64().context("number")?;
|
||||
let f = fixture_of(&export, &segments, n, &hashes, &db)?;
|
||||
let pre_root = db.state_root();
|
||||
let node_root = b256(&seg["stateRoot"])?;
|
||||
let out = execute_block(&mut db, &f);
|
||||
db.push_block_hash(number, f.env.hash);
|
||||
hashes.push((number, f.env.hash));
|
||||
if out.state_root != node_root {
|
||||
bail!("segment {number}: port state root {} differs from the node's {node_root}; the port is not the node's executor, stop", out.state_root);
|
||||
}
|
||||
checked += 1;
|
||||
if number == want {
|
||||
let expected = Expected {
|
||||
pre_state_root: pre_root,
|
||||
post_state_root: out.state_root,
|
||||
receipts_root: out.receipts_root,
|
||||
gas_used: out.gas_used,
|
||||
pgas_used: out.pgas_used,
|
||||
executed: out.executed.len() as u32,
|
||||
skipped: out.skipped.len() as u32,
|
||||
node_state_root: node_root,
|
||||
};
|
||||
let txs: usize = f.blocks.iter().map(|b| b.txs.len()).sum();
|
||||
println!(
|
||||
"block {number}: {txs} transactions in {} including blocks, {} executed, {} skipped, gas {}, pgas {}, {} accounts in the pre-state",
|
||||
f.blocks.len(),
|
||||
expected.executed,
|
||||
expected.skipped,
|
||||
expected.gas_used,
|
||||
expected.pgas_used,
|
||||
f.pre_state.len()
|
||||
);
|
||||
for (h, why) in &out.skipped {
|
||||
println!(" skipped {h}: {why:?}");
|
||||
}
|
||||
for r in &out.executed {
|
||||
println!(" executed {}: status {} gas {} pgas {} logs {}", r.tx_hash, r.status, r.gas_used, r.pgas_used, r.logs.len());
|
||||
}
|
||||
println!(" pre-state root {pre_root}\n post-state root {} (node: {node_root})\n receipts root {}", out.state_root, out.receipts_root);
|
||||
fixture = Some(Fixture { format: igneum_prove_core::fixture::FORMAT.into(), source: source.clone(), block: f, expected });
|
||||
}
|
||||
}
|
||||
let final_root = b256(&export["stateRoot"])?;
|
||||
if db.state_root() != final_root {
|
||||
bail!("final state root {} differs from the export's {final_root}", db.state_root());
|
||||
}
|
||||
println!("replayed {checked} segments from genesis; every state root equals the node's; final root {final_root}");
|
||||
let fixture = fixture.with_context(|| format!("block {want} is not in the export (0 to {})", segments.len() - 1))?;
|
||||
let json = serde_json::to_string_pretty(&fixture)?;
|
||||
std::fs::write(out_path, &json)?;
|
||||
println!("wrote {out_path} ({} bytes, input commitment {})", json.len(), keccak256(json.as_bytes()));
|
||||
Ok(())
|
||||
}
|
||||
23
proving/igneum-prove/host/Cargo.toml
Normal file
23
proving/igneum-prove/host/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "igneum-prove-host"
|
||||
description = "SP1 host: loads a block fixture, runs the guest in execute, core and compressed modes, prints cycles, times and sizes, verifies the proofs; holds the versioned ProofSystem trait"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
igneum-prove-core.workspace = true
|
||||
sp1-sdk = { workspace = true, features = ["blocking"] }
|
||||
alloy-primitives.workspace = true
|
||||
bincode.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
sp1-build.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# The CUDA prover (SP1_PROVER=cuda): Linux x86_64 only, the SDK downloads sp1-gpu-server into ~/.sp1/bin.
|
||||
cuda = ["sp1-sdk/cuda"]
|
||||
3
proving/igneum-prove/host/build.rs
Normal file
3
proving/igneum-prove/host/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
sp1_build::build_program("../program");
|
||||
}
|
||||
145
proving/igneum-prove/host/src/main.rs
Normal file
145
proving/igneum-prove/host/src/main.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
//! igneum-prove-host: proves one Igneum chain block fixture with SP1.
|
||||
//!
|
||||
//! Usage: igneum-prove-host <fixture.json> [--mode native|execute|core|compressed|all] [--out <results.json>]
|
||||
//!
|
||||
//! Modes build on each other: `native` runs the statement on the host and checks the fixture's expected roots;
|
||||
//! `execute` runs the guest in SP1's executor for the cycle count (no proof); `core` makes and verifies a core
|
||||
//! (STARK, many shards) proof; `compressed` makes and verifies the recursion-compressed proof; `all` runs them
|
||||
//! all. The prover comes from `SP1_PROVER` (cpu, the default; cuda, Linux x86_64 with the `cuda` feature; mock).
|
||||
//! Every run prints one `RESULT` line per mode (cycles, seconds, bytes) that PROVE-BLOCK and the bench log read.
|
||||
|
||||
mod proof_system;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use igneum_prove_core::executor::prove_statement;
|
||||
use igneum_prove_core::{Fixture, ProveOutput};
|
||||
use proof_system::{ProofSystem, SegmentClaim, ShardWitness, Sp1ProofSystem, StubProofSystem};
|
||||
use sp1_sdk::{include_elf, Elf};
|
||||
use std::time::Instant;
|
||||
|
||||
const ELF: Elf = include_elf!("igneum-prove-program");
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let path = args.get(1).filter(|a| !a.starts_with("--")).context("usage: igneum-prove-host <fixture.json> [--mode native|execute|core|compressed|all] [--out results.json]")?;
|
||||
let mode = args.iter().position(|a| a == "--mode").and_then(|i| args.get(i + 1)).map(String::as_str).unwrap_or("all");
|
||||
let out_path = args.iter().position(|a| a == "--out").and_then(|i| args.get(i + 1)).cloned();
|
||||
let fixture: Fixture = serde_json::from_str(&std::fs::read_to_string(path).with_context(|| format!("read {path}"))?)?;
|
||||
if fixture.format != igneum_prove_core::fixture::FORMAT {
|
||||
bail!("fixture format {} is not {}", fixture.format, igneum_prove_core::fixture::FORMAT);
|
||||
}
|
||||
let block = &fixture.block;
|
||||
let txs: usize = block.blocks.iter().map(|b| b.txs.len()).sum();
|
||||
let prover = std::env::var("SP1_PROVER").unwrap_or_else(|_| "cpu".into());
|
||||
println!("fixture {path}: chain {} block {} ({}), {txs} transactions in {} including blocks, {} accounts in the pre-state; SP1_PROVER={prover}", block.chain_id, block.env.number, block.env.hash, block.blocks.len(), block.pre_state.len());
|
||||
let mut results = serde_json::Map::new();
|
||||
results.insert("fixture".into(), path.clone().into());
|
||||
results.insert("block".into(), block.env.number.into());
|
||||
results.insert("prover".into(), prover.clone().into());
|
||||
results.insert("sp1_crate_version".into(), "6.8.1".into());
|
||||
results.insert("sp1_circuit_version".into(), sp1_sdk::SP1_CIRCUIT_VERSION.into());
|
||||
|
||||
// 1. Native: the statement on the host, against the fixture's expected values.
|
||||
let t = Instant::now();
|
||||
let native = prove_statement(block);
|
||||
let native_s = t.elapsed().as_secs_f64();
|
||||
let e = &fixture.expected;
|
||||
let same = native.pre_state_root == e.pre_state_root && native.post_state_root == e.post_state_root && native.receipts_root == e.receipts_root && native.gas_used == e.gas_used && native.pgas_used == e.pgas_used;
|
||||
println!("RESULT native: {:.4} s, pre {} post {} receipts {} gas {} pgas {} executed {} skipped {}: {}", native_s, native.pre_state_root, native.post_state_root, native.receipts_root, native.gas_used, native.pgas_used, native.executed, native.skipped, if same { "MATCHES the fixture's expected values" } else { "DIFFERS from the fixture's expected values" });
|
||||
if !same {
|
||||
bail!("native execution differs from the fixture; regenerate the fixture with igneum-prove-export");
|
||||
}
|
||||
if e.node_state_root != e.post_state_root {
|
||||
bail!("the fixture's node state root differs from its expected post-state root; the exporter must not have produced this file");
|
||||
}
|
||||
results.insert("native_seconds".into(), native_s.into());
|
||||
let claim = SegmentClaim::from_output(&native);
|
||||
|
||||
// 2. The devnet stub behind the same trait (design 5.7), so the pipeline shape is exercised too.
|
||||
let stub = StubProofSystem::new([7u8; 32]);
|
||||
let witness = ShardWitness { block: block.clone(), shard_index: 0, prover: alloy_primitives::B256::ZERO };
|
||||
let stub_proof = stub.prove_shard(&witness)?;
|
||||
let stub_seg = stub.aggregate(None, &[stub_proof])?;
|
||||
println!("RESULT stub: ProofSystem v{} program {} verify_segment {}", StubProofSystem::VERSION, stub.program_id(), stub.verify_segment(&stub_seg, &claim));
|
||||
if mode == "native" {
|
||||
return finish(results, out_path);
|
||||
}
|
||||
|
||||
// 3. SP1, version 1 behind the trait.
|
||||
let t = Instant::now();
|
||||
let sp1 = Sp1ProofSystem::from_env(ELF)?;
|
||||
println!("RESULT setup: {:.2} s, ProofSystem v{} program id (vk hash) {}", t.elapsed().as_secs_f64(), Sp1ProofSystem::VERSION, sp1.program_id());
|
||||
results.insert("setup_seconds".into(), t.elapsed().as_secs_f64().into());
|
||||
results.insert("program_id".into(), sp1.program_id().to_string().into());
|
||||
|
||||
if matches!(mode, "execute" | "all" | "core" | "compressed") {
|
||||
let (out, report, dt) = sp1.execute(block)?;
|
||||
check_output(&out, &native)?;
|
||||
let cycles = report.total_instruction_count();
|
||||
let gas = report.gas().unwrap_or(0);
|
||||
println!("RESULT execute: {} cycles, prover gas {}, {:.2} s, {:.0} cycles per EVM gas, syscalls {}", cycles, gas, dt.as_secs_f64(), cycles as f64 / native.gas_used.max(1) as f64, report.syscall_counts.values().sum::<u64>());
|
||||
results.insert("cycles".into(), cycles.into());
|
||||
results.insert("prover_gas".into(), gas.into());
|
||||
results.insert("execute_seconds".into(), dt.as_secs_f64().into());
|
||||
}
|
||||
if mode == "execute" {
|
||||
return finish(results, out_path);
|
||||
}
|
||||
|
||||
if matches!(mode, "core" | "all") {
|
||||
let shard = sp1.prove_shard(&witness)?;
|
||||
let core_proof = shard.proof.clone().expect("prove_shard returns a core proof");
|
||||
let (prove_s, verify_s, bytes) = report_proof(&sp1, "core", &core_proof, |p| sp1.verify_shard(p, &claim), &shard)?;
|
||||
results.insert("core_prove_seconds".into(), prove_s.into());
|
||||
results.insert("core_verify_seconds".into(), verify_s.into());
|
||||
results.insert("core_proof_bytes".into(), bytes.into());
|
||||
if let Some(dir) = out_path.as_deref().and_then(|p| std::path::Path::new(p).parent()) {
|
||||
let _ = core_proof.save(dir.join(format!("block-{}-core.bin", block.env.number)));
|
||||
}
|
||||
}
|
||||
if matches!(mode, "compressed" | "all") {
|
||||
// v0: the segment proof is the compressed proof of the single shard; the witness is re-proven in
|
||||
// compressed mode (SP1 compresses by recursion over the core shards it generates itself).
|
||||
let shard = proof_system::Sp1ShardProof { proof: None, witness_input: igneum_prove_core::ProveInput { block: block.clone() } };
|
||||
let seg = sp1.aggregate(None, &[shard])?;
|
||||
let (prove_s, verify_s, bytes) = report_proof(&sp1, "compressed", &seg.proof, |_| sp1.verify_segment(&seg, &claim), &seg)?;
|
||||
results.insert("compressed_prove_seconds".into(), prove_s.into());
|
||||
results.insert("compressed_verify_seconds".into(), verify_s.into());
|
||||
results.insert("compressed_proof_bytes".into(), bytes.into());
|
||||
if let Some(dir) = out_path.as_deref().and_then(|p| std::path::Path::new(p).parent()) {
|
||||
let _ = seg.proof.save(dir.join(format!("block-{}-compressed.bin", block.env.number)));
|
||||
}
|
||||
}
|
||||
finish(results, out_path)
|
||||
}
|
||||
|
||||
fn check_output(out: &ProveOutput, native: &ProveOutput) -> Result<()> {
|
||||
if out != native {
|
||||
bail!("the guest's public values differ from the native run:\n guest {out:?}\n native {native:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prints one RESULT line for a proof: prove time (from the system's timing log), proof size (bincode, as SP1
|
||||
/// saves it) and verify time. Returns (prove seconds, verify seconds, bytes).
|
||||
fn report_proof<T>(sp1: &Sp1ProofSystem, what: &str, proof: &sp1_sdk::SP1ProofWithPublicValues, verify: impl Fn(&T) -> bool, p: &T) -> Result<(f64, f64, usize)> {
|
||||
let prove_s = sp1.timings.lock().unwrap().iter().rev().find(|(k, _)| k == what).map(|(_, d)| d.as_secs_f64()).ok_or_else(|| anyhow!("no timing for {what}"))?;
|
||||
let bytes = bincode::serialize(proof)?.len();
|
||||
let t = Instant::now();
|
||||
let ok = verify(p);
|
||||
let verify_s = t.elapsed().as_secs_f64();
|
||||
let out = ProveOutput::from_bytes(proof.public_values.as_slice()).ok_or_else(|| anyhow!("public values of the {what} proof have the wrong length"))?;
|
||||
println!("RESULT {what}: prove {:.1} s, proof {} bytes, verify {:.3} s, {}; post-state root {} receipts root {}", prove_s, bytes, verify_s, if ok { "VERIFIED" } else { "VERIFY FAILED" }, out.post_state_root, out.receipts_root);
|
||||
if !ok {
|
||||
bail!("{what} proof did not verify");
|
||||
}
|
||||
Ok((prove_s, verify_s, bytes))
|
||||
}
|
||||
|
||||
fn finish(results: serde_json::Map<String, serde_json::Value>, out_path: Option<String>) -> Result<()> {
|
||||
if let Some(p) = out_path {
|
||||
std::fs::write(&p, serde_json::to_string_pretty(&serde_json::Value::Object(results))?)?;
|
||||
println!("results written to {p}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
253
proving/igneum-prove/host/src/proof_system.rs
Normal file
253
proving/igneum-prove/host/src/proof_system.rs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
//! The versioned prover trait of `docs/design/execution-layer.md` section 5.6, with the two implementations the
|
||||
//! design names: the devnet stub (5.7, a signed claim) and version 1, SP1 (Hypercube class, hash-based).
|
||||
//!
|
||||
//! v0 scope. A shard is one whole chain block (the shard planner of 5.1 is not here yet); `aggregate` over one
|
||||
//! shard is SP1's compress stage on that shard's witness (recursion over several shard proofs is the next step);
|
||||
//! `wrap` (Groth16 or Plonk over bn254 for the bridge verifier and light clients, ledger P3) is not run in v0
|
||||
//! and returns an error. Ledger P12 (the prover's payout key inside the shard statement) is also not in v0:
|
||||
//! `ShardWitness.prover` is carried but not committed by the guest yet.
|
||||
|
||||
#![allow(dead_code)] // wrap, verify_wrapped, pgas_table, shard_index and prover are the trait surface of design 5.6, unused in v0
|
||||
|
||||
use alloy_primitives::{keccak256, B256};
|
||||
use anyhow::{anyhow, Result};
|
||||
use igneum_prove_core::{BlockFixture, ProveInput, ProveOutput};
|
||||
use sp1_sdk::blocking::{Prover, ProveRequest, ProverClient, SP1Stdin};
|
||||
use sp1_sdk::{Elf, HashableKey, ProvingKey, SP1ProofWithPublicValues, SP1VerifyingKey};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// The prototype pgas table identity (design 4.2, the table itself lives in `igneum_prove_core::pgas`).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PgasTable {
|
||||
pub name: &'static str,
|
||||
pub version: u16,
|
||||
}
|
||||
|
||||
/// What a shard proof is about: the segment's block environment and ordered transactions plus the pre-state
|
||||
/// the shard touches (v0: the whole in-memory state). `prover` is the payout key of design 5.4 (not yet in the
|
||||
/// statement, ledger P12).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ShardWitness {
|
||||
pub block: BlockFixture,
|
||||
pub shard_index: u32,
|
||||
pub prover: B256,
|
||||
}
|
||||
|
||||
/// The claim a proof record makes (design 5.4): the segment, its pre- and post-roots and receipts root.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SegmentClaim {
|
||||
pub segment: B256,
|
||||
pub pre_root: B256,
|
||||
pub post_root: B256,
|
||||
pub receipts: B256,
|
||||
}
|
||||
|
||||
impl SegmentClaim {
|
||||
pub fn from_output(o: &ProveOutput) -> Self {
|
||||
Self { segment: o.block_hash, pre_root: o.pre_state_root, post_root: o.post_state_root, receipts: o.receipts_root }
|
||||
}
|
||||
pub fn digest(&self) -> B256 {
|
||||
let mut buf = Vec::with_capacity(128);
|
||||
buf.extend_from_slice(self.segment.as_slice());
|
||||
buf.extend_from_slice(self.pre_root.as_slice());
|
||||
buf.extend_from_slice(self.post_root.as_slice());
|
||||
buf.extend_from_slice(self.receipts.as_slice());
|
||||
keccak256(buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProofSystem: Send + Sync {
|
||||
const VERSION: u16;
|
||||
type ShardProof;
|
||||
type SegmentProof;
|
||||
type WrappedProof;
|
||||
/// Hash of the executor guest (SP1: the verifying key hash; stub: a fixed tag).
|
||||
fn program_id(&self) -> B256;
|
||||
fn prove_shard(&self, w: &ShardWitness) -> Result<Self::ShardProof>;
|
||||
fn aggregate(&self, prev: Option<&Self::SegmentProof>, shards: &[Self::ShardProof]) -> Result<Self::SegmentProof>;
|
||||
fn wrap(&self, p: &Self::SegmentProof) -> Result<Self::WrappedProof>;
|
||||
fn verify_segment(&self, p: &Self::SegmentProof, claim: &SegmentClaim) -> bool;
|
||||
fn verify_wrapped(&self, p: &Self::WrappedProof, claim: &SegmentClaim) -> bool;
|
||||
fn pgas_table(&self) -> &PgasTable;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Version 0: the devnet stub of design 5.7. `prove_shard` executes natively and signs the claim with a devnet
|
||||
// key (keccak MAC); `verify_segment` checks the MAC. Never a mainnet version.
|
||||
|
||||
pub struct StubProofSystem {
|
||||
pub key: [u8; 32],
|
||||
table: PgasTable,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StubProof {
|
||||
pub claim: SegmentClaim,
|
||||
pub mac: B256,
|
||||
}
|
||||
|
||||
impl StubProofSystem {
|
||||
pub fn new(key: [u8; 32]) -> Self {
|
||||
Self { key, table: PgasTable { name: "prototype-fb33069", version: 0 } }
|
||||
}
|
||||
fn mac(&self, claim: &SegmentClaim) -> B256 {
|
||||
let mut buf = Vec::with_capacity(64);
|
||||
buf.extend_from_slice(&self.key);
|
||||
buf.extend_from_slice(claim.digest().as_slice());
|
||||
keccak256(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProofSystem for StubProofSystem {
|
||||
const VERSION: u16 = 0;
|
||||
type ShardProof = StubProof;
|
||||
type SegmentProof = StubProof;
|
||||
type WrappedProof = StubProof;
|
||||
|
||||
fn program_id(&self) -> B256 {
|
||||
keccak256(b"igneum-proof-system-stub-v0")
|
||||
}
|
||||
fn prove_shard(&self, w: &ShardWitness) -> Result<StubProof> {
|
||||
let out = igneum_prove_core::executor::prove_statement(&w.block);
|
||||
let claim = SegmentClaim::from_output(&out);
|
||||
Ok(StubProof { mac: self.mac(&claim), claim })
|
||||
}
|
||||
fn aggregate(&self, _prev: Option<&StubProof>, shards: &[StubProof]) -> Result<StubProof> {
|
||||
shards.first().cloned().ok_or_else(|| anyhow!("no shards"))
|
||||
}
|
||||
fn wrap(&self, p: &StubProof) -> Result<StubProof> {
|
||||
Ok(p.clone())
|
||||
}
|
||||
fn verify_segment(&self, p: &StubProof, claim: &SegmentClaim) -> bool {
|
||||
&p.claim == claim && p.mac == self.mac(claim)
|
||||
}
|
||||
fn verify_wrapped(&self, p: &StubProof, claim: &SegmentClaim) -> bool {
|
||||
self.verify_segment(p, claim)
|
||||
}
|
||||
fn pgas_table(&self) -> &PgasTable {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// Version 1: SP1. Core proof per shard, compressed proof as the segment proof (v0: one shard, so the segment
|
||||
// proof is the compressed proof of that shard's witness), wrap deferred.
|
||||
|
||||
pub struct Sp1ProofSystem {
|
||||
client: sp1_sdk::blocking::EnvProver,
|
||||
pk: sp1_sdk::blocking::EnvProvingKey,
|
||||
vk: SP1VerifyingKey,
|
||||
table: PgasTable,
|
||||
pub timings: std::sync::Mutex<Vec<(String, Duration)>>,
|
||||
}
|
||||
|
||||
pub struct Sp1ShardProof {
|
||||
/// The core proof; `None` for a witness-only shard handed to `aggregate` when the core stage is skipped (v0).
|
||||
pub proof: Option<SP1ProofWithPublicValues>,
|
||||
pub witness_input: ProveInput,
|
||||
}
|
||||
|
||||
pub struct Sp1SegmentProof {
|
||||
pub proof: SP1ProofWithPublicValues,
|
||||
}
|
||||
|
||||
pub struct Sp1WrappedProof(pub SP1ProofWithPublicValues);
|
||||
|
||||
impl Sp1ProofSystem {
|
||||
/// Builds the prover from the environment (`SP1_PROVER` = cpu, cuda or mock) and runs the key setup.
|
||||
pub fn from_env(elf: Elf) -> Result<Self> {
|
||||
let client = ProverClient::from_env();
|
||||
let t = Instant::now();
|
||||
let pk = client.setup(elf)?;
|
||||
let vk = pk.verifying_key().clone();
|
||||
let setup = t.elapsed();
|
||||
Ok(Self { client, pk, vk, table: PgasTable { name: "prototype-fb33069", version: 1 }, timings: std::sync::Mutex::new(vec![("setup".into(), setup)]) })
|
||||
}
|
||||
|
||||
pub fn vk(&self) -> &SP1VerifyingKey {
|
||||
&self.vk
|
||||
}
|
||||
|
||||
pub fn stdin_for(block: &BlockFixture) -> Result<SP1Stdin> {
|
||||
let mut stdin = SP1Stdin::new();
|
||||
stdin.write_vec(bincode::serialize(&ProveInput { block: block.clone() })?);
|
||||
Ok(stdin)
|
||||
}
|
||||
|
||||
pub fn execute(&self, block: &BlockFixture) -> Result<(ProveOutput, sp1_sdk::ExecutionReport, Duration)> {
|
||||
let stdin = Self::stdin_for(block)?;
|
||||
let t = Instant::now();
|
||||
let (pv, report) = self.client.execute(self.pk.elf().clone(), stdin).calculate_gas(true).run().map_err(|e| anyhow!("{e}"))?;
|
||||
let dt = t.elapsed();
|
||||
let out = ProveOutput::from_bytes(pv.as_slice()).ok_or_else(|| anyhow!("public values are {} bytes, expected {}", pv.as_slice().len(), ProveOutput::LEN))?;
|
||||
Ok((out, report, dt))
|
||||
}
|
||||
|
||||
fn record(&self, what: &str, dt: Duration) {
|
||||
self.timings.lock().unwrap().push((what.to_string(), dt));
|
||||
}
|
||||
|
||||
fn output_of(proof: &SP1ProofWithPublicValues) -> Option<ProveOutput> {
|
||||
ProveOutput::from_bytes(proof.public_values.as_slice())
|
||||
}
|
||||
|
||||
fn check(&self, proof: &SP1ProofWithPublicValues, claim: &SegmentClaim) -> bool {
|
||||
let t = Instant::now();
|
||||
let ok = self.client.verify(proof, &self.vk, None).is_ok();
|
||||
self.record("verify", t.elapsed());
|
||||
ok && Self::output_of(proof).map(|o| &SegmentClaim::from_output(&o) == claim).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProofSystem for Sp1ProofSystem {
|
||||
const VERSION: u16 = 1;
|
||||
type ShardProof = Sp1ShardProof;
|
||||
type SegmentProof = Sp1SegmentProof;
|
||||
type WrappedProof = Sp1WrappedProof;
|
||||
|
||||
fn program_id(&self) -> B256 {
|
||||
B256::from_slice(&self.vk.bytes32_raw())
|
||||
}
|
||||
|
||||
fn prove_shard(&self, w: &ShardWitness) -> Result<Sp1ShardProof> {
|
||||
let stdin = Self::stdin_for(&w.block)?;
|
||||
let t = Instant::now();
|
||||
let proof = self.client.prove(&self.pk, stdin).core().run()?;
|
||||
self.record("core", t.elapsed());
|
||||
Ok(Sp1ShardProof { proof: Some(proof), witness_input: ProveInput { block: w.block.clone() } })
|
||||
}
|
||||
|
||||
fn aggregate(&self, prev: Option<&Sp1SegmentProof>, shards: &[Sp1ShardProof]) -> Result<Sp1SegmentProof> {
|
||||
if prev.is_some() {
|
||||
return Err(anyhow!("v0 has no chain recursion (segment N-1 inside segment N); next step"));
|
||||
}
|
||||
let [shard] = shards else { return Err(anyhow!("v0 aggregates exactly one shard (the whole block); got {}", shards.len())) };
|
||||
let stdin = Self::stdin_for(&shard.witness_input.block)?;
|
||||
let t = Instant::now();
|
||||
let proof = self.client.prove(&self.pk, stdin).compressed().run()?;
|
||||
self.record("compressed", t.elapsed());
|
||||
Ok(Sp1SegmentProof { proof })
|
||||
}
|
||||
|
||||
fn wrap(&self, _p: &Sp1SegmentProof) -> Result<Sp1WrappedProof> {
|
||||
Err(anyhow!("wrap (Groth16 or Plonk over bn254) is not run in v0: it needs SP1's circuit artifacts and is the ledger P3 measurement"))
|
||||
}
|
||||
|
||||
fn verify_segment(&self, p: &Sp1SegmentProof, claim: &SegmentClaim) -> bool {
|
||||
self.check(&p.proof, claim)
|
||||
}
|
||||
|
||||
fn verify_wrapped(&self, p: &Sp1WrappedProof, claim: &SegmentClaim) -> bool {
|
||||
self.check(&p.0, claim)
|
||||
}
|
||||
|
||||
fn pgas_table(&self) -> &PgasTable {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Sp1ProofSystem {
|
||||
pub fn verify_shard(&self, p: &Sp1ShardProof, claim: &SegmentClaim) -> bool {
|
||||
p.proof.as_ref().map(|pr| self.check(pr, claim)).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
11
proving/igneum-prove/program/Cargo.toml
Normal file
11
proving/igneum-prove/program/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "igneum-prove-program"
|
||||
description = "SP1 guest: re-executes one Igneum chain block and commits the post-state root and the receipts root"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sp1-zkvm.workspace = true
|
||||
igneum-prove-core.workspace = true
|
||||
bincode.workspace = true
|
||||
16
proving/igneum-prove/program/src/main.rs
Normal file
16
proving/igneum-prove/program/src/main.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//! The guest. Input: bincode of `ProveInput` (the block fixture). Output (public values): `ProveOutput` in its
|
||||
//! fixed byte layout. Everything between is `igneum_prove_core::executor::prove_statement`, the same code the
|
||||
//! host runs natively first.
|
||||
|
||||
#![no_main]
|
||||
sp1_zkvm::entrypoint!(main);
|
||||
|
||||
use igneum_prove_core::executor::prove_statement;
|
||||
use igneum_prove_core::ProveInput;
|
||||
|
||||
pub fn main() {
|
||||
let input = sp1_zkvm::io::read_vec();
|
||||
let input: ProveInput = bincode::deserialize(&input).expect("ProveInput decodes");
|
||||
let out = prove_statement(&input.block);
|
||||
sp1_zkvm::io::commit_slice(&out.to_bytes());
|
||||
}
|
||||
27
proving/windows-wsl2/PROVE-BLOCK.bat
Normal file
27
proving/windows-wsl2/PROVE-BLOCK.bat
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
@echo off
|
||||
rem Igneum proving v0: proves the fixture block (block-78-increment by default) inside WSL2 on the GPU
|
||||
rem (SP1_PROVER=cuda) and then on the CPU, prints the numbers and uploads the log to the Igneum log intake.
|
||||
rem PAUSE MINING FIRST (Ctrl+C in the START-MINING window): the prover needs the whole GPU and most of the RAM.
|
||||
rem Usage: PROVE-BLOCK.bat [fixture name, default block-78-increment] [cpu modes, default core]
|
||||
cd /d "%~dp0"
|
||||
set "FIXTURE=%~1"
|
||||
if "%FIXTURE%"=="" set "FIXTURE=block-78-increment"
|
||||
set "CPU_MODES=%~2"
|
||||
if "%CPU_MODES%"=="" set "CPU_MODES=core"
|
||||
set "DRIVE=%~d0"
|
||||
set "DRIVE=%DRIVE:~0,1%"
|
||||
set "REST=%~p0"
|
||||
set "REST=%REST:\=/%"
|
||||
call :lower DRIVE
|
||||
set "LINUXDIR=/mnt/%DRIVE%%REST%"
|
||||
echo Running prove-block.sh in Ubuntu-24.04 at %LINUXDIR% (fixture %FIXTURE%, cpu modes %CPU_MODES%)
|
||||
wsl.exe -d Ubuntu-24.04 -- bash "%LINUXDIR%prove-block.sh" "%FIXTURE%" "%CPU_MODES%"
|
||||
echo.
|
||||
echo Finished. The log is next to this file (prove-*.log) and was uploaded to the Mac side.
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
:lower
|
||||
for %%a in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do call set "%~1=%%%~1:%%a=%%a%%"
|
||||
for %%a in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do call set "%~1=%%%~1:%%a=%%a%%"
|
||||
exit /b
|
||||
10
proving/windows-wsl2/README.txt
Normal file
10
proving/windows-wsl2/README.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Igneum proving v0 on a Windows 11 PC with an NVIDIA card (RTX 5090), through WSL2 Ubuntu 24.04.
|
||||
1. Double-click SETUP-PROVER.bat. First run: it enables WSL2 and installs Ubuntu 24.04 (about 400 MB), then asks you to REBOOT.
|
||||
2. After the reboot Ubuntu opens once and asks for a username and password (any, e.g. igneum). Close it. Double-click SETUP-PROVER.bat again:
|
||||
it installs the CUDA toolkit for WSL (about 3 GB), Rust (about 500 MB), sp1up and the SP1 toolchain (about 300 MB download, 1 GB on disk), then pre-builds the prover.
|
||||
Downloads in total: about 4 to 5 GB (approximate). Time: 15 to 40 minutes depending on the connection and the CPU (approximate). The NVIDIA driver is NOT installed inside Ubuntu: Windows provides it. Docker is not needed (SP1 v6 runs a native sp1-gpu-server, which the SDK downloads on first use, about 134 MB).
|
||||
3. PAUSE MINING (Ctrl+C in the START-MINING window). The prover needs the whole GPU (24 GB of VRAM or more) and most of the RAM.
|
||||
4. Double-click PROVE-BLOCK.bat. It proves the fixture block on the GPU (execute, core proof, compressed proof, each verified), then a core proof on the CPU for comparison, prints the RESULT lines and uploads the log to the Igneum log intake (run_id prove-<PC name>-<stamp>).
|
||||
First run: 10 to 30 minutes of compiling before the first proof (approximate). The proof itself: seconds to a few minutes on the GPU, minutes to tens of minutes on the CPU (approximate, that is what this run measures).
|
||||
5. Resume mining afterwards. Results also land next to this file in results/*.json. Each line marked RESULT in the log is a number for docs/bench-log.md.
|
||||
Everything version-specific (CUDA 12.8 package name, SP1 v6.8.1, the server asset name) was read from the SP1 and NVIDIA documentation on 3 October 2026 and is approximate from that date on.
|
||||
9
proving/windows-wsl2/SETUP-PROVER.bat
Normal file
9
proving/windows-wsl2/SETUP-PROVER.bat
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
@echo off
|
||||
rem Igneum proving v0, Windows 11 setup. Double-click this file (it asks for administrator rights).
|
||||
rem Run 1: enables WSL2 and installs Ubuntu 24.04, then asks you to reboot.
|
||||
rem Run 2 (after the reboot): creates the Ubuntu user if needed, then runs setup-wsl.sh inside Ubuntu
|
||||
rem (build tools, CUDA toolkit for WSL, Rust, sp1up and the SP1 toolchain). Then PROVE-BLOCK.bat.
|
||||
cd /d "%~dp0"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-prover.ps1"
|
||||
echo.
|
||||
pause
|
||||
26
proving/windows-wsl2/make-package.sh
Executable file
26
proving/windows-wsl2/make-package.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Builds igneum-prove-wsl2.zip (double-click setup and proof run for a Windows 11 PC) on the Mac.
|
||||
# Packs this directory plus the proving sources and the execution layer's evm-types crate under package/, so the
|
||||
# path dependency resolves on the PC without the repository. Usage: windows-wsl2/make-package.sh [out zip, default ~/Desktop/igneum-prove-wsl2.zip]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||
OUT="${1:-$HOME/Desktop/igneum-prove-wsl2.zip}"
|
||||
STAGE="$(mktemp -d)/igneum-prove-wsl2"
|
||||
PKG="$STAGE/package"
|
||||
mkdir -p "$PKG/proving" "$PKG/vendor/igneum-node-exec/igneum"
|
||||
cp "$HERE/SETUP-PROVER.bat" "$HERE/setup-prover.ps1" "$HERE/setup-wsl.sh" "$HERE/prove-block.sh" "$HERE/PROVE-BLOCK.bat" "$HERE/README.txt" "$STAGE/"
|
||||
rsync -a --exclude target --exclude Cargo.lock "$ROOT/proving/igneum-prove" "$PKG/proving/"
|
||||
cp "$ROOT/proving/igneum-prove/Cargo.lock" "$PKG/proving/igneum-prove/" 2>/dev/null || true
|
||||
rsync -a "$ROOT/proving/fixtures" "$PKG/proving/"
|
||||
rsync -a --exclude target "$ROOT/vendor/igneum-node-exec/igneum/evm-types" "$PKG/vendor/igneum-node-exec/igneum/"
|
||||
# evm-types inherits thiserror from the node's workspace; pin it inline (the node's Cargo.toml line 346: 2.0.18) so the crate builds alone.
|
||||
perl -pi -e 's/^thiserror\.workspace = true/thiserror = { version = "2.0.18", default-features = false }/' "$PKG/vendor/igneum-node-exec/igneum/evm-types/Cargo.toml"
|
||||
# The exporter's input, so the fixture can be regenerated on the PC too.
|
||||
mkdir -p "$PKG/tools/evm-smoke" && cp "$ROOT/tools/evm-smoke/seq.json" "$PKG/tools/evm-smoke/" 2>/dev/null || true
|
||||
for f in "$STAGE/SETUP-PROVER.bat" "$STAGE/PROVE-BLOCK.bat" "$STAGE/README.txt" "$STAGE/setup-prover.ps1"; do perl -pi -e 's/\r?\n/\r\n/' "$f"; done
|
||||
for f in "$STAGE/setup-wsl.sh" "$STAGE/prove-block.sh"; do perl -pi -e 's/\r\n/\n/' "$f"; chmod +x "$f"; done
|
||||
rm -f "$OUT"
|
||||
(cd "$(dirname "$STAGE")" && zip -qr "$OUT" "$(basename "$STAGE")")
|
||||
ls -la "$OUT"
|
||||
unzip -l "$OUT" | tail -n +4 | awk '{print $4}' | sed '/^$/d' | grep -v '/$' | head -40
|
||||
61
proving/windows-wsl2/prove-block.sh
Executable file
61
proving/windows-wsl2/prove-block.sh
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
# Igneum proving v0: proves one fixture block inside WSL2, GPU first (SP1_PROVER=cuda) then CPU for comparison,
|
||||
# prints the RESULT lines and uploads the log to the Igneum log intake. Run by PROVE-BLOCK.bat.
|
||||
# Usage: prove-block.sh [fixture name without .json, default block-78-increment] [cpu-modes, default core]
|
||||
set -uo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
FIXTURE="${1:-block-78-increment}"
|
||||
CPU_MODES="${2:-core}"
|
||||
export PATH="$HOME/.cargo/bin:$HOME/.sp1/bin:$PATH"
|
||||
CUDA_DIR="$(ls -d /usr/local/cuda-12.* 2>/dev/null | sort -V | tail -1 || true)"
|
||||
[ -n "$CUDA_DIR" ] && export PATH="$CUDA_DIR/bin:$PATH" && export LD_LIBRARY_PATH="$CUDA_DIR/lib64:/usr/lib/wsl/lib:${LD_LIBRARY_PATH:-}"
|
||||
DEST="$HOME/igneum-prove"
|
||||
STAMP="$(date -u +%Y%m%d-%H%M%S)"
|
||||
LOG="$HERE/prove-$FIXTURE-$STAMP.log"
|
||||
RUN_ID="prove-$(hostname)-$STAMP"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
echo "igneum proving v0, run $RUN_ID, $(date -u +%FT%TZ), host $(hostname), fixture $FIXTURE"
|
||||
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader 2>/dev/null || echo "nvidia-smi not available in WSL"
|
||||
echo "cpu: $(nproc) cores, ram: $(free -g | awk '/Mem:/ {print $2}') GB visible to WSL"
|
||||
|
||||
# Fresh sources from the package (edits on the Windows side are picked up), build with the cuda feature.
|
||||
mkdir -p "$DEST"
|
||||
rsync -a --delete --exclude target "$HERE/package/" "$DEST/" 2>/dev/null || cp -r "$HERE/package/." "$DEST/"
|
||||
cd "$DEST/proving/igneum-prove"
|
||||
echo "building (first time: 10 to 30 minutes, approximate; the guest is compiled by cargo-prove inside the host build)"
|
||||
if ! cargo build --release -p igneum-prove-host --features igneum-prove-host/cuda 2>&1 | tail -3; then
|
||||
echo "BUILD FAILED"; upload; exit 1
|
||||
fi
|
||||
HOST="$DEST/proving/igneum-prove/target/release/igneum-prove-host"
|
||||
FIX="$DEST/proving/fixtures/$FIXTURE.json"
|
||||
mkdir -p "$HERE/results"
|
||||
|
||||
upload() {
|
||||
# Last 256 KB of the log to the intake, same URL and key as upload-log.bat (log uploads only).
|
||||
python3 - "$LOG" "$RUN_ID" <<'PY'
|
||||
import json, socket, sys, urllib.request
|
||||
path, run_id = sys.argv[1], sys.argv[2]
|
||||
data = open(path, 'rb').read()[-262144:].decode('utf-8', 'replace')
|
||||
body = json.dumps({"label": "prove-" + socket.gethostname(), "machine": socket.gethostname(), "run_id": run_id, "lines": data}).encode()
|
||||
req = urllib.request.Request("https://igneum-six.vercel.app/api/log", data=body, headers={"Content-Type": "application/json", "x-igneum-key": "***INTAKE-KEY-REMOVED***"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
print("upload:", r.status, r.read()[:200].decode('utf-8', 'replace'))
|
||||
except Exception as e:
|
||||
print("upload failed:", e)
|
||||
PY
|
||||
}
|
||||
|
||||
echo "=== GPU run: SP1_PROVER=cuda, modes execute + core + compressed (the first run downloads sp1-gpu-server, about 134 MB) ==="
|
||||
SP1_PROVER=cuda RUST_LOG=info "$HOST" "$FIX" --mode all --out "$HERE/results/$FIXTURE-cuda-$STAMP.json"
|
||||
echo "gpu run exit $?"
|
||||
upload
|
||||
|
||||
echo "=== CPU run: SP1_PROVER=cpu, modes $CPU_MODES (for comparison; a core proof on the CPU can take many minutes) ==="
|
||||
SP1_PROVER=cpu RUST_LOG=info "$HOST" "$FIX" --mode "$CPU_MODES" --out "$HERE/results/$FIXTURE-cpu-$STAMP.json"
|
||||
echo "cpu run exit $?"
|
||||
|
||||
echo "=== SUMMARY (RESULT lines) ==="
|
||||
grep -h '^RESULT\|^fixture\|exit' "$LOG" | sed 's/^/ /'
|
||||
upload
|
||||
echo "log: $LOG (uploaded as run_id $RUN_ID; on the Mac: node tools/logs.mjs $RUN_ID)"
|
||||
60
proving/windows-wsl2/setup-prover.ps1
Normal file
60
proving/windows-wsl2/setup-prover.ps1
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Igneum proving v0: Windows side of the setup. Run through SETUP-PROVER.bat.
|
||||
# Step A (first run): enable WSL2 and install Ubuntu 24.04. Needs a reboot.
|
||||
# Step B (after the reboot): run setup-wsl.sh inside Ubuntu.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$distro = 'Ubuntu-24.04'
|
||||
|
||||
function Is-Admin { ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) }
|
||||
if (-not (Is-Admin)) {
|
||||
Write-Host 'Asking for administrator rights (WSL install needs them)...'
|
||||
Start-Process powershell -Verb RunAs -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$PSCommandPath`"")
|
||||
exit
|
||||
}
|
||||
|
||||
$build = [int](Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuildNumber
|
||||
if ($build -lt 22000) { Write-Host "This is Windows build $build; Windows 11 (build 22000 or later) is expected. WSL2 with GPU also works on recent Windows 10 builds, continuing anyway." }
|
||||
|
||||
$nv = Get-Command nvidia-smi -ErrorAction SilentlyContinue
|
||||
if ($nv) { Write-Host 'NVIDIA driver on Windows:'; & nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader } else { Write-Host 'nvidia-smi not found: install the NVIDIA Windows driver (it includes the WSL CUDA driver; nothing is installed inside Ubuntu for the driver).' }
|
||||
|
||||
# Is the distro already installed?
|
||||
$installed = $false
|
||||
try { $list = (& wsl.exe --list --quiet 2>$null) -join "`n"; if ($list -match 'Ubuntu-24\.04') { $installed = $true } } catch {}
|
||||
|
||||
if (-not $installed) {
|
||||
Write-Host "Step A: enabling WSL2 and installing $distro (download about 400 MB, approximate)."
|
||||
Write-Host 'Command: wsl --install -d Ubuntu-24.04'
|
||||
& wsl.exe --install -d $distro
|
||||
Write-Host ''
|
||||
Write-Host '================================================================================'
|
||||
Write-Host 'REBOOT NOW. WSL2 turns on the Virtual Machine Platform, which only takes effect after a restart.'
|
||||
Write-Host 'After the reboot: Ubuntu opens once by itself and asks for a username and password (pick anything,'
|
||||
Write-Host 'for example igneum); close it, then double-click SETUP-PROVER.bat again for step B.'
|
||||
Write-Host '================================================================================'
|
||||
exit
|
||||
}
|
||||
|
||||
# Step B: inside Ubuntu.
|
||||
Write-Host "Step B: $distro is installed. Setting WSL2 as default and checking the GPU is visible inside it."
|
||||
& wsl.exe --set-default-version 2 | Out-Null
|
||||
& wsl.exe --update | Out-Null
|
||||
$wslconfig = Join-Path $env:USERPROFILE '.wslconfig'
|
||||
if (-not (Test-Path $wslconfig)) {
|
||||
# SP1's CPU prover wants 16 GB or more; the GPU prover wants 4 cores and 16 GB on the host side. Give WSL most of the RAM.
|
||||
$ramGb = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB)
|
||||
$give = [math]::Max(16, [math]::Floor($ramGb * 0.75))
|
||||
"[wsl2]`nmemory=${give}GB`nswap=16GB`n" | Set-Content -Path $wslconfig -Encoding ascii
|
||||
Write-Host "Wrote $wslconfig (memory=${give}GB of $ramGb GB, swap=16GB). WSL restarts to apply it."
|
||||
& wsl.exe --shutdown
|
||||
}
|
||||
$gpu = (& wsl.exe -d $distro -- bash -lc 'nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || ls /usr/lib/wsl/lib/libcuda.so.1 2>/dev/null') -join ' '
|
||||
if ($gpu) { Write-Host "GPU visible inside WSL: $gpu" } else { Write-Host 'WARNING: no GPU visible inside WSL. Update the NVIDIA Windows driver (GeForce 470 or later) and run wsl --update.' }
|
||||
|
||||
# Hand over to the Linux script with the package directory mounted at /mnt/<drive>/...
|
||||
$drive = $here.Substring(0,1).ToLower()
|
||||
$rest = $here.Substring(2).Replace('\', '/')
|
||||
$linuxDir = "/mnt/$drive$rest"
|
||||
Write-Host "Running setup-wsl.sh inside $distro (package at $linuxDir). This installs about 3 to 4 GB in total (approximate) and takes 15 to 40 minutes."
|
||||
& wsl.exe -d $distro -- bash "$linuxDir/setup-wsl.sh"
|
||||
Write-Host 'Setup finished. Next: double-click PROVE-BLOCK.bat.'
|
||||
66
proving/windows-wsl2/setup-wsl.sh
Executable file
66
proving/windows-wsl2/setup-wsl.sh
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env bash
|
||||
# Igneum proving v0: Ubuntu 24.04 side (inside WSL2). Run by SETUP-PROVER.bat; can be re-run, every step is idempotent.
|
||||
# Installs: build tools, the NVIDIA CUDA toolkit for WSL-Ubuntu (the WSL-specific repository; the driver comes from
|
||||
# Windows and must NOT be installed here), rustup + stable Rust, sp1up + the SP1 toolchain (succinct rustc, cargo-prove).
|
||||
# SP1's GPU prover (v6.x): no Docker and no NVIDIA Container Toolkit any more; the SDK downloads a native
|
||||
# `sp1-gpu-server` binary (about 134 MB, release asset sp1_gpu_server_v6.8.1_x86_64.tar.gz) into ~/.sp1/bin on
|
||||
# the first SP1_PROVER=cuda run. Source: https://docs.succinct.xyz/docs/sp1/generating-proofs/hardware-acceleration
|
||||
# ("Docker and the NVIDIA Container Toolkit are no longer required", "CUDA 12 runtime and a compatible NVIDIA driver",
|
||||
# "24GB or more VRAM", Linux x86_64). Version-specific lines below are approximate and dated 3 October 2026.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
log() { echo "[setup-wsl $(date +%H:%M:%S)] $*"; }
|
||||
|
||||
if ! grep -qi microsoft /proc/version; then log "this does not look like WSL; continuing anyway"; fi
|
||||
if [ ! -e /usr/lib/wsl/lib/libcuda.so.1 ]; then
|
||||
log "WARNING: /usr/lib/wsl/lib/libcuda.so.1 is missing: the Windows NVIDIA driver is not exposing the GPU to WSL. Update the Windows driver and run 'wsl --update' in PowerShell."
|
||||
fi
|
||||
|
||||
log "1/5 apt packages (build-essential, clang, pkg-config, libssl-dev, git, curl, python3, about 300 MB approximate)"
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq build-essential clang pkg-config libssl-dev git curl ca-certificates python3 wget gnupg2 >/dev/null
|
||||
|
||||
log "2/5 NVIDIA CUDA toolkit for WSL-Ubuntu (repository wsl-ubuntu, cuda-toolkit-12-8, about 3 GB approximate; the driver is NOT installed here)"
|
||||
# Source: https://docs.nvidia.com/cuda/wsl-user-guide/index.html (CUDA on WSL User Guide, "Option 1: Using the WSL-Ubuntu Package").
|
||||
if ! dpkg -s cuda-keyring >/dev/null 2>&1; then
|
||||
wget -q https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb -O /tmp/cuda-keyring.deb
|
||||
sudo dpkg -i /tmp/cuda-keyring.deb >/dev/null
|
||||
sudo apt-get update -qq
|
||||
fi
|
||||
if ! dpkg -s cuda-toolkit-12-8 >/dev/null 2>&1; then
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq cuda-toolkit-12-8 >/dev/null || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq cuda-toolkit-12-9 >/dev/null
|
||||
fi
|
||||
CUDA_DIR="$(ls -d /usr/local/cuda-12.* 2>/dev/null | sort -V | tail -1 || true)"
|
||||
if [ -n "$CUDA_DIR" ]; then
|
||||
grep -q 'usr/local/cuda' ~/.bashrc || printf '\nexport PATH="%s/bin:$PATH"\nexport LD_LIBRARY_PATH="%s/lib64:/usr/lib/wsl/lib:${LD_LIBRARY_PATH:-}"\n' "$CUDA_DIR" "$CUDA_DIR" >> ~/.bashrc
|
||||
export PATH="$CUDA_DIR/bin:$PATH"; export LD_LIBRARY_PATH="$CUDA_DIR/lib64:/usr/lib/wsl/lib:${LD_LIBRARY_PATH:-}"
|
||||
log "CUDA: $("$CUDA_DIR/bin/nvcc" --version | tail -1)"
|
||||
else
|
||||
log "WARNING: no /usr/local/cuda-12.* after the install; SP1_PROVER=cuda will fail until the toolkit is present"
|
||||
fi
|
||||
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader 2>/dev/null | sed 's/^/GPU inside WSL: /' || log "nvidia-smi not available inside WSL (the GPU may still work; the Windows driver decides)"
|
||||
|
||||
log "3/5 Rust (rustup, stable; about 500 MB approximate)"
|
||||
if [ ! -x "$HOME/.cargo/bin/cargo" ]; then
|
||||
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
|
||||
fi
|
||||
export PATH="$HOME/.cargo/bin:$HOME/.sp1/bin:$PATH"
|
||||
rustup show active-toolchain || true
|
||||
|
||||
log "4/5 sp1up and the SP1 toolchain (cargo-prove 21 MB + succinct rust toolchain about 300 MB download, 1 GB on disk, approximate)"
|
||||
# Source: https://docs.succinct.xyz/docs/sp1/getting-started/install
|
||||
if [ ! -x "$HOME/.sp1/bin/sp1up" ]; then
|
||||
curl -L https://sp1up.succinct.xyz | bash >/dev/null
|
||||
fi
|
||||
"$HOME/.sp1/bin/sp1up" --version v6.8.1 2>&1 | tail -3 || "$HOME/.sp1/bin/sp1up" 2>&1 | tail -3
|
||||
cargo prove --version
|
||||
cargo +succinct --version
|
||||
|
||||
log "5/5 copying the proving sources into the Linux file system (cargo on /mnt/c is slow) and pre-building the host with the cuda feature"
|
||||
DEST="$HOME/igneum-prove"
|
||||
mkdir -p "$DEST"
|
||||
rsync -a --delete --exclude target "$HERE/package/" "$DEST/" 2>/dev/null || cp -r "$HERE/package/." "$DEST/"
|
||||
cd "$DEST/proving/igneum-prove"
|
||||
# The guest is built by host/build.rs through cargo-prove; the host links sp1-sdk with the cuda feature.
|
||||
cargo build --release -p igneum-prove-export -p igneum-prove-host --features igneum-prove-host/cuda 2>&1 | tail -5
|
||||
log "done. Double-click PROVE-BLOCK.bat on Windows to prove the fixture block on the GPU and on the CPU."
|
||||
Loading…
Reference in a new issue