kona_proof_interop/
provider.rs

1//! [InteropProvider] trait implementation using a [CommsClient] data source.
2
3use crate::{BootInfo, HintType, PreState};
4use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
5use alloy_consensus::Header;
6use alloy_eips::eip2718::Decodable2718;
7use alloy_primitives::{Address, B256};
8use alloy_rlp::Decodable;
9use async_trait::async_trait;
10use kona_interop::InteropProvider;
11use kona_mpt::{OrderedListWalker, TrieHinter, TrieNode, TrieProvider};
12use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType, errors::PreimageOracleError};
13use kona_proof::{eip_2935_history_lookup, errors::OracleProviderError};
14use kona_registry::HashMap;
15use op_alloy_consensus::OpReceiptEnvelope;
16use spin::RwLock;
17
18/// A [CommsClient] backed [InteropProvider] implementation.
19#[derive(Debug, Clone)]
20pub struct OracleInteropProvider<T> {
21    /// The oracle client.
22    oracle: Arc<T>,
23    /// The [PreState] for the current program execution.
24    boot: BootInfo,
25    /// The safe head block header cache, keyed by chain ID.
26    safe_head_cache: Arc<RwLock<HashMap<u64, Header>>>,
27    /// The chain ID for the current call context. Used to declare the chain ID for the trie hints.
28    chain_id: Arc<RwLock<Option<u64>>>,
29}
30
31impl<T> OracleInteropProvider<T>
32where
33    T: CommsClient + Send + Sync,
34{
35    /// Creates a new [OracleInteropProvider] with the given oracle client and [PreState].
36    pub fn new(oracle: Arc<T>, boot: BootInfo) -> Self {
37        Self {
38            oracle,
39            boot,
40            safe_head_cache: Arc::new(RwLock::new(HashMap::default())),
41            chain_id: Arc::new(RwLock::new(None)),
42        }
43    }
44
45    /// Fetch the [Header] for the block with the given hash.
46    pub async fn header_by_hash(
47        &self,
48        chain_id: u64,
49        block_hash: B256,
50    ) -> Result<Header, <Self as InteropProvider>::Error> {
51        HintType::L2BlockHeader
52            .with_data(&[block_hash.as_slice(), chain_id.to_be_bytes().as_ref()])
53            .send(self.oracle.as_ref())
54            .await?;
55
56        let header_rlp = self
57            .oracle
58            .get(PreimageKey::new(*block_hash, PreimageKeyType::Keccak256))
59            .await
60            .map_err(OracleProviderError::Preimage)?;
61
62        Header::decode(&mut header_rlp.as_ref()).map_err(OracleProviderError::Rlp)
63    }
64
65    /// Fetch the [OpReceiptEnvelope]s for the block with the given hash.
66    async fn derive_receipts(
67        &self,
68        chain_id: u64,
69        block_hash: B256,
70        header: &Header,
71    ) -> Result<Vec<OpReceiptEnvelope>, <Self as InteropProvider>::Error> {
72        // Send a hint for the block's receipts, and walk through the receipts trie in the header to
73        // verify them.
74        HintType::L2Receipts
75            .with_data(&[block_hash.as_ref(), chain_id.to_be_bytes().as_slice()])
76            .send(self.oracle.as_ref())
77            .await?;
78        let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)
79            .map_err(OracleProviderError::TrieWalker)?;
80
81        // Decode the receipts within the receipts trie.
82        let receipts = trie_walker
83            .into_iter()
84            .map(|(_, rlp)| {
85                let envelope = OpReceiptEnvelope::decode_2718(&mut rlp.as_ref())?;
86                Ok(envelope)
87            })
88            .collect::<Result<Vec<_>, _>>()
89            .map_err(OracleProviderError::Rlp)?;
90
91        Ok(receipts)
92    }
93}
94
95#[async_trait]
96impl<T> InteropProvider for OracleInteropProvider<T>
97where
98    T: CommsClient + Send + Sync,
99{
100    type Error = OracleProviderError;
101
102    /// Fetch a [Header] by its number.
103    async fn header_by_number(&self, chain_id: u64, number: u64) -> Result<Header, Self::Error> {
104        // Find the safe head for the given chain ID.
105        //
106        // If the safe head is not in the cache, we need to fetch it from the oracle.
107        let mut header = if let Some(header) = self.safe_head_cache.read().get(&chain_id) {
108            header.clone()
109        } else {
110            let pre_state = match &self.boot.agreed_pre_state {
111                PreState::SuperRoot(super_root) => super_root,
112                PreState::TransitionState(transition_state) => &transition_state.pre_state,
113            };
114            let output = pre_state
115                .output_roots
116                .iter()
117                .find(|o| o.chain_id == chain_id)
118                .ok_or(OracleProviderError::UnknownChainId(chain_id))?;
119            HintType::L2OutputRoot
120                .with_data(&[
121                    output.output_root.as_slice(),
122                    output.chain_id.to_be_bytes().as_slice(),
123                ])
124                .send(self.oracle.as_ref())
125                .await?;
126            let output_preimage = self
127                .oracle
128                .get(PreimageKey::new(*output.output_root, PreimageKeyType::Keccak256))
129                .await
130                .map_err(OracleProviderError::Preimage)?;
131            let safe_head_hash = output_preimage[96..128]
132                .try_into()
133                .map_err(OracleProviderError::SliceConversion)?;
134
135            // Fetch the starting block header.
136            let header = self.header_by_hash(chain_id, safe_head_hash).await?;
137            self.safe_head_cache.write().insert(chain_id, header.clone());
138            header
139        };
140
141        // Check if the block number is in range. If not, we can fail early.
142        if number > header.number {
143            return Err(OracleProviderError::BlockNumberPastHead(number, header.number));
144        }
145
146        // Set the chain ID for the trie hints, and explicitly drop the lock.
147        let mut chain_id_lock = self.chain_id.write();
148        *chain_id_lock = Some(chain_id);
149        drop(chain_id_lock);
150
151        // Walk back the block headers to the desired block number.
152        let rollup_config = self.boot.rollup_config(chain_id).ok_or_else(|| {
153            PreimageOracleError::Other("Missing rollup config for chain ID".to_string())
154        })?;
155        let mut linear_fallback = false;
156
157        while header.number > number {
158            if rollup_config.is_isthmus_active(header.timestamp) && !linear_fallback {
159                // If Isthmus is active, the EIP-2935 contract is used to perform leaping lookbacks
160                // through consulting the ring buffer within the contract. If this
161                // lookup fails for any reason, we fall back to linear walk back.
162                let block_hash = match eip_2935_history_lookup(&header, 0, self, self).await {
163                    Ok(hash) => hash,
164                    Err(_) => {
165                        // If the EIP-2935 lookup fails for any reason, attempt fallback to linear
166                        // walk back.
167                        linear_fallback = true;
168                        continue;
169                    }
170                };
171
172                header = self.header_by_hash(chain_id, block_hash).await?;
173            } else {
174                // Walk back the block headers one-by-one until the desired block number is reached.
175                header = self.header_by_hash(chain_id, header.parent_hash).await?;
176            }
177        }
178
179        Ok(header)
180    }
181
182    /// Fetch all receipts for a given block by number.
183    async fn receipts_by_number(
184        &self,
185        chain_id: u64,
186        number: u64,
187    ) -> Result<Vec<OpReceiptEnvelope>, Self::Error> {
188        let header = self.header_by_number(chain_id, number).await?;
189        self.derive_receipts(chain_id, header.hash_slow(), &header).await
190    }
191
192    /// Fetch all receipts for a given block by hash.
193    async fn receipts_by_hash(
194        &self,
195        chain_id: u64,
196        block_hash: B256,
197    ) -> Result<Vec<OpReceiptEnvelope>, Self::Error> {
198        let header = self.header_by_hash(chain_id, block_hash).await?;
199        self.derive_receipts(chain_id, block_hash, &header).await
200    }
201}
202
203impl<T> TrieProvider for OracleInteropProvider<T>
204where
205    T: CommsClient + Send + Sync + Clone,
206{
207    type Error = OracleProviderError;
208
209    fn trie_node_by_hash(&self, key: B256) -> Result<TrieNode, Self::Error> {
210        kona_proof::block_on(async move {
211            let trie_node_rlp = self
212                .oracle
213                .get(PreimageKey::new(*key, PreimageKeyType::Keccak256))
214                .await
215                .map_err(OracleProviderError::Preimage)?;
216            TrieNode::decode(&mut trie_node_rlp.as_ref()).map_err(OracleProviderError::Rlp)
217        })
218    }
219}
220
221impl<T: CommsClient> TrieHinter for OracleInteropProvider<T> {
222    type Error = OracleProviderError;
223
224    fn hint_trie_node(&self, hash: B256) -> Result<(), Self::Error> {
225        kona_proof::block_on(async move {
226            HintType::L2StateNode
227                .with_data(&[hash.as_slice()])
228                .with_data(
229                    self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()),
230                )
231                .send(self.oracle.as_ref())
232                .await
233        })
234    }
235
236    fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<(), Self::Error> {
237        kona_proof::block_on(async move {
238            HintType::L2AccountProof
239                .with_data(&[block_number.to_be_bytes().as_ref(), address.as_slice()])
240                .with_data(
241                    self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()),
242                )
243                .send(self.oracle.as_ref())
244                .await
245        })
246    }
247
248    fn hint_storage_proof(
249        &self,
250        address: alloy_primitives::Address,
251        slot: alloy_primitives::U256,
252        block_number: u64,
253    ) -> Result<(), Self::Error> {
254        kona_proof::block_on(async move {
255            HintType::L2AccountStorageProof
256                .with_data(&[
257                    block_number.to_be_bytes().as_ref(),
258                    address.as_slice(),
259                    slot.to_be_bytes::<32>().as_ref(),
260                ])
261                .with_data(
262                    self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()),
263                )
264                .send(self.oracle.as_ref())
265                .await
266        })
267    }
268
269    fn hint_execution_witness(
270        &self,
271        parent_hash: B256,
272        op_payload_attributes: &op_alloy_rpc_types_engine::OpPayloadAttributes,
273    ) -> Result<(), Self::Error> {
274        kona_proof::block_on(async move {
275            let encoded_attributes =
276                serde_json::to_vec(op_payload_attributes).map_err(OracleProviderError::Serde)?;
277
278            HintType::L2PayloadWitness
279                .with_data(&[parent_hash.as_slice(), &encoded_attributes])
280                .with_data(
281                    self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()),
282                )
283                .send(self.oracle.as_ref())
284                .await
285        })
286    }
287}