1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// Copyright 2025 RISC Zero, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Types related to commitments to a historical state.
use crate::{
    beacon, BlockHeaderCommit, Commitment, CommitmentVersion, ComposeInput, EvmBlockHeader,
};
use alloy_primitives::{Sealed, B256, U256};
use beacon::{BeaconCommit, GeneralizedBeaconCommit, STATE_ROOT_LEAF_INDEX};
use beacon_roots::{BeaconRootsContract, BeaconRootsState};
use serde::{Deserialize, Serialize};

pub(crate) mod beacon_roots;

/// Input committing a previous block hash to the corresponding Beacon Chain block root.
pub type HistoryInput<H> = ComposeInput<H, HistoryCommit>;

/// A commitment that an execution block is included as an ancestor of a specific beacon block on
/// the Ethereum blockchain.
///
/// This struct encapsulates the necessary data to prove that a given execution block is part of the
/// canonical chain according to the Beacon Chain.
#[derive(Clone, Serialize, Deserialize)]
pub struct HistoryCommit {
    /// Commit of the Steel EVM execution block hash to its beacon block hash.
    evm_commit: BeaconCommit,
    /// Iterative commits for verifying `evm_commit` as an ancestor of some valid Beacon block.
    state_commits: Vec<StateCommit>,
}

/// Represents a commitment of a beacon roots contract state to a Beacon Chain block root.
#[derive(Clone, Serialize, Deserialize)]
struct StateCommit {
    /// State for verifying `evm_commit`.
    state: BeaconRootsState,
    /// Commitment for `state` to a Beacon Chain block root.
    state_commit: GeneralizedBeaconCommit<STATE_ROOT_LEAF_INDEX>,
}

impl<H: EvmBlockHeader> BlockHeaderCommit<H> for HistoryCommit {
    /// Generates a commitment that proves the given block header is included in the Beacon Chain's
    /// history. Panics if the provided [HistoryCommit] data is invalid or inconsistent.
    #[inline]
    fn commit(self, header: &Sealed<H>, config_id: B256) -> Commitment {
        // first, compute the beacon commit of the EVM execution
        let initial_commitment = self.evm_commit.commit(header, config_id);
        let (mut timestamp, version) = initial_commitment.decode_id();
        // just a sanity check, a BeaconCommit will always have this version
        assert_eq!(version, CommitmentVersion::Beacon as u16);

        // starting from evm_commit, "walk forward" along state_commits to reach a later beacon root
        let mut beacon_root = initial_commitment.digest;
        for mut state_commit in self.state_commits {
            // verify that the previous commitment is valid wrt the current state
            let state_root = state_commit.state.root();
            let commitment_root =
                BeaconRootsContract::get_from_db(&mut state_commit.state, timestamp)
                    .expect("Beacon roots contract failed");
            assert_eq!(commitment_root, beacon_root, "Beacon root does not match");

            // compute the beacon commitment of the current state
            let (commit_ts, commit_beacon_root) = state_commit.state_commit.into_commit(state_root);
            timestamp = U256::from(commit_ts);
            beacon_root = commit_beacon_root;
        }

        Commitment::new(
            CommitmentVersion::Beacon as u16,
            timestamp.to(),
            beacon_root,
            initial_commitment.configID,
        )
    }
}

#[cfg(feature = "host")]
mod host {
    use super::*;
    use crate::{
        beacon::host::{client::BeaconClient, create_beacon_commit},
        ethereum::EthBlockHeader,
        history::beacon_roots::{BeaconRootsState, HISTORY_BUFFER_LENGTH},
    };
    use alloy::{network::Ethereum, providers::Provider};
    use alloy_primitives::{BlockNumber, Sealable};
    use anyhow::{ensure, Context};
    use url::Url;

    impl HistoryCommit {
        /// Creates a `HistoryCommit` from an EVM block header and a commitment header.
        ///
        /// This method fetches the necessary data from the Ethereum and Beacon chain to construct a
        /// `HistoryCommit`. It iterates through blocks from the EVM header's number up to
        /// the commitment header's number, generating `StateCommit`s for each block in the range.
        pub(crate) async fn from_headers<P>(
            evm_header: &Sealed<EthBlockHeader>,
            commitment_header: &Sealed<EthBlockHeader>,
            rpc_provider: P,
            beacon_url: Url,
        ) -> anyhow::Result<Self>
        where
            P: Provider<Ethereum>,
        {
            ensure!(
                evm_header.number() < commitment_header.number(),
                "EVM execution block not before commitment block"
            );
            let client = BeaconClient::new(beacon_url.clone()).context("invalid URL")?;

            // create a regular beacon commit to the block header used for EVM execution
            let evm_commit =
                BeaconCommit::from_header(evm_header, &rpc_provider, beacon_url).await?;
            let mut commit_ts = evm_commit.timestamp();
            // safe unwrap: BeaconCommit::from_header checks that the proof can be processed
            let mut commit_beacon_root = evm_commit.process_proof(evm_header.seal()).unwrap();

            let mut state_commits: Vec<StateCommit> = Vec::new();

            // we assume that not more than 25% of the blocks have been skipped
            // TODO(#309): implement a more sophisticated way to determine the step size
            let step = HISTORY_BUFFER_LENGTH.to::<BlockNumber>() * 75 / 100;
            let target = commitment_header.number();

            let mut state_block = evm_header.number;
            while state_block < target {
                state_block = std::cmp::min(state_block + step, target);

                // get the header of the state block
                let rpc_block = rpc_provider
                    .get_block_by_number(state_block.into())
                    .await
                    .context("eth_getBlockByNumber failed")?
                    .with_context(|| format!("block {} not found", state_block))?;
                let header: EthBlockHeader = rpc_block
                    .header
                    .try_into()
                    .with_context(|| format!("block {} invalid", state_block))?;
                let header = header.seal_slow();

                log::debug!(
                    "chained commitment for block {} ({})",
                    header.number,
                    header.seal()
                );

                // derive the historic state needed to verify the previous beacon commitment
                let (beacon_root, state) = BeaconRootsState::preflight_get(
                    U256::from(commit_ts),
                    &rpc_provider,
                    header.seal().into(),
                )
                .await
                .with_context(|| format!("preflight failed for block {}", header.seal()))?;
                // the result of the beacon roots contract must match the beacon root of the commit
                ensure!(
                    beacon_root == commit_beacon_root,
                    "inconsistent state for block {}",
                    header.seal()
                );

                // create a beacon commitment to that state
                let (state_commit, beacon_root) =
                    create_beacon_commit(&header, "state_root".into(), &rpc_provider, &client)
                        .await?;
                state_commit
                    .verify(state.root(), beacon_root)
                    .context("proof derived from API does not verify")?;
                commit_ts = state_commit.timestamp();
                commit_beacon_root = beacon_root;

                state_commits.push(StateCommit {
                    state,
                    state_commit,
                });
            }

            log::debug!("state commitments: {}", state_commits.len());

            Ok(HistoryCommit {
                evm_commit,
                state_commits,
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ethereum::EthBlockHeader;
    use alloy::providers::{Provider, ProviderBuilder};
    use alloy_primitives::Sealable;

    const EL_URL: &str = "https://ethereum-rpc.publicnode.com";
    const CL_URL: &str = "https://ethereum-beacon-api.publicnode.com";

    #[tokio::test]
    #[ignore = "queries actual RPC nodes"]
    async fn from_beacon_commit_and_header() {
        let el = ProviderBuilder::default().connect(EL_URL).await.unwrap();

        // get the latest 4 headers
        let headers = get_headers(4).await.unwrap();

        // create a history commitment executing on header[0] and committing to header[2]
        let mut commit =
            HistoryCommit::from_headers(&headers[0], &headers[2], &el, CL_URL.parse().unwrap())
                .await
                .unwrap();

        let [StateCommit {
            state,
            state_commit,
        }] = &mut commit.state_commits[..]
        else {
            panic!("invalid state_commits")
        };

        // the state commit should verify against the beacon block root of headers[2]<
        state_commit
            .verify(state.root(), headers[3].parent_beacon_block_root.unwrap())
            .unwrap();
        // the beacon roots contract should return the beacon block root of headers[0]
        assert_eq!(
            BeaconRootsContract::get_from_db(state, U256::from(commit.evm_commit.timestamp()))
                .unwrap(),
            headers[1].parent_beacon_block_root.unwrap(),
        );
        // the resulting commitment should correspond to the beacon block root of headers[2]
        assert_eq!(
            commit.commit(&headers[0], B256::ZERO).digest,
            headers[3].parent_beacon_block_root.unwrap()
        );
    }

    // get the latest n headers, with header[0] being the oldest and header[n-1] being the newest.
    async fn get_headers(n: usize) -> anyhow::Result<Vec<Sealed<EthBlockHeader>>> {
        let el = ProviderBuilder::new().connect(EL_URL).await?;
        let latest = el.get_block_number().await?;

        let mut headers = Vec::with_capacity(n);
        for number in latest + 1 - (n as u64)..=latest {
            let block = el.get_block_by_number(number.into()).await?.unwrap();
            let header: EthBlockHeader = block.header.try_into()?;
            headers.push(header.seal_slow());
        }

        Ok(headers)
    }
}