kona_sources/runtime/
loader.rs

1//! Contains the [`RuntimeLoader`] implementation.
2
3use 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
15/// The default cache size for the [`RuntimeLoader`].
16const DEFAULT_CACHE_SIZE: usize = 100;
17
18/// The storage slot that the unsafe block signer address is stored at.
19/// Computed as: `bytes32(uint256(keccak256("systemconfig.unsafeblocksigner")) - 1)`
20const UNSAFE_BLOCK_SIGNER_ADDRESS_STORAGE_SLOT: B256 =
21    b256!("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08");
22
23/// The storage slot that the required protocol version is stored at.
24/// Computed as: `bytes32(uint256(keccak256("protocolversion.required")) - 1)`
25const REQUIRED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
26    b256!("0x4aaefe95bd84fd3f32700cf3b7566bc944b73138e41958b5785826df2aecace0");
27
28/// The storage slot that the recommended protocol version is stored at.
29/// Computed as: `bytes32(uint256(keccak256("protocolversion.recommended")) - 1)`
30const RECOMMENDED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
31    b256!("0xe314dfc40f0025322aacc0ba8ef420b62fb3b702cf01e0cdf3d829117ac2ff1a");
32
33/// The runtime loader.
34#[derive(Debug, Clone)]
35pub struct RuntimeLoader {
36    /// The L1 Client
37    pub provider: AlloyChainProvider,
38    /// The rollup config.
39    pub config: Arc<RollupConfig>,
40    /// Caches the previously loaded runtime config.
41    runtime: RuntimeConfig,
42    /// Cache mapping [`BlockInfo`] to the [`RuntimeConfig`].
43    ///
44    /// If the block hash for the given block info is a mismatch, the runtime config
45    /// will be reloaded.
46    pub cache: LruCache<BlockInfo, RuntimeConfig>,
47}
48
49impl RuntimeLoader {
50    /// Constructs a new [`RuntimeLoader`] with the given provider [`Url`].
51    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    /// Loads the [`RuntimeConfig`] for the latest block.
66    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    /// Loads the [`RuntimeConfig`] for the given [`BlockInfo`].
73    pub async fn load(
74        &mut self,
75        block_info: BlockInfo,
76    ) -> Result<RuntimeConfig, RuntimeLoaderError> {
77        // Check if the runtime config is already cached.
78        if let Some(config) = self.cache.get(&block_info) {
79            // Only use the cached config if the block hash matches.
80            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        // Fetch the unsafe block signer address from the system config.
88        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        // Convert the unsafe block signer address to the correct type.
99        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        // If the protocol versions address is not set, return the default config.
105        let mut required_protocol_version = self.runtime.required_protocol_version;
106        let mut recommended_protocol_version = self.runtime.recommended_protocol_version;
107
108        // Fetch the required protocol version from the system config.
109        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        // Metrics
139        #[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        // Construct the runtime config.
153        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        // Cache the runtime config.
162        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        // Load the OP Mainnet config.
181        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        // Construct the runtime loader.
186        let config = Arc::new(config);
187        let url = Url::parse(RPC_URL).unwrap();
188        let mut loader = RuntimeLoader::new(url.clone(), config);
189
190        // Load the runtime config.
191        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}