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.