I had tackled into SP1 and Lighthouse in the past, and found that we have to manage our own beacon chain implementation for PoC. ([Past Dev Log](https://hackmd.io/@reamlabs/B1nPfcdwyl)) As O [nicely worked](https://hackmd.io/@reamlabs/S1rdp49uJx) on Risc0, I also tried same approach with him. I pruned the RPC module to fetch from Mekong because 1) Ream is based on `deneb` state, and 2) EF tests already provide nicely compressed snappy files for state transition. # What I did for last few hours https://github.com/ReamLabs/consensp1us/tree/945dacfc47c53721ef924bb9dbdb933c0c9da664 - Bumped SP1 version from `v3.x.x` to `v4.0.0`. - Removed Lighthouse depencency and imported Ream. - Special thanks to Kolby and Varun who implemented all those beacon types and transitions. - Checked cycles on SP1 zkVM to inspect potential bottlenecks. # Simple analysis on the way we commit values Here's my first approach: As `BeaconState` already implements `(De)Serialize` traits, I committed it directly as per the [SP1 docs](https://docs.succinct.xyz/docs/writing-programs/inputs-and-outputs#committing-data). ```rust pub fn main() { // Read an input to the program. // // Behind the scenes, this compiles down to a custom system call which handles reading inputs // from the prover. // NOTE: BeaconState/BeaconBlock should implement Serialize & Deserialize trait. println!("cycle-tracker-start: read-pre-state"); let mut pre_state = sp1_zkvm::io::read::<BeaconState>(); println!("cycle-tracker-end: read-pre-state"); println!("cycle-tracker-start: read-block"); let block = sp1_zkvm::io::read::<BeaconBlock>(); println!("cycle-tracker-end: read-block"); // Main logic of the program. // State transition of the beacon state. println!("cycle-tracker-start: process-block-header"); let _ = pre_state.process_block_header(block); println!("cycle-tracker-end: process-block-header"); // Commit to the public values of the program. The final proof will have a commitment to all the // bytes that were committed to. // NOTE: BeaconState should implement Serialize & Deserialize trait. println!("cycle-tracker-start: commit"); sp1_zkvm::io::commit::<BeaconState>(&pre_state); println!("cycle-tracker-end: commit"); } ``` The result: ``` 2025-02-02T10:56:34.654759Z INFO execute: clk = 0 pc = 0x214bc8 2025-02-02T10:56:34.654842Z INFO execute: ┌╴read-pre-state 2025-02-02T10:56:35.145496Z INFO execute: clk = 10000000 pc = 0x214b94 2025-02-02T10:56:35.594106Z INFO execute: clk = 20000000 pc = 0x214b74 2025-02-02T10:56:36.031010Z INFO execute: └╴28,998,756 cycles 2025-02-02T10:56:36.031156Z INFO execute: ┌╴read-block 2025-02-02T10:56:36.032038Z INFO execute: └╴40,289 cycles 2025-02-02T10:56:36.032060Z INFO execute: ┌╴process-block-header 2025-02-02T10:56:36.114106Z INFO execute: clk = 30000000 pc = 0x221c88 2025-02-02T10:56:36.193042Z INFO execute: └╴2,635,302 cycles 2025-02-02T10:56:36.193089Z INFO execute: ┌╴commit 2025-02-02T10:56:36.468130Z INFO execute: clk = 40000000 pc = 0x221a18 2025-02-02T10:56:37.031501Z INFO execute: clk = 50000000 pc = 0x222414 2025-02-02T10:56:37.504234Z INFO execute: clk = 60000000 pc = 0x222b4c 2025-02-02T10:56:37.957009Z INFO execute: clk = 70000000 pc = 0x2232ec 2025-02-02T10:56:38.271849Z INFO execute: clk = 80000000 pc = 0x22208c 2025-02-02T10:56:38.744302Z INFO execute: clk = 90000000 pc = 0x22248c 2025-02-02T10:56:39.009990Z INFO execute: clk = 100000000 pc = 0x222e88 2025-02-02T10:56:39.442106Z INFO execute: clk = 110000000 pc = 0x223628 2025-02-02T10:56:39.717434Z INFO execute: clk = 120000000 pc = 0x223de0 2025-02-02T10:56:40.087400Z INFO execute: clk = 130000000 pc = 0x2247d8 2025-02-02T10:56:40.329666Z INFO execute: clk = 140000000 pc = 0x20befc 2025-02-02T10:56:40.630426Z INFO execute: clk = 150000000 pc = 0x221184 2025-02-02T10:56:40.871142Z INFO execute: clk = 160000000 pc = 0x221b7c 2025-02-02T10:56:41.135529Z INFO execute: clk = 170000000 pc = 0x222578 2025-02-02T10:56:41.547897Z INFO execute: clk = 180000000 pc = 0x222d18 2025-02-02T10:56:41.872431Z INFO execute: clk = 190000000 pc = 0x2234d0 2025-02-02T10:56:42.258735Z INFO execute: clk = 200000000 pc = 0x223ec8 2025-02-02T10:56:42.679410Z INFO execute: clk = 210000000 pc = 0x2248c0 2025-02-02T10:56:43.113947Z INFO execute: clk = 220000000 pc = 0x214934 2025-02-02T10:56:43.663033Z INFO execute: clk = 230000000 pc = 0x22126c 2025-02-02T10:56:43.947052Z INFO execute: clk = 240000000 pc = 0x221c68 2025-02-02T10:56:44.235545Z INFO execute: clk = 250000000 pc = 0x222408 2025-02-02T10:56:44.521727Z INFO execute: clk = 260000000 pc = 0x222bc0 2025-02-02T10:56:44.867585Z INFO execute: clk = 270000000 pc = 0x2235b8 2025-02-02T10:56:45.127102Z INFO execute: clk = 280000000 pc = 0x222064 2025-02-02T10:56:45.351294Z INFO execute: └╴256,895,679 cycles 2025-02-02T10:56:45.356578Z INFO execute: close time.busy=10.7s time.idle=3.75µs Program executed successfully. Execution is correct! Number of cycles: 288590600 ``` Wow, committing the whole beacon state requires more than 200 million cycles. Next, I tried committing `bytes` like this: ```rust pub fn main() { // ... Same as above // Commit to the public values of the program. The final proof will have a commitment to all the // bytes that were committed to. // NOTE: BeaconState should implement Serialize & Deserialize trait. println!("cycle-tracker-start: convert-to-ssz-bytes"); let pre_state_bytes = pre_state.as_ssz_bytes(); println!("cycle-tracker-end: convert-to-ssz-bytes"); println!("cycle-tracker-start: commit"); sp1_zkvm::io::commit_slice(&pre_state_bytes); println!("cycle-tracker-end: commit"); } ``` Result was: ``` 2025-02-02T11:23:56.307329Z INFO vk verification: true 2025-02-02T11:24:02.208368Z INFO execute: clk = 0 pc = 0x211898 2025-02-02T11:24:02.208476Z INFO execute: ┌╴read-pre-state 2025-02-02T11:24:02.546354Z INFO execute: clk = 10000000 pc = 0x211858 2025-02-02T11:24:02.894937Z INFO execute: clk = 20000000 pc = 0x21172c 2025-02-02T11:24:03.220593Z INFO execute: └╴28,998,978 cycles 2025-02-02T11:24:03.220655Z INFO execute: ┌╴read-block 2025-02-02T11:24:03.221715Z INFO execute: └╴40,464 cycles 2025-02-02T11:24:03.221739Z INFO execute: ┌╴process-block-header 2025-02-02T11:24:03.246149Z INFO execute: clk = 30000000 pc = 0x220714 2025-02-02T11:24:03.290393Z INFO execute: └╴2,635,302 cycles 2025-02-02T11:24:03.290424Z INFO execute: ┌╴convert-to-ssz-bytes 2025-02-02T11:24:03.614655Z INFO execute: clk = 40000000 pc = 0x21157c 2025-02-02T11:24:03.707844Z INFO execute: └╴11,168,881 cycles 2025-02-02T11:24:03.708444Z INFO execute: ┌╴commit 2025-02-02T11:24:03.901205Z INFO execute: clk = 50000000 pc = 0x2213a0 2025-02-02T11:24:04.133915Z INFO execute: clk = 60000000 pc = 0x223a64 2025-02-02T11:24:04.388716Z INFO execute: clk = 70000000 pc = 0x222554 2025-02-02T11:24:04.633064Z INFO execute: clk = 80000000 pc = 0x221044 2025-02-02T11:24:04.892342Z INFO execute: clk = 90000000 pc = 0x223708 2025-02-02T11:24:05.149124Z INFO execute: clk = 100000000 pc = 0x2221f8 2025-02-02T11:24:05.617887Z INFO execute: clk = 110000000 pc = 0x220ce8 2025-02-02T11:24:05.893535Z INFO execute: clk = 120000000 pc = 0x2233ac 2025-02-02T11:24:06.126702Z INFO execute: clk = 130000000 pc = 0x221e9c 2025-02-02T11:24:06.392399Z INFO execute: clk = 140000000 pc = 0x22098c 2025-02-02T11:24:06.777869Z INFO execute: clk = 150000000 pc = 0x223050 2025-02-02T11:24:07.208907Z INFO execute: clk = 160000000 pc = 0x221b40 2025-02-02T11:24:07.583941Z INFO execute: clk = 170000000 pc = 0x220630 2025-02-02T11:24:07.955476Z INFO execute: clk = 180000000 pc = 0x222cf4 2025-02-02T11:24:08.296755Z INFO execute: clk = 190000000 pc = 0x2217e4 2025-02-02T11:24:08.661738Z INFO execute: clk = 200000000 pc = 0x2202d4 2025-02-02T11:24:09.027538Z INFO execute: └╴165,892,212 cycles 2025-02-02T11:24:09.031684Z INFO execute: close time.busy=6.83s time.idle=9.25µs Program executed successfully. Execution is correct! Number of cycles: 208756823 ``` This approach reduced cycle usage by about 80 million. # Another analysis (vs. Risc0) ``` 2025-02-02T11:24:02.208368Z INFO execute: clk = 0 pc = 0x211898 2025-02-02T11:24:02.208476Z INFO execute: ┌╴read-pre-state 2025-02-02T11:24:02.546354Z INFO execute: clk = 10000000 pc = 0x211858 2025-02-02T11:24:02.894937Z INFO execute: clk = 20000000 pc = 0x21172c 2025-02-02T11:24:03.220593Z INFO execute: └╴28,998,978 cycles 2025-02-02T11:24:03.220655Z INFO execute: ┌╴read-block 2025-02-02T11:24:03.221715Z INFO execute: └╴40,464 cycles 2025-02-02T11:24:03.221739Z INFO execute: ┌╴process-block-header 2025-02-02T11:24:03.246149Z INFO execute: clk = 30000000 pc = 0x220714 2025-02-02T11:24:03.290393Z INFO execute: └╴2,635,302 cycles ``` O also provided the [benchmark](https://hackmd.io/@reamlabs/S1rdp49uJx#Details) on Risc0. Interestingly, SP1 requires **fewer** cycles. | | Risc0 | SP1 | |----------------------|------------|-------------| | read_pre_state | 47,791,528 | 28,998,978 | | read_block | 49,553 | 40,464 | | process_block_header | 3,809,599 | 2,635,302 | # What's next - `process_block_header` is one of the smallest transitions in the beacon chain. So it would be better to work on `process_attestation`, which must **verify** BLS signatures. - We could start building our `runtime` crate based on our work.