use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
fmt,
future::Future,
marker::PhantomData,
str::FromStr,
sync::{Arc, RwLock},
};
use tokio::runtime::Builder;
use zksync_basic_types::{Address, L1BatchNumber, L2BlockNumber, L2ChainId, H256, U256, U64};
use zksync_types::{
api::{
Block, BlockDetails, BlockIdVariant, BlockNumber, BridgeAddresses, Transaction,
TransactionDetails, TransactionVariant,
},
fee_model::FeeParams,
l2::L2Tx,
url::SensitiveUrl,
ProtocolVersionId, StorageKey,
};
use zksync_state::ReadStorage;
use zksync_utils::{bytecode::hash_bytecode, h256_to_u256};
use zksync_web3_decl::{
client::{Client, L2},
namespaces::ZksNamespaceClient,
};
use zksync_web3_decl::{namespaces::EthNamespaceClient, types::Index};
use crate::{config::cache::CacheConfig, node::TEST_NODE_NETWORK_ID};
use crate::{
config::gas::{DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR},
system_contracts,
};
use crate::{deps::InMemoryStorage, http_fork_source::HttpForkSource};
pub fn block_on<F: Future + Send + 'static>(future: F) -> F::Output
where
F::Output: Send,
{
std::thread::spawn(move || {
let runtime = Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio runtime creation failed");
runtime.block_on(future)
})
.join()
.unwrap()
}
#[derive(Debug, Clone)]
pub enum ForkNetwork {
Mainnet,
SepoliaTestnet,
GoerliTestnet,
Other(String),
}
impl ForkNetwork {
pub fn to_url(&self) -> &str {
match self {
ForkNetwork::Mainnet => "https://mainnet.era.zksync.io:443",
ForkNetwork::SepoliaTestnet => "https://sepolia.era.zksync.dev:443",
ForkNetwork::GoerliTestnet => "https://testnet.era.zksync.dev:443",
ForkNetwork::Other(url) => url,
}
}
pub fn local_gas_scale_factors(&self) -> (f64, f32) {
match self {
ForkNetwork::Mainnet => (1.5, 1.2),
ForkNetwork::SepoliaTestnet => (2.0, 1.2),
ForkNetwork::GoerliTestnet => (1.2, 1.2),
ForkNetwork::Other(_) => (
DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
),
}
}
}
#[derive(Debug, Clone)]
pub struct ForkStorage<S> {
pub inner: Arc<RwLock<ForkStorageInner<S>>>,
pub chain_id: L2ChainId,
}
#[derive(Debug)]
pub struct ForkStorageInner<S> {
pub raw_storage: InMemoryStorage,
pub value_read_cache: HashMap<StorageKey, H256>,
pub factory_dep_cache: HashMap<H256, Option<Vec<u8>>>,
pub fork: Option<ForkDetails>,
pub dummy: PhantomData<S>,
}
impl<S: ForkSource> ForkStorage<S> {
pub fn new(
fork: Option<ForkDetails>,
system_contracts_options: &system_contracts::Options,
) -> Self {
let chain_id = fork
.as_ref()
.and_then(|d| d.overwrite_chain_id)
.unwrap_or(L2ChainId::from(TEST_NODE_NETWORK_ID));
tracing::info!("Starting network with chain id: {:?}", chain_id);
ForkStorage {
inner: Arc::new(RwLock::new(ForkStorageInner {
raw_storage: InMemoryStorage::with_system_contracts_and_chain_id(
chain_id,
hash_bytecode,
system_contracts_options,
),
value_read_cache: Default::default(),
fork,
factory_dep_cache: Default::default(),
dummy: Default::default(),
})),
chain_id,
}
}
pub fn get_cache_config(&self) -> Result<CacheConfig, String> {
let reader = self
.inner
.read()
.map_err(|e| format!("Failed to acquire read lock: {}", e))?;
let cache_config = if let Some(ref fork_details) = reader.fork {
fork_details.cache_config.clone()
} else {
CacheConfig::default()
};
Ok(cache_config)
}
pub fn get_fork_url(&self) -> Result<String, String> {
let reader = self
.inner
.read()
.map_err(|e| format!("Failed to acquire read lock: {}", e))?;
if let Some(ref fork_details) = reader.fork {
fork_details
.fork_source
.get_fork_url()
.map_err(|e| e.to_string())
} else {
Err("not forked".to_string())
}
}
pub fn read_value_internal(
&self,
key: &StorageKey,
) -> eyre::Result<zksync_types::StorageValue> {
let mut mutator = self.inner.write().unwrap();
let local_storage = mutator.raw_storage.read_value(key);
if let Some(fork) = &mutator.fork {
if !H256::is_zero(&local_storage) {
return Ok(local_storage);
}
if let Some(value) = mutator.value_read_cache.get(key) {
return Ok(*value);
}
let l2_miniblock = fork.l2_miniblock;
let key_ = *key;
let result = fork.fork_source.get_storage_at(
*key_.account().address(),
h256_to_u256(*key_.key()),
Some(BlockIdVariant::BlockNumber(BlockNumber::Number(U64::from(
l2_miniblock,
)))),
)?;
mutator.value_read_cache.insert(*key, result);
Ok(result)
} else {
Ok(local_storage)
}
}
pub fn load_factory_dep_internal(&self, hash: H256) -> eyre::Result<Option<Vec<u8>>> {
let mut mutator = self.inner.write().unwrap();
let local_storage = mutator.raw_storage.load_factory_dep(hash);
if let Some(fork) = &mutator.fork {
if local_storage.is_some() {
return Ok(local_storage);
}
if let Some(value) = mutator.factory_dep_cache.get(&hash) {
return Ok(value.clone());
}
let result = fork.fork_source.get_bytecode_by_hash(hash)?;
mutator.factory_dep_cache.insert(hash, result.clone());
Ok(result)
} else {
Ok(local_storage)
}
}
pub fn is_write_initial_internal(&self, key: &StorageKey) -> eyre::Result<bool> {
let value = self.read_value_internal(key)?;
if value != H256::zero() {
return Ok(false);
}
let mut mutator = self.inner.write().unwrap();
Ok(mutator.raw_storage.is_write_initial(key))
}
fn get_enumeration_index_internal(&self, _key: &StorageKey) -> Option<u64> {
Some(0_u64)
}
}
impl<S: std::fmt::Debug + ForkSource> ReadStorage for ForkStorage<S> {
fn is_write_initial(&mut self, key: &StorageKey) -> bool {
self.is_write_initial_internal(key).unwrap()
}
fn load_factory_dep(&mut self, hash: H256) -> Option<Vec<u8>> {
self.load_factory_dep_internal(hash).unwrap()
}
fn read_value(&mut self, key: &StorageKey) -> zksync_types::StorageValue {
self.read_value_internal(key).unwrap()
}
fn get_enumeration_index(&mut self, key: &StorageKey) -> Option<u64> {
self.get_enumeration_index_internal(key)
}
}
impl<S: std::fmt::Debug + ForkSource> ReadStorage for &ForkStorage<S> {
fn read_value(&mut self, key: &StorageKey) -> zksync_types::StorageValue {
self.read_value_internal(key).unwrap()
}
fn is_write_initial(&mut self, key: &StorageKey) -> bool {
self.is_write_initial_internal(key).unwrap()
}
fn load_factory_dep(&mut self, hash: H256) -> Option<Vec<u8>> {
self.load_factory_dep_internal(hash).unwrap()
}
fn get_enumeration_index(&mut self, key: &StorageKey) -> Option<u64> {
self.get_enumeration_index_internal(key)
}
}
impl<S> ForkStorage<S> {
pub fn set_value(&mut self, key: StorageKey, value: zksync_types::StorageValue) {
let mut mutator = self.inner.write().unwrap();
mutator.raw_storage.set_value(key, value)
}
pub fn store_factory_dep(&mut self, hash: H256, bytecode: Vec<u8>) {
let mut mutator = self.inner.write().unwrap();
mutator.raw_storage.store_factory_dep(hash, bytecode)
}
}
pub trait ForkSource {
fn get_fork_url(&self) -> eyre::Result<String>;
fn get_storage_at(
&self,
address: Address,
idx: U256,
block: Option<BlockIdVariant>,
) -> eyre::Result<H256>;
fn get_bytecode_by_hash(&self, hash: H256) -> eyre::Result<Option<Vec<u8>>>;
fn get_transaction_by_hash(&self, hash: H256) -> eyre::Result<Option<Transaction>>;
fn get_transaction_details(&self, hash: H256) -> eyre::Result<Option<TransactionDetails>>;
fn get_raw_block_transactions(
&self,
block_number: L2BlockNumber,
) -> eyre::Result<Vec<zksync_types::Transaction>>;
fn get_block_by_hash(
&self,
hash: H256,
full_transactions: bool,
) -> eyre::Result<Option<Block<TransactionVariant>>>;
fn get_block_by_number(
&self,
block_number: zksync_types::api::BlockNumber,
full_transactions: bool,
) -> eyre::Result<Option<Block<TransactionVariant>>>;
fn get_block_details(&self, miniblock: L2BlockNumber) -> eyre::Result<Option<BlockDetails>>;
fn get_fee_params(&self) -> eyre::Result<FeeParams>;
fn get_block_transaction_count_by_hash(&self, block_hash: H256) -> eyre::Result<Option<U256>>;
fn get_block_transaction_count_by_number(
&self,
block_number: zksync_types::api::BlockNumber,
) -> eyre::Result<Option<U256>>;
fn get_transaction_by_block_hash_and_index(
&self,
block_hash: H256,
index: Index,
) -> eyre::Result<Option<Transaction>>;
fn get_transaction_by_block_number_and_index(
&self,
block_number: BlockNumber,
index: Index,
) -> eyre::Result<Option<Transaction>>;
fn get_bridge_contracts(&self) -> eyre::Result<BridgeAddresses>;
fn get_confirmed_tokens(
&self,
from: u32,
limit: u8,
) -> eyre::Result<Vec<zksync_web3_decl::types::Token>>;
}
pub struct ForkDetails {
pub fork_source: Box<dyn ForkSource + Send + Sync>,
pub l1_block: L1BatchNumber,
pub l2_block: zksync_types::api::Block<zksync_types::api::TransactionVariant>,
pub l2_miniblock: u64,
pub l2_miniblock_hash: H256,
pub block_timestamp: u64,
pub overwrite_chain_id: Option<L2ChainId>,
pub l1_gas_price: u64,
pub l2_fair_gas_price: u64,
pub estimate_gas_price_scale_factor: f64,
pub estimate_gas_scale_factor: f32,
pub fee_params: Option<FeeParams>,
pub cache_config: CacheConfig,
}
const SUPPORTED_VERSIONS: &[ProtocolVersionId] = &[
ProtocolVersionId::Version9,
ProtocolVersionId::Version10,
ProtocolVersionId::Version11,
ProtocolVersionId::Version12,
ProtocolVersionId::Version13,
ProtocolVersionId::Version14,
ProtocolVersionId::Version15,
ProtocolVersionId::Version16,
ProtocolVersionId::Version17,
ProtocolVersionId::Version18,
ProtocolVersionId::Version19,
ProtocolVersionId::Version20,
ProtocolVersionId::Version21,
ProtocolVersionId::Version22,
ProtocolVersionId::Version23,
ProtocolVersionId::Version24,
ProtocolVersionId::Version25,
];
pub fn supported_protocol_versions(version: ProtocolVersionId) -> bool {
SUPPORTED_VERSIONS.contains(&version)
}
pub fn supported_versions_to_string() -> String {
let versions: Vec<String> = SUPPORTED_VERSIONS
.iter()
.map(|v| format!("{:?}", v))
.collect();
versions.join(", ")
}
impl fmt::Debug for ForkDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ForkDetails")
.field("l1_block", &self.l1_block)
.field("l2_block", &self.l2_block)
.field("l2_miniblock", &self.l2_miniblock)
.field("l2_miniblock_hash", &self.l2_miniblock_hash)
.field("block_timestamp", &self.block_timestamp)
.field("overwrite_chain_id", &self.overwrite_chain_id)
.field("l1_gas_price", &self.l1_gas_price)
.field("l2_fair_gas_price", &self.l2_fair_gas_price)
.finish()
}
}
impl ForkDetails {
pub async fn from_network_and_miniblock_and_chain(
network: ForkNetwork,
client: Client<L2>,
miniblock: u64,
chain_id: Option<L2ChainId>,
cache_config: CacheConfig,
) -> Self {
let url = network.to_url();
let block_details = client
.get_block_details(L2BlockNumber(miniblock as u32))
.await
.unwrap()
.unwrap_or_else(|| panic!("Could not find block {:?} in {:?}", miniblock, url));
let root_hash = block_details
.base
.root_hash
.unwrap_or_else(|| panic!("fork block #{} missing root hash", miniblock));
let block = client
.get_block_by_hash(root_hash, true)
.await
.expect("failed retrieving block")
.unwrap_or_else(|| {
panic!(
"Could not find block #{:?} ({:#x}) in {:?}",
miniblock, root_hash, url
)
});
let l1_batch_number = block_details.l1_batch_number;
tracing::info!(
"Creating fork from {:?} L1 block: {:?} L2 block: {:?} with timestamp {:?}, L1 gas price {:?}, L2 fair gas price {:?} and protocol version: {:?}" ,
url, l1_batch_number, miniblock, block_details.base.timestamp, block_details.base.l1_gas_price, block_details.base.l2_fair_gas_price, block_details.protocol_version
);
if !block_details
.protocol_version
.map(supported_protocol_versions)
.unwrap_or(false)
{
panic!(
"This block is using the unsupported protocol version: {:?}. This binary supports versions {}.",
block_details.protocol_version,
supported_versions_to_string()
);
}
let (estimate_gas_price_scale_factor, estimate_gas_scale_factor) =
network.local_gas_scale_factors();
let fee_params = client.get_fee_params().await.ok();
ForkDetails {
fork_source: Box::new(HttpForkSource::new(url.to_owned(), cache_config.clone())),
l1_block: l1_batch_number,
l2_block: block,
block_timestamp: block_details.base.timestamp,
l2_miniblock: miniblock,
l2_miniblock_hash: root_hash,
overwrite_chain_id: chain_id,
l1_gas_price: block_details.base.l1_gas_price,
l2_fair_gas_price: block_details.base.l2_fair_gas_price,
estimate_gas_price_scale_factor,
estimate_gas_scale_factor,
fee_params,
cache_config,
}
}
pub async fn from_network(fork: &str, fork_at: Option<u64>, cache_config: CacheConfig) -> Self {
let (network, client) = Self::fork_network_and_client(fork);
let l2_miniblock = if let Some(fork_at) = fork_at {
fork_at
} else {
client.get_block_number().await.unwrap().as_u64()
};
Self::from_network_and_miniblock_and_chain(
network,
client,
l2_miniblock,
None,
cache_config,
)
.await
}
pub async fn from_network_tx(fork: &str, tx: H256, cache_config: CacheConfig) -> Self {
let (network, client) = Self::fork_network_and_client(fork);
let tx_details = client.get_transaction_by_hash(tx).await.unwrap().unwrap();
let overwrite_chain_id = Some(
L2ChainId::try_from(tx_details.chain_id.as_u64()).unwrap_or_else(|err| {
panic!("erroneous chain id {}: {:?}", tx_details.chain_id, err,)
}),
);
let miniblock_number = L2BlockNumber(tx_details.block_number.unwrap().as_u32());
let l2_miniblock = miniblock_number.saturating_sub(1) as u64;
Self::from_network_and_miniblock_and_chain(
network,
client,
l2_miniblock,
overwrite_chain_id,
cache_config,
)
.await
}
pub fn from_url(
url: String,
fork_at: Option<u64>,
cache_config: CacheConfig,
) -> eyre::Result<Self> {
let parsed_url = SensitiveUrl::from_str(&url)?;
let builder = match Client::http(parsed_url) {
Ok(b) => b,
Err(error) => {
return Err(eyre::Report::msg(error));
}
};
let client = builder.build();
block_on(async move {
let l2_miniblock = if let Some(fork_at) = fork_at {
fork_at
} else {
client.get_block_number().await?.as_u64()
};
Ok(Self::from_network_and_miniblock_and_chain(
ForkNetwork::Other(url),
client,
l2_miniblock,
None,
cache_config,
)
.await)
})
}
pub fn fork_network_and_client(fork: &str) -> (ForkNetwork, Client<L2>) {
let network = match fork {
"mainnet" => ForkNetwork::Mainnet,
"sepolia-testnet" => ForkNetwork::SepoliaTestnet,
"goerli-testnet" => ForkNetwork::GoerliTestnet,
_ => ForkNetwork::Other(fork.to_string()),
};
let url = network.to_url();
let parsed_url = SensitiveUrl::from_str(url)
.unwrap_or_else(|_| panic!("Unable to parse client URL: {}", &url));
let client = Client::http(parsed_url)
.unwrap_or_else(|_| panic!("Unable to create a client for fork: {}", &url))
.build();
(network, client)
}
pub async fn get_earlier_transactions_in_same_block(&self, replay_tx: H256) -> Vec<L2Tx> {
let tx_details = self
.fork_source
.get_transaction_by_hash(replay_tx)
.unwrap()
.unwrap();
let miniblock = L2BlockNumber(tx_details.block_number.unwrap().as_u32());
let block_transactions = self
.fork_source
.get_raw_block_transactions(miniblock)
.unwrap();
let mut tx_to_apply = Vec::new();
for tx in block_transactions {
let h = tx.hash();
let l2_tx: L2Tx = tx.try_into().unwrap();
tx_to_apply.push(l2_tx);
if h == replay_tx {
return tx_to_apply;
}
}
panic!(
"Cound not find tx {:?} in miniblock: {:?}",
replay_tx, miniblock
);
}
}
#[cfg(test)]
mod tests {
use zksync_basic_types::{AccountTreeId, L1BatchNumber, H256};
use zksync_state::ReadStorage;
use zksync_types::{api::TransactionVariant, StorageKey};
use crate::config::cache::CacheConfig;
use crate::config::gas::{
DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
DEFAULT_L2_GAS_PRICE,
};
use crate::{deps::InMemoryStorage, system_contracts, testing};
use super::{ForkDetails, ForkStorage};
#[test]
fn test_initial_writes() {
let account = AccountTreeId::default();
let never_written_key = StorageKey::new(account, H256::from_low_u64_be(1));
let key_with_some_value = StorageKey::new(account, H256::from_low_u64_be(2));
let key_with_value_0 = StorageKey::new(account, H256::from_low_u64_be(3));
let mut in_memory_storage = InMemoryStorage::default();
in_memory_storage.set_value(key_with_some_value, H256::from_low_u64_be(13));
in_memory_storage.set_value(key_with_value_0, H256::from_low_u64_be(0));
let external_storage = testing::ExternalStorage {
raw_storage: in_memory_storage,
};
let options = system_contracts::Options::default();
let fork_details = ForkDetails {
fork_source: Box::new(external_storage),
l1_block: L1BatchNumber(1),
l2_block: zksync_types::api::Block::<TransactionVariant>::default(),
l2_miniblock: 1,
l2_miniblock_hash: H256::zero(),
block_timestamp: 0,
overwrite_chain_id: None,
l1_gas_price: 100,
l2_fair_gas_price: DEFAULT_L2_GAS_PRICE,
estimate_gas_price_scale_factor: DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
estimate_gas_scale_factor: DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
fee_params: None,
cache_config: CacheConfig::None,
};
let mut fork_storage: ForkStorage<testing::ExternalStorage> =
ForkStorage::new(Some(fork_details), &options);
assert!(fork_storage.is_write_initial(&never_written_key));
assert!(!fork_storage.is_write_initial(&key_with_some_value));
assert!(fork_storage.is_write_initial(&key_with_value_0));
fork_storage.set_value(key_with_value_0, H256::zero());
assert!(!fork_storage.is_write_initial(&key_with_value_0));
}
}