kona_sources/runtime/
loader.rs1use crate::{RuntimeConfig, RuntimeLoaderError};
4use alloy_primitives::{Address, B256, b256};
5use alloy_provider::Provider;
6use kona_derive::ChainProvider;
7use kona_genesis::RollupConfig;
8use kona_protocol::BlockInfo;
9use kona_providers_alloy::AlloyChainProvider;
10use lru::LruCache;
11use op_alloy_rpc_types_engine::ProtocolVersion;
12use std::{num::NonZeroUsize, sync::Arc};
13use url::Url;
14
15const DEFAULT_CACHE_SIZE: usize = 100;
17
18const UNSAFE_BLOCK_SIGNER_ADDRESS_STORAGE_SLOT: B256 =
21 b256!("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08");
22
23const REQUIRED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
26 b256!("0x4aaefe95bd84fd3f32700cf3b7566bc944b73138e41958b5785826df2aecace0");
27
28const RECOMMENDED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
31 b256!("0xe314dfc40f0025322aacc0ba8ef420b62fb3b702cf01e0cdf3d829117ac2ff1a");
32
33#[derive(Debug, Clone)]
35pub struct RuntimeLoader {
36 pub provider: AlloyChainProvider,
38 pub config: Arc<RollupConfig>,
40 runtime: RuntimeConfig,
42 pub cache: LruCache<BlockInfo, RuntimeConfig>,
47}
48
49impl RuntimeLoader {
50 pub fn new(l1_eth_rpc: Url, config: Arc<RollupConfig>) -> Self {
52 let provider = AlloyChainProvider::new_http(l1_eth_rpc, DEFAULT_CACHE_SIZE);
53 Self {
54 provider,
55 config,
56 cache: LruCache::new(NonZeroUsize::new(DEFAULT_CACHE_SIZE).unwrap()),
57 runtime: RuntimeConfig {
58 unsafe_block_signer_address: Address::ZERO,
59 required_protocol_version: ProtocolVersion::V0(Default::default()),
60 recommended_protocol_version: ProtocolVersion::V0(Default::default()),
61 },
62 }
63 }
64
65 pub async fn load_latest(&mut self) -> Result<RuntimeConfig, RuntimeLoaderError> {
67 let latest_block_num = self.provider.latest_block_number().await?;
68 let block_info = self.provider.block_info_by_number(latest_block_num).await?;
69 self.load(block_info).await
70 }
71
72 pub async fn load(
74 &mut self,
75 block_info: BlockInfo,
76 ) -> Result<RuntimeConfig, RuntimeLoaderError> {
77 if let Some(config) = self.cache.get(&block_info) {
79 let block = self.provider.inner.get_block(block_info.hash.into()).await?;
81 if block.is_some_and(|block| block.header.hash == block_info.hash) {
82 debug!(target: "runtime_loader", "Using cached runtime config");
83 return Ok(*config);
84 }
85 }
86
87 let unsafe_block_signer_address = self
89 .provider
90 .inner
91 .get_storage_at(
92 self.config.l1_system_config_address,
93 UNSAFE_BLOCK_SIGNER_ADDRESS_STORAGE_SLOT.into(),
94 )
95 .hash(block_info.hash)
96 .await?;
97
98 let unsafe_block_signer_address = alloy_primitives::Address::from_slice(
100 &unsafe_block_signer_address.to_be_bytes_vec()[12..],
101 );
102 debug!(target: "runtime_loader", "Unsafe block signer address: {:#x}", unsafe_block_signer_address);
103
104 let mut required_protocol_version = self.runtime.required_protocol_version;
106 let mut recommended_protocol_version = self.runtime.recommended_protocol_version;
107
108 if self.config.protocol_versions_address != Address::ZERO {
110 let required = self
111 .provider
112 .inner
113 .get_storage_at(
114 self.config.protocol_versions_address,
115 REQUIRED_PROTOCOL_VERSION_STORAGE_SLOT.into(),
116 )
117 .hash(block_info.hash)
118 .await?;
119 required_protocol_version = ProtocolVersion::decode(required.into())?;
120 debug!(target: "runtime_loader", "Required protocol version: {:?}", required_protocol_version);
121
122 let recommended = self
123 .provider
124 .inner
125 .get_storage_at(
126 self.config.protocol_versions_address,
127 RECOMMENDED_PROTOCOL_VERSION_STORAGE_SLOT.into(),
128 )
129 .hash(block_info.hash)
130 .await?;
131 recommended_protocol_version = ProtocolVersion::decode(recommended.into())?;
132 debug!(target: "runtime_loader", "Recommended protocol version: {:?}", recommended_protocol_version);
133 } else {
134 warn!(target: "runtime_loader", "Protocol versions address is not set in Rollup Config.");
135 warn!(target: "runtime_loader", "Using default protocol version: {:?}", required_protocol_version);
136 }
137
138 #[cfg(feature = "metrics")]
140 {
141 let gauge = metrics::gauge!(
142 crate::Metrics::RUNTIME_LOADER,
143 &[
144 ("unsafe_block_signer_address", unsafe_block_signer_address.to_string()),
145 ("required_protocol_version", required_protocol_version.to_string()),
146 ("recommended_protocol_version", recommended_protocol_version.to_string()),
147 ]
148 );
149 gauge.set(1);
150 }
151
152 let runtime_config = RuntimeConfig {
154 unsafe_block_signer_address,
155 required_protocol_version,
156 recommended_protocol_version,
157 };
158 debug!(target: "runtime_loader", "{}", runtime_config);
159 self.runtime = runtime_config;
160
161 self.cache.put(block_info, runtime_config);
163
164 Ok(runtime_config)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use alloy_primitives::address;
172 use op_alloy_rpc_types_engine::ProtocolVersionFormatV0;
173
174 const RPC_URL: &str = "https://docs-demo.quiknode.pro/";
175
176 #[tokio::test]
177 async fn test_online_runtime_loader() {
178 kona_cli::init_test_tracing();
179
180 let chain_id = kona_genesis::OP_MAINNET_CHAIN_ID;
182 let config =
183 kona_registry::ROLLUP_CONFIGS.get(&chain_id).expect("Invalid chain ID").clone();
184
185 let config = Arc::new(config);
187 let url = Url::parse(RPC_URL).unwrap();
188 let mut loader = RuntimeLoader::new(url.clone(), config);
189
190 let version = ProtocolVersionFormatV0 { major: 9, ..Default::default() };
192 let expected = RuntimeConfig {
193 unsafe_block_signer_address: address!("aaaa45d9549eda09e70937013520214382ffc4a2"),
194 required_protocol_version: ProtocolVersion::V0(version),
195 recommended_protocol_version: ProtocolVersion::V0(version),
196 };
197 let runtime_config = loader.load_latest().await.unwrap();
198 assert_eq!(runtime_config, expected);
199 }
200}