use crate::{RuntimeConfig, RuntimeLoaderError};
use alloy_primitives::{Address, B256, b256};
use alloy_provider::Provider;
use kona_derive::ChainProvider;
use kona_genesis::RollupConfig;
use kona_protocol::BlockInfo;
use kona_providers_alloy::AlloyChainProvider;
use lru::LruCache;
use op_alloy_rpc_types_engine::ProtocolVersion;
use std::{num::NonZeroUsize, sync::Arc};
use url::Url;
const DEFAULT_CACHE_SIZE: usize = 100;
const UNSAFE_BLOCK_SIGNER_ADDRESS_STORAGE_SLOT: B256 =
b256!("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08");
const REQUIRED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
b256!("0x4aaefe95bd84fd3f32700cf3b7566bc944b73138e41958b5785826df2aecace0");
const RECOMMENDED_PROTOCOL_VERSION_STORAGE_SLOT: B256 =
b256!("0xe314dfc40f0025322aacc0ba8ef420b62fb3b702cf01e0cdf3d829117ac2ff1a");
#[derive(Debug, Clone)]
pub struct RuntimeLoader {
pub provider: AlloyChainProvider,
pub config: Arc<RollupConfig>,
runtime: RuntimeConfig,
pub cache: LruCache<BlockInfo, RuntimeConfig>,
}
impl RuntimeLoader {
pub fn new(l1_eth_rpc: Url, config: Arc<RollupConfig>) -> Self {
let provider = AlloyChainProvider::new_http(l1_eth_rpc, DEFAULT_CACHE_SIZE);
Self {
provider,
config,
cache: LruCache::new(NonZeroUsize::new(DEFAULT_CACHE_SIZE).unwrap()),
runtime: RuntimeConfig {
unsafe_block_signer_address: Address::ZERO,
required_protocol_version: ProtocolVersion::V0(Default::default()),
recommended_protocol_version: ProtocolVersion::V0(Default::default()),
},
}
}
pub async fn load_latest(&mut self) -> Result<RuntimeConfig, RuntimeLoaderError> {
let latest_block_num = self.provider.latest_block_number().await?;
let block_info = self.provider.block_info_by_number(latest_block_num).await?;
self.load(block_info).await
}
pub async fn load(
&mut self,
block_info: BlockInfo,
) -> Result<RuntimeConfig, RuntimeLoaderError> {
if let Some(config) = self.cache.get(&block_info) {
let block = self.provider.inner.get_block(block_info.hash.into()).await?;
if block.is_some_and(|block| block.header.hash == block_info.hash) {
debug!(target: "runtime_loader", "Using cached runtime config");
return Ok(*config);
}
}
let unsafe_block_signer_address = self
.provider
.inner
.get_storage_at(
self.config.l1_system_config_address,
UNSAFE_BLOCK_SIGNER_ADDRESS_STORAGE_SLOT.into(),
)
.hash(block_info.hash)
.await?;
let unsafe_block_signer_address = alloy_primitives::Address::from_slice(
&unsafe_block_signer_address.to_be_bytes_vec()[12..],
);
debug!(target: "runtime_loader", "Unsafe block signer address: {:#x}", unsafe_block_signer_address);
let mut required_protocol_version = self.runtime.required_protocol_version;
let mut recommended_protocol_version = self.runtime.recommended_protocol_version;
if self.config.protocol_versions_address != Address::ZERO {
let required = self
.provider
.inner
.get_storage_at(
self.config.protocol_versions_address,
REQUIRED_PROTOCOL_VERSION_STORAGE_SLOT.into(),
)
.hash(block_info.hash)
.await?;
required_protocol_version = ProtocolVersion::decode(required.into())?;
debug!(target: "runtime_loader", "Required protocol version: {:?}", required_protocol_version);
let recommended = self
.provider
.inner
.get_storage_at(
self.config.protocol_versions_address,
RECOMMENDED_PROTOCOL_VERSION_STORAGE_SLOT.into(),
)
.hash(block_info.hash)
.await?;
recommended_protocol_version = ProtocolVersion::decode(recommended.into())?;
debug!(target: "runtime_loader", "Recommended protocol version: {:?}", recommended_protocol_version);
} else {
warn!(target: "runtime_loader", "Protocol versions address is not set in Rollup Config.");
warn!(target: "runtime_loader", "Using default protocol version: {:?}", required_protocol_version);
}
#[cfg(feature = "metrics")]
{
let gauge = metrics::gauge!(
crate::Metrics::RUNTIME_LOADER,
&[
("unsafe_block_signer_address", unsafe_block_signer_address.to_string()),
("required_protocol_version", required_protocol_version.to_string()),
("recommended_protocol_version", recommended_protocol_version.to_string()),
]
);
gauge.set(1);
}
let runtime_config = RuntimeConfig {
unsafe_block_signer_address,
required_protocol_version,
recommended_protocol_version,
};
debug!(target: "runtime_loader", "{}", runtime_config);
self.runtime = runtime_config;
self.cache.put(block_info, runtime_config);
Ok(runtime_config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;
use op_alloy_rpc_types_engine::ProtocolVersionFormatV0;
const RPC_URL: &str = "https://docs-demo.quiknode.pro/";
#[tokio::test]
async fn test_online_runtime_loader() {
kona_cli::init_test_tracing();
let chain_id = kona_genesis::OP_MAINNET_CHAIN_ID;
let config =
kona_registry::ROLLUP_CONFIGS.get(&chain_id).expect("Invalid chain ID").clone();
let config = Arc::new(config);
let url = Url::parse(RPC_URL).unwrap();
let mut loader = RuntimeLoader::new(url.clone(), config);
let version = ProtocolVersionFormatV0 { major: 9, ..Default::default() };
let expected = RuntimeConfig {
unsafe_block_signer_address: address!("aaaa45d9549eda09e70937013520214382ffc4a2"),
required_protocol_version: ProtocolVersion::V0(version),
recommended_protocol_version: ProtocolVersion::V0(version),
};
let runtime_config = loader.load_latest().await.unwrap();
assert_eq!(runtime_config, expected);
}
}