diff --git a/Cargo.toml b/Cargo.toml index 8781d1ee6..e7d65c8ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "tests/instances/multiblock_batch", "tests/instances/header", "tests/instances/evm", + "tests/instances/interop", "cycle_marker", "tests/binary_checker", "supporting_crates/delegated_u256", @@ -57,8 +58,8 @@ keywords = ["blockchain", "zksync", "zk", "risc-v"] categories = ["cryptography"] [workspace.dependencies] -zksync_os_evm_errors = { version = "0.0.10", default-features = false } -zksync_os_interface = { version = "0.0.10"} +zksync_os_evm_errors = { git = "https://github.com/matter-labs/zksync-os-interface", branch = "alocascio-prover-input-gen", default-features = false } +zksync_os_interface = { git = "https://github.com/matter-labs/zksync-os-interface", branch = "alocascio-prover-input-gen", version = "0.0.10"} risc_v_simulator = { git = "https://github.com/matter-labs/zksync-airbender", tag = "v0.4.3"} blake2s_u32 = { git = "https://github.com/matter-labs/zksync-airbender", tag = "v0.4.3"} diff --git a/api/Cargo.toml b/api/Cargo.toml index 8b4c7fcd5..86d8d6274 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -19,10 +19,13 @@ risc_v_simulator = { workspace = true } zk_ee = { path = "../zk_ee", default-features = false } crypto = { path = "../crypto", default-features = false } basic_system = { path = "../basic_system", default-features = false } +basic_bootloader = { path = "../basic_bootloader", default-features = false } evm_interpreter = { path = "../evm_interpreter", default-features = false } ruint = { version = "1.12.3", default-features = false, features = ["alloc"] } alloy = { version = "=1", default-features = false, features = ["eip712", "consensus", "rpc-types", "signer-local", "dyn-abi", "network"] } zksync_os_interface = { workspace = true } +alloy-sol-types = "1" + [features] # By default, use the risc_v_simulator with delegation (which is faster). diff --git a/api/src/helpers.rs b/api/src/helpers.rs index 86dd9dbae..0ea1aa27b 100644 --- a/api/src/helpers.rs +++ b/api/src/helpers.rs @@ -2,15 +2,22 @@ use alloy::consensus::{EthereumTxEnvelope, SignableTransaction}; use alloy::consensus::{Signed, TxEnvelope, TypedTransaction}; use alloy::dyn_abi::DynSolValue; use alloy::network::TxSignerSync; +use alloy::primitives::Address; use alloy::primitives::Signature; +use alloy::rlp::{encode, BufMut, Encodable}; use alloy::rpc::types::TransactionRequest; use alloy::signers::local::PrivateKeySigner; +use alloy_sol_types::sol; +use alloy_sol_types::SolCall; +use basic_bootloader::bootloader::constants::BOOTLOADER_FORMAL_ADDRESS; +use basic_bootloader::bootloader::transaction::rlp_encoded::transaction_types::service_tx::SERVICE_TX_TYPE; use basic_system::system_implementation::flat_storage_model::bytecode_padding_len; use basic_system::system_implementation::flat_storage_model::AccountProperties; use forward_system::run::PreimageSource; use ruint::aliases::U256; use std::alloc::Global; use std::ops::Add; +use zk_ee::common_structs::interop_root_storage::InteropRoot as StoredInteropRoot; use zk_ee::execution_environment_type::ExecutionEnvironmentType; use zk_ee::system::EIP7702_DELEGATION_MARKER; use zk_ee::utils::Bytes32; @@ -235,3 +242,102 @@ pub fn sign_and_encode_transaction_request( TypedTransaction::Eip4844(_) => panic!("Unsupported tx type"), } } + +/// Helper wrapper representing the RLP *body* of a service tx: +/// [nonce, gas_limit, to, data] +struct ServiceTxBody<'a> { + nonce: u64, + gas_limit: u64, + to: &'a [u8; 20], + data: &'a [u8], +} + +enum ServiceTxField<'b> { + U64(u64), + Bytes(&'b [u8]), +} + +impl<'b> Encodable for ServiceTxField<'b> { + fn encode(&self, out: &mut dyn BufMut) { + match self { + ServiceTxField::U64(v) => v.encode(out), + ServiceTxField::Bytes(b) => (*b).encode(out), + } + } +} + +impl<'a> Encodable for ServiceTxBody<'a> { + fn encode(&self, out: &mut dyn BufMut) { + let fields = vec![ + ServiceTxField::U64(self.nonce), + ServiceTxField::U64(self.gas_limit), + ServiceTxField::Bytes(self.to.as_slice()), + ServiceTxField::Bytes(self.data), + ]; + + fields.encode(out); + } +} + +/// +/// Encode a service transaction +/// +pub fn encode_service_tx(nonce: u64, gas_limit: u64, to: &[u8; 20], data: &[u8]) -> EncodedTx { + let body = ServiceTxBody { + nonce, + gas_limit, + to, + data, + }; + let rlp_body = encode(&body); + let mut out = Vec::with_capacity(1 + rlp_body.len()); + out.push(SERVICE_TX_TYPE); + out.extend_from_slice(&rlp_body); + let from = Address::from_slice(&BOOTLOADER_FORMAL_ADDRESS.to_be_bytes::<20>()); + EncodedTx::Rlp(out, from) +} + +/// +/// Calldata used by service transactions that import interop roots. +/// +/// Constructs the calldata for: +/// +/// function addInteropRootsInBatch(InteropRoot[] calldata interopRootsInput); +/// +/// where +/// +/// struct InteropRoot { +/// uint256 chainId; +/// uint256 blockOrBatchNumber; +/// bytes32[] sides; +/// } +/// +pub fn encode_interop_root_import_calldata(interop_roots: Vec) -> Vec { + // Declare sol interface + sol! { + struct InteropRoot { + uint256 chainId; + uint256 blockOrBatchNumber; + bytes32[] sides; + } + + function addInteropRootsInBatch(InteropRoot[] calldata interopRootsInput); + } + + // Construct calldata + let interop_roots: Vec = interop_roots + .into_iter() + .map(|r: StoredInteropRoot| { + let root_b256 = alloy::primitives::B256::from_slice(r.root.as_u8_ref()); + InteropRoot { + chainId: r.chain_id, + blockOrBatchNumber: r.block_or_batch_number, + sides: vec![root_b256], + } + }) + .collect(); + addInteropRootsInBatchCall { + interopRootsInput: interop_roots, + } + .abi_encode() +} diff --git a/api/src/lib.rs b/api/src/lib.rs index 913ebd84c..13aecf3ae 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,54 +1,5 @@ #![feature(allocator_api)] -use std::{path::PathBuf, str::FromStr}; - -use forward_system::run::{ - test_impl::{InMemoryPreimageSource, InMemoryTree}, - BlockContext, StorageCommitment, -}; -use oracle_provider::ReadWitnessSource; -use zksync_os_interface::traits::TxListSource; -pub mod helpers; - /// Runs the batch, and returns the output (that contains gas usage, transaction status etc.). -pub use forward_system::run::run_block; -use zk_ee::common_structs::da_commitment_scheme::DACommitmentScheme; -use zk_ee::common_structs::ProofData; - -/// Runs a block in RISC-V - using zksync_os binary - and returns the -/// witness that can be passed to the prover subsystem. -pub fn run_block_generate_witness( - block_context: BlockContext, - tree: InMemoryTree, - preimage_source: InMemoryPreimageSource, - tx_source: TxListSource, - proof_data: ProofData, - da_commitment_scheme: DACommitmentScheme, - zksync_os_bin_path: &str, -) -> Vec { - use forward_system::run::*; - - let oracle = make_oracle_for_proofs_and_dumps_for_init_data( - block_context, - tree, - preimage_source, - tx_source, - Some(proof_data), - Some(da_commitment_scheme), - false, - ); - - // We'll wrap the source, to collect all the reads. - let copy_source = ReadWitnessSource::new(oracle); - - let items = copy_source.get_read_items(); - // By default - enable diagnostics is false (which makes the test run faster). - let path = PathBuf::from_str(zksync_os_bin_path).unwrap(); - let output = zksync_os_runner::run(path, None, 1 << 36, copy_source); - - // We return 0s in case of failure. - assert_ne!(output, [0u32; 8]); - - let result = items.borrow().clone(); - result -} +pub use forward_system::run::{generate_proof_input, run_block}; +pub mod helpers; diff --git a/basic_bootloader/src/bootloader/constants.rs b/basic_bootloader/src/bootloader/constants.rs index 6931396b9..fe34604de 100644 --- a/basic_bootloader/src/bootloader/constants.rs +++ b/basic_bootloader/src/bootloader/constants.rs @@ -4,6 +4,9 @@ use ruint::aliases::{B160, U256}; pub const SPECIAL_ADDRESS_SPACE_BOUND: u64 = 0x010000; pub const SPECIAL_ADDRESS_TO_WASM_DEPLOY: B160 = B160::from_limbs([0x9000, 0, 0]); +/// Bootloader's formal address for system-level operations +pub const BOOTLOADER_FORMAL_ADDRESS: B160 = B160::from_limbs([0x8001, 0, 0]); + pub const MAX_TX_LEN_BYTES: usize = 1 << 23; pub const MAX_TX_LEN_WORDS: usize = MAX_TX_LEN_BYTES / core::mem::size_of::(); diff --git a/basic_bootloader/src/bootloader/mod.rs b/basic_bootloader/src/bootloader/mod.rs index b4d47a4cf..bc33c5aad 100644 --- a/basic_bootloader/src/bootloader/mod.rs +++ b/basic_bootloader/src/bootloader/mod.rs @@ -3,6 +3,7 @@ use result_keeper::ResultKeeperExt; use ruint::aliases::*; use zk_ee::common_structs::MAX_NUMBER_OF_LOGS; use zk_ee::execution_environment_type::ExecutionEnvironmentType; +use zk_ee::system::errors::internal::InternalError; use zk_ee::system::tracer::Tracer; use zk_ee::system::{EthereumLikeTypes, System, SystemTypes}; @@ -35,7 +36,7 @@ use crate::bootloader::runner::RunnerMemoryBuffers; use crate::bootloader::transaction_flow::{ BasicTransactionFlow, ExecutionOutput, ExecutionResult, TxProcessingResult, }; -use system_hooks::HooksStorage; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::system::*; use zk_ee::utils::*; @@ -125,19 +126,23 @@ where let mut system_functions = HooksStorage::new_in(system.get_allocator()); - system_functions.add_precompiles(); + system_hooks::add_precompiles(&mut system_functions); #[cfg(not(feature = "disable_system_contracts"))] { - system_functions.add_l1_messenger(); - system_functions.add_l2_base_token(); - system_functions.add_contract_deployer(); + system_hooks::add_l1_messenger(&mut system_functions); + system_hooks::add_l2_base_token(&mut system_functions); + system_hooks::add_contract_deployer(&mut system_functions); + system_hooks::add_interop_root_reporter(&mut system_functions); } let mut tx_rolling_hash = [0u8; 32]; let mut l1_to_l2_txs_hasher = crypto::blake2s::Blake2s256::new(); let mut first_tx = true; + // Service blocks are blocks that only contain service transactions. + // Service transactions can only be included in service blocks. + let mut is_service_block = false; let mut upgrade_tx_hash = Bytes32::zero(); let mut block_gas_used = 0; let mut block_computational_native_used = 0; @@ -217,6 +222,14 @@ where "Tx execution result = {:?}\n", &tx_processing_result, )); + + // Check for service block invariants + Self::check_for_service_block_invariants( + &mut is_service_block, + first_tx, + tx_processing_result.is_service_tx, + )?; + // Do not update the accumulators yet, we may need to revert the transaction let next_block_gas_used = block_gas_used + tx_processing_result.gas_used; @@ -400,4 +413,31 @@ where } } } + + /// Check the service block invariants: + /// 1. If the first tx is a service tx, then the block is a service block + /// 2. Service transactions can only be processed in service blocks + /// 3. Non-service transactions cannot be processed in service blocks + fn check_for_service_block_invariants( + is_service_block: &mut bool, + is_first_tx: bool, + is_service_tx: bool, + ) -> Result<(), InternalError> { + // 1. If the first tx is a service tx, then the block is a service block + if is_first_tx && is_service_tx { + *is_service_block = true; + } + if *is_service_block { + if !is_service_tx { + // 3. Non-service transactions cannot be processed in service blocks + return Err(internal_error!("Non-service tx in service block")); + } + } else { + // 2. Service transactions can only be processed in service blocks + if is_service_tx { + return Err(internal_error!("Service tx in non-service block")); + } + } + Ok(()) + } } diff --git a/basic_bootloader/src/bootloader/process_transaction.rs b/basic_bootloader/src/bootloader/process_transaction.rs index 43f5ddc2d..8ffbbe2a1 100644 --- a/basic_bootloader/src/bootloader/process_transaction.rs +++ b/basic_bootloader/src/bootloader/process_transaction.rs @@ -23,8 +23,8 @@ use gas_helpers::check_enough_resources_for_pubdata; use gas_helpers::get_resources_to_charge_for_pubdata; use gas_helpers::ResourcesForTx; use metadata::zk_metadata::TxLevelMetadata; -use system_hooks::HooksStorage; use transaction::charge_keccak; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::interface_error; use zk_ee::internal_error; use zk_ee::system::errors::cascade::CascadedError; @@ -396,6 +396,7 @@ where tx_hash, is_l1_tx: is_priority_op, is_upgrade_tx: !is_priority_op, + is_service_tx: false, gas_used, gas_refunded: evm_refund, computational_native_used, @@ -767,11 +768,14 @@ where .as_str(), ); + let is_service_tx = transaction.is_service(); + Ok(TxProcessingResult { result: execution_result, tx_hash, is_l1_tx: false, is_upgrade_tx: false, + is_service_tx, gas_used, gas_refunded: evm_refund, computational_native_used, diff --git a/basic_bootloader/src/bootloader/run_single_interaction.rs b/basic_bootloader/src/bootloader/run_single_interaction.rs index b03caa630..648309798 100644 --- a/basic_bootloader/src/bootloader/run_single_interaction.rs +++ b/basic_bootloader/src/bootloader/run_single_interaction.rs @@ -1,7 +1,7 @@ use crate::bootloader::errors::BootloaderInterfaceError; use crate::bootloader::runner::{run_till_completion, RunnerMemoryBuffers}; use errors::BootloaderSubsystemError; -use system_hooks::HooksStorage; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::system::errors::subsystem::SubsystemError; use zk_ee::system::errors::{runtime::RuntimeError, system::SystemError}; use zk_ee::system::CallModifier; diff --git a/basic_bootloader/src/bootloader/runner.rs b/basic_bootloader/src/bootloader/runner.rs index 978891c14..389ee99cb 100644 --- a/basic_bootloader/src/bootloader/runner.rs +++ b/basic_bootloader/src/bootloader/runner.rs @@ -6,7 +6,7 @@ use core::fmt::Write; use core::mem::MaybeUninit; use errors::internal::InternalError; use ruint::aliases::B160; -use system_hooks::*; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::common_structs::CalleeAccountProperties; use zk_ee::error_ctx; use zk_ee::execution_environment_type::ExecutionEnvironmentType; @@ -414,7 +414,13 @@ impl<'external, S: EthereumLikeTypes> ExecutionContext<'_, 'external, S> { let new_ee_type = new_vm.ee_type(); let mut preemption = new_vm - .start_executing_frame(self.system, external_call_launch_params, heap, tracer) + .start_executing_frame( + self.system, + self.hooks, + external_call_launch_params, + heap, + tracer, + ) .map_err(wrap_error!())?; // Execute until we get `End` preemption point @@ -446,7 +452,13 @@ impl<'external, S: EthereumLikeTypes> ExecutionContext<'_, 'external, S> { self.callstack_height -= 1; preemption = new_vm - .continue_after_preemption(self.system, resources_returned, result, tracer) + .continue_after_preemption( + self.system, + self.hooks, + resources_returned, + result, + tracer, + ) .map_err(wrap_error!())?; } ExecutionEnvironmentPreemptionPoint::End(CompletedExecution { diff --git a/basic_bootloader/src/bootloader/supported_ees.rs b/basic_bootloader/src/bootloader/supported_ees.rs index 7476ad463..7669375b4 100644 --- a/basic_bootloader/src/bootloader/supported_ees.rs +++ b/basic_bootloader/src/bootloader/supported_ees.rs @@ -1,7 +1,7 @@ use crate::bootloader::EVM_EE_BYTE; use errors::{EESubsystemError, InterfaceError}; use zk_ee::{ - common_structs::CalleeAccountProperties, + common_structs::{system_hooks::HooksStorage, CalleeAccountProperties}, execution_environment_type::ExecutionEnvironmentType, interface_error, memory::slice_vec::SliceVec, @@ -97,6 +97,7 @@ impl<'ee, S: EthereumLikeTypes> SupportedEEVMState<'ee, S> { pub fn start_executing_frame<'a, 'i: 'ee, 'h: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, initial_state: ExecutionEnvironmentLaunchParams<'i, S>, heap: SliceVec<'h, u8>, tracer: &mut impl Tracer, @@ -106,7 +107,7 @@ impl<'ee, S: EthereumLikeTypes> SupportedEEVMState<'ee, S> { { match self { Self::EVM(evm_frame) => evm_frame - .start_executing_frame(system, initial_state, heap, tracer) + .start_executing_frame(system, hooks, initial_state, heap, tracer) .map_err(wrap_error!()), } } @@ -114,6 +115,7 @@ impl<'ee, S: EthereumLikeTypes> SupportedEEVMState<'ee, S> { pub fn continue_after_preemption<'a, 'res: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, returned_resources: S::Resources, call_result: CallResult<'res, S>, tracer: &mut impl Tracer, @@ -123,7 +125,7 @@ impl<'ee, S: EthereumLikeTypes> SupportedEEVMState<'ee, S> { { match self { Self::EVM(evm_frame) => evm_frame - .continue_after_preemption(system, returned_resources, call_result, tracer) + .continue_after_preemption(system, hooks, returned_resources, call_result, tracer) .map_err(wrap_error!()), } } diff --git a/basic_bootloader/src/bootloader/transaction/abi_encoded/mod.rs b/basic_bootloader/src/bootloader/transaction/abi_encoded/mod.rs index 3c9479638..f027224d0 100644 --- a/basic_bootloader/src/bootloader/transaction/abi_encoded/mod.rs +++ b/basic_bootloader/src/bootloader/transaction/abi_encoded/mod.rs @@ -300,7 +300,7 @@ impl AbiEncodedTransaction { } } - pub fn sig_parity_r_s<'a>(&'a self) -> (bool, &'a [u8], &'a [u8]) { + pub fn sig_parity_r_s<'a>(&'a self) -> Option<(bool, &'a [u8], &'a [u8])> { let signature = unsafe { self.underlying_buffer .as_slice() @@ -312,7 +312,7 @@ impl AbiEncodedTransaction { // Pre checked, but just in case assert!(*v == 27 || *v == 28); let parity = v - 27 == 1; - (parity, r, s) + Some((parity, r, s)) } /// diff --git a/basic_bootloader/src/bootloader/transaction/mod.rs b/basic_bootloader/src/bootloader/transaction/mod.rs index 839623b78..ee778308e 100644 --- a/basic_bootloader/src/bootloader/transaction/mod.rs +++ b/basic_bootloader/src/bootloader/transaction/mod.rs @@ -101,6 +101,13 @@ impl Transaction { } } + pub fn is_service(&self) -> bool { + match self { + Self::Abi(_) => false, + Self::Rlp(tx) => tx.is_service(), + } + } + /// Returns the transaction nonce as U256. pub fn nonce(&self) -> U256 { match self { @@ -195,8 +202,9 @@ impl Transaction { } } - /// Returns the signature as `(y_parity, r, s)` borrowed from the underlying tx. - pub fn sig_parity_r_s<'a>(&'a self) -> (bool, &'a [u8], &'a [u8]) { + /// Returns the signature as `Some((y_parity, r, s))` borrowed from the underlying tx. + /// Returns None for transactions with no signature (service txs). + pub fn sig_parity_r_s<'a>(&'a self) -> Option<(bool, &'a [u8], &'a [u8])> { match self { Self::Rlp(tx) => tx.sig_parity_r_s(), Self::Abi(tx) => tx.sig_parity_r_s(), diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/mod.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/mod.rs index 027efce98..63338fb98 100644 --- a/basic_bootloader/src/bootloader/transaction/rlp_encoded/mod.rs +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/mod.rs @@ -1,5 +1,9 @@ use crypto::MiniDigest; +use rlp::minimal_rlp_parser::RlpListDecode; +use ruint::aliases::B160; +use transaction_types::service_tx::ServiceTx; +use crate::bootloader::constants::BOOTLOADER_FORMAL_ADDRESS; use crate::bootloader::errors::InvalidTransaction; use crate::bootloader::transaction::rlp_encoded::rlp::minimal_rlp_parser::Rlp; use crate::bootloader::transaction::rlp_encoded::transaction_types::legacy_tx::{ @@ -17,7 +21,7 @@ use zk_ee::utils::Bytes32; mod eip_2718_tx_envelope; mod rlp; mod transaction; -mod transaction_types; +pub mod transaction_types; pub use self::transaction::RlpEncodedTransaction; pub use transaction_types::eip_2930_tx::AccessListForAddress; @@ -33,13 +37,16 @@ pub(crate) enum RlpEncodedTxInner<'a> { EIP2930(EIP2930Tx<'a>, EIP2718SignatureData<'a>), EIP1559(EIP1559Tx<'a>, EIP2718SignatureData<'a>), EIP7702(EIP7702Tx<'a>, EIP2718SignatureData<'a>), + Service(ServiceTx<'a>), } impl<'a> RlpEncodedTxInner<'a> { // NOTE: u64 chain ID allows to avoid handling some overflows below + // For service txs, the signed hash is Bytes32::ZERO. pub(crate) fn parse_and_compute_signed_hash( input: &'a [u8], expected_chain_id: u64, + from: &B160, ) -> Result<(Self, Bytes32), TxError> { // Try to read a leading single-byte item as the typed marker. // For typed txs, this is a bare byte (1/2/4) followed by an RLP list. @@ -78,6 +85,20 @@ impl<'a> RlpEncodedTxInner<'a> { } Ok((Self::EIP7702(tx, sig_data), sig_hash)) } + ServiceTx::TX_TYPE => { + // Check that from provided by oracle is BOOTLOADER_FORMAL_ADDRESS + if from != &BOOTLOADER_FORMAL_ADDRESS { + return Err(InvalidTransaction::IncorrectFrom { + tx: *from, + recovered: BOOTLOADER_FORMAL_ADDRESS, + } + .into()); + } + + let tx = ServiceTx::decode_list_from(&mut r)?; + + Ok((Self::Service(tx), Bytes32::ZERO)) + } _ => Err(InvalidTransaction::InvalidEncoding.into()), } } else { @@ -170,4 +191,12 @@ mod test { tx.hash_for_signature_verification().as_u8_array() ) } + + #[test] + fn test_on_random_service() { + let input = hex::decode("7df902112a830f4240941111111111111111111111111111111111111111b901f4000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3").unwrap(); + let buffer = UsizeAlignedByteBox::::from_slice_in(&input, Global); + let _tx = + RlpEncodedTransaction::parse_from_buffer(buffer, 1, BOOTLOADER_FORMAL_ADDRESS).unwrap(); + } } diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs index 4802ad396..83c686cb5 100644 --- a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs @@ -60,7 +60,9 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::parse_and_compute_signed_hash( unsafe { core::mem::transmute::<&[u8], &[u8]>(buffer.as_slice()) }, expected_chain_id, + &from, )?; + Ok(Self { buffer, inner, @@ -71,6 +73,10 @@ impl RlpEncodedTransaction { }) } + pub fn is_service(&self) -> bool { + matches!(&self.inner, RlpEncodedTxInner::Service(_)) + } + pub fn chain_id(&self) -> Option { match &self.inner { RlpEncodedTxInner::Legacy(_, _) => None, @@ -86,6 +92,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => tx.nonce, RlpEncodedTxInner::EIP1559(tx, _) => tx.nonce, RlpEncodedTxInner::EIP7702(tx, _) => tx.nonce, + RlpEncodedTxInner::Service(tx) => tx.nonce, } } @@ -97,6 +104,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => &tx.value, RlpEncodedTxInner::EIP1559(tx, _) => &tx.value, RlpEncodedTxInner::EIP7702(tx, _) => &tx.value, + RlpEncodedTxInner::Service(_) => &U256::ZERO, } } @@ -126,6 +134,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(_, _) => 1, RlpEncodedTxInner::EIP1559(_, _) => 2, RlpEncodedTxInner::EIP7702(_, _) => 4, + RlpEncodedTxInner::Service(_) => ServiceTx::TX_TYPE, } } @@ -137,6 +146,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => tx.data, RlpEncodedTxInner::EIP1559(tx, _) => tx.data, RlpEncodedTxInner::EIP7702(tx, _) => tx.data, + RlpEncodedTxInner::Service(tx) => tx.data, } } @@ -146,6 +156,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => Some(tx.access_list), RlpEncodedTxInner::EIP1559(tx, _) => Some(tx.access_list), RlpEncodedTxInner::EIP7702(tx, _) => Some(tx.access_list), + RlpEncodedTxInner::Service(_) => None, } } @@ -157,6 +168,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => Some(tx.access_list.iter()), RlpEncodedTxInner::EIP1559(tx, _) => Some(tx.access_list.iter()), RlpEncodedTxInner::EIP7702(tx, _) => Some(tx.access_list.iter()), + RlpEncodedTxInner::Service(_) => None, } } @@ -180,20 +192,21 @@ impl RlpEncodedTransaction { &self.from } - pub fn sig_parity_r_s<'a>(&'a self) -> (bool, &'a [u8], &'a [u8]) { + pub fn sig_parity_r_s<'a>(&'a self) -> Option<(bool, &'a [u8], &'a [u8])> { match &self.inner { RlpEncodedTxInner::Legacy(_, sig) => { let parity = sig.v - U256::from(27) == U256::ONE; - (parity, sig.r, sig.s) // prechecked + Some((parity, sig.r, sig.s)) // prechecked } RlpEncodedTxInner::LegacyWithEIP155(_, sig) => { let chain_id = self.chain_id; let parity = sig.v - U256::from(35) - (U256::from(chain_id) * U256::from(2)); // no underflows - (parity == 1, sig.r, sig.s) + Some((parity == 1, sig.r, sig.s)) } - RlpEncodedTxInner::EIP2930(_, sig) => (sig.y_parity, sig.r, sig.s), - RlpEncodedTxInner::EIP1559(_, sig) => (sig.y_parity, sig.r, sig.s), - RlpEncodedTxInner::EIP7702(_, sig) => (sig.y_parity, sig.r, sig.s), + RlpEncodedTxInner::EIP2930(_, sig) => Some((sig.y_parity, sig.r, sig.s)), + RlpEncodedTxInner::EIP1559(_, sig) => Some((sig.y_parity, sig.r, sig.s)), + RlpEncodedTxInner::EIP7702(_, sig) => Some((sig.y_parity, sig.r, sig.s)), + RlpEncodedTxInner::Service(_) => None, } } @@ -212,6 +225,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => tx.gas_limit, RlpEncodedTxInner::EIP1559(tx, _) => tx.gas_limit, RlpEncodedTxInner::EIP7702(tx, _) => tx.gas_limit, + RlpEncodedTxInner::Service(tx) => tx.gas_limit, } } @@ -230,6 +244,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => map_fn(tx.to), RlpEncodedTxInner::EIP1559(tx, _) => map_fn(tx.to), RlpEncodedTxInner::EIP7702(tx, _) => Some(B160::from_be_bytes(*tx.to)), + RlpEncodedTxInner::Service(tx) => Some(B160::from_be_bytes(*tx.to)), } } @@ -241,6 +256,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(tx, _) => &tx.gas_price, RlpEncodedTxInner::EIP1559(tx, _) => &tx.max_fee_per_gas, RlpEncodedTxInner::EIP7702(tx, _) => &tx.max_fee_per_gas, + RlpEncodedTxInner::Service(_) => &U256::ZERO, } } @@ -250,6 +266,7 @@ impl RlpEncodedTransaction { RlpEncodedTxInner::EIP2930(_, _) => None, RlpEncodedTxInner::EIP1559(tx, _) => Some(&tx.max_priority_fee_per_gas), RlpEncodedTxInner::EIP7702(tx, _) => Some(&tx.max_priority_fee_per_gas), + RlpEncodedTxInner::Service(_) => None, } } diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/mod.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/mod.rs index 77f674102..a337b8a5a 100644 --- a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/mod.rs +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/mod.rs @@ -2,6 +2,7 @@ pub mod eip_1559_tx; pub mod eip_2930_tx; pub mod eip_7702_tx; pub mod legacy_tx; +pub mod service_tx; pub trait EthereumTxType { const TX_TYPE: u8; diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/service_tx.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/service_tx.rs new file mode 100644 index 000000000..48eac48ed --- /dev/null +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction_types/service_tx.rs @@ -0,0 +1,137 @@ +use crate::bootloader::errors::InvalidTransaction; +use crate::bootloader::transaction::rlp_encoded::rlp::minimal_rlp_parser::{Rlp, RlpListDecode}; +use crate::bootloader::transaction::rlp_encoded::transaction_types::EthereumTxType; +use ruint::aliases::B160; +use system_hooks::addresses_constants::L2_INTEROP_ROOT_STORAGE_ADDRESS; + +/// ZKsync OS service (type 0x7d) transaction . +/// Used for system operations, such as importing interop roots. +/// Can only be executed in service blocks, i.e. blocks with only service +/// transactions. +/// They have no signature, as they are added directly by the operator. +/// +#[derive(Clone, Copy, Debug)] +pub(crate) struct ServiceTx<'a> { + pub(crate) nonce: u64, + pub(crate) gas_limit: u64, + pub(crate) to: &'a [u8; 20], // NOTE: has to be one of the addresses in SERVICE_DESTINATION_WHITELIST + pub(crate) data: &'a [u8], +} + +const SERVICE_DESTINATION_WHITELIST: &[B160] = &[L2_INTEROP_ROOT_STORAGE_ADDRESS]; + +pub const SERVICE_TX_TYPE: u8 = 0x7d; + +impl<'a> EthereumTxType for ServiceTx<'a> { + const TX_TYPE: u8 = SERVICE_TX_TYPE; +} + +impl<'a> RlpListDecode<'a> for ServiceTx<'a> { + /// Decode the 4-field list body: + /// [nonce, gas_limit, destination, data] + fn decode_list_body(r: &mut Rlp<'a>) -> Result { + let nonce = r.u64()?; + let gas_limit = r.u64()?; + + let to_slice = r.bytes()?; + if to_slice.len() != 20 { + return Err(InvalidTransaction::InvalidStructure); + } + let to: &'a [u8; 20] = to_slice + .try_into() + .map_err(|_| InvalidTransaction::InvalidStructure)?; + + let to_b160 = B160::from_be_bytes(*to); + + // Validate whitelist + if !SERVICE_DESTINATION_WHITELIST.contains(&to_b160) { + return Err(InvalidTransaction::InvalidStructure); + } + + let data = r.bytes()?; + Ok(Self { + nonce, + gas_limit, + to, + data, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bootloader::transaction::rlp_encoded::rlp::minimal_rlp_parser::RlpListDecode; + use alloy_rlp::Encodable; + + /// Helper to RLP-encode the 4-field ServiceTx body: + /// [nonce, gas_limit, destination, data] + fn encode_service_tx(nonce: u64, gas_limit: u64, to: &[u8], data: &[u8]) -> Vec { + let mut buf = Vec::new(); + + // Temporary placeholder for the list header; we’ll fix it once we know the payload length. + buf.push(0xc0); // dummy + + let start = buf.len(); + + nonce.encode(&mut buf); + gas_limit.encode(&mut buf); + to.encode(&mut buf); + data.encode(&mut buf); + + let payload_len = buf.len() - start; + + // Short list form is enough for these tiny tests. + assert!(payload_len < 56, "test list unexpectedly large"); + buf[0] = 0xc0 + payload_len as u8; + buf + } + + #[test] + fn empty_to_fails() { + let nonce = 0; + let gas_limit = 21_000; + let to: &[u8] = &[]; // RLP empty string -> len() == 0 + let data: &[u8] = &[0x01, 0x02]; + + let bytes = encode_service_tx(nonce, gas_limit, to, data); + + let res = ServiceTx::decode_list_full(&bytes); + assert!(matches!(res, Err(InvalidTransaction::InvalidStructure))); + } + + #[test] + fn to_outside_whitelist_fails() { + let nonce = 1; + let gas_limit = 50_000; + + // Some arbitrary 20-byte address that is not in the whitelist. + let to_bytes: [u8; 20] = [0x11u8; 20]; + + let data: &[u8] = &[]; + + let bytes = encode_service_tx(nonce, gas_limit, &to_bytes, data); + + let res = ServiceTx::decode_list_full(&bytes); + assert!(matches!(res, Err(InvalidTransaction::InvalidStructure))); + } + + #[test] + fn to_in_whitelist_parses() { + let nonce = 7; + let gas_limit = 42_000; + + let to_bytes: [u8; 20] = L2_INTEROP_ROOT_STORAGE_ADDRESS.to_be_bytes(); + let data: Vec = vec![0xde, 0xad, 0xbe, 0xef]; + + let bytes = encode_service_tx(nonce, gas_limit, &to_bytes, &data); + + let tx: ServiceTx<'_> = + ServiceTx::decode_list_full(&bytes).expect("whitelisted address must decode"); + + assert_eq!(tx.nonce, nonce); + assert_eq!(tx.gas_limit, gas_limit); + assert_eq!(tx.to, to_bytes.as_slice()); + assert_eq!(tx.data, data.as_slice()); + } +} diff --git a/basic_bootloader/src/bootloader/transaction_flow/mod.rs b/basic_bootloader/src/bootloader/transaction_flow/mod.rs index 2e7488d7e..cfeefd66c 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/mod.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/mod.rs @@ -8,7 +8,7 @@ use crate::bootloader::errors::TxError; use crate::bootloader::runner::RunnerMemoryBuffers; use crate::bootloader::transaction::Transaction; use ruint::aliases::{B160, U256}; -use system_hooks::HooksStorage; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::execution_environment_type::ExecutionEnvironmentType; use zk_ee::system::tracer::Tracer; use zk_ee::system::EthereumLikeTypes; @@ -72,6 +72,7 @@ pub struct TxProcessingResult<'a> { pub tx_hash: Bytes32, pub is_l1_tx: bool, pub is_upgrade_tx: bool, + pub is_service_tx: bool, pub gas_used: u64, pub gas_refunded: u64, pub computational_native_used: u64, diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs index e67bfe2ab..a6e125168 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs @@ -17,7 +17,7 @@ use core::fmt::Write; use crypto::secp256k1::SECP256K1N_HALF; use evm_interpreter::{ERGS_PER_GAS, MAX_INITCODE_SIZE}; use ruint::aliases::{B160, U256}; -use system_hooks::HooksStorage; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::execution_environment_type::ExecutionEnvironmentType; use zk_ee::memory::ArrayBuilder; use zk_ee::system::errors::interface::InterfaceError; @@ -92,52 +92,55 @@ where Err(SystemError::LeafDefect(e)) => return Err(TxError::Internal(e.into())), } - // Even if we don't validate a signature, we still need to charge for ecrecover for equivalent behavior - if !Config::VALIDATE_EOA_SIGNATURE | Config::SIMULATION { - resources.charge(&Resources::from_ergs_and_native( - ECRECOVER_COST_ERGS, - <::Resources as Resources>::Native::from_computational( - ECRECOVER_NATIVE_COST, - ), - ))?; - } else { - let (parity, r, s) = transaction.sig_parity_r_s(); - if U256::from_be_slice(s) > U256::from_be_bytes(SECP256K1N_HALF) { - return Err(InvalidTransaction::MalleableSignature.into()); - } - - let mut ecrecover_input = [0u8; 128]; - ecrecover_input[0..32].copy_from_slice(suggested_signed_hash.as_u8_array_ref()); - ecrecover_input[63] = (parity as u8) + 27; - ecrecover_input[64..96][(32 - r.len())..].copy_from_slice(r); - ecrecover_input[96..128][(32 - s.len())..].copy_from_slice(s); - - let mut ecrecover_output = ArrayBuilder::default(); - S::SystemFunctions::secp256k1_ec_recover( - ecrecover_input.as_slice(), - &mut ecrecover_output, - resources, - system.get_allocator(), - ) - .map_err(SystemError::from)?; + // Only service transactions have no signature, + // we don't even charge gas/native related to ecrecover for them. + if let Some((parity, r, s)) = transaction.sig_parity_r_s() { + // Even if we don't validate a signature, we still need to charge for ecrecover for equivalent behavior + if !Config::VALIDATE_EOA_SIGNATURE | Config::SIMULATION { + resources.charge(&Resources::from_ergs_and_native( + ECRECOVER_COST_ERGS, + <::Resources as Resources>::Native::from_computational( + ECRECOVER_NATIVE_COST, + ), + ))?; + } else { + if U256::from_be_slice(s) > U256::from_be_bytes(SECP256K1N_HALF) { + return Err(InvalidTransaction::MalleableSignature.into()); + } - if ecrecover_output.is_empty() { - return Err(InvalidTransaction::IncorrectFrom { - recovered: B160::ZERO, - tx: *from, + let mut ecrecover_input = [0u8; 128]; + ecrecover_input[0..32].copy_from_slice(suggested_signed_hash.as_u8_array_ref()); + ecrecover_input[63] = (parity as u8) + 27; + ecrecover_input[64..96][(32 - r.len())..].copy_from_slice(r); + ecrecover_input[96..128][(32 - s.len())..].copy_from_slice(s); + + let mut ecrecover_output = ArrayBuilder::default(); + S::SystemFunctions::secp256k1_ec_recover( + ecrecover_input.as_slice(), + &mut ecrecover_output, + resources, + system.get_allocator(), + ) + .map_err(SystemError::from)?; + + if ecrecover_output.is_empty() { + return Err(InvalidTransaction::IncorrectFrom { + recovered: B160::ZERO, + tx: *from, + } + .into()); } - .into()); - } - let recovered_from = B160::try_from_be_slice(&ecrecover_output.build()[12..]) - .ok_or(internal_error!("Invalid ecrecover return value"))?; + let recovered_from = B160::try_from_be_slice(&ecrecover_output.build()[12..]) + .ok_or(internal_error!("Invalid ecrecover return value"))?; - if &recovered_from != from { - return Err(InvalidTransaction::IncorrectFrom { - recovered: recovered_from, - tx: *from, + if &recovered_from != from { + return Err(InvalidTransaction::IncorrectFrom { + recovered: recovered_from, + tx: *from, + } + .into()); } - .into()); } } diff --git a/basic_system/src/system_functions/keccak256.rs b/basic_system/src/system_functions/keccak256.rs index 70daf1a98..980401418 100644 --- a/basic_system/src/system_functions/keccak256.rs +++ b/basic_system/src/system_functions/keccak256.rs @@ -28,11 +28,14 @@ impl SystemFunction for Keccak256Impl { } } +pub fn keccak256_native_cost_u64(len: usize) -> u64 { + let rounds = core::cmp::max(1, len.div_ceil(KECCAK256_CHUNK_SIZE)); + (rounds as u64) * KECCAK256_ROUND_NATIVE_COST + KECCAK256_BASE_NATIVE_COST +} + pub fn keccak256_native_cost(len: usize) -> R::Native { use zk_ee::system::Computational; - let rounds = core::cmp::max(1, len.div_ceil(KECCAK256_CHUNK_SIZE)); - let native_cost = (rounds as u64) * KECCAK256_ROUND_NATIVE_COST + KECCAK256_BASE_NATIVE_COST; - R::Native::from_computational(native_cost) + R::Native::from_computational(keccak256_native_cost_u64(len)) } fn keccak256_as_system_function_inner, R: Resources>( diff --git a/basic_system/src/system_functions/mod.rs b/basic_system/src/system_functions/mod.rs index eb5d6574e..a1d150328 100644 --- a/basic_system/src/system_functions/mod.rs +++ b/basic_system/src/system_functions/mod.rs @@ -28,10 +28,12 @@ fn bytereverse(input: &mut [u8]) { /// /// No std system functions implementations. /// All of them are following EVM specs(for precompiles and keccak opcode). +/// USE_ADVICE const parameter affects only the forward run, as advice +/// is always used for proving one. /// -pub struct NoStdSystemFunctions; +pub struct NoStdSystemFunctions; -impl SystemFunctions for NoStdSystemFunctions { +impl SystemFunctions for NoStdSystemFunctions { type Keccak256 = keccak256::Keccak256Impl; type Sha256 = sha256::Sha256Impl; type Secp256k1ECRecover = ecrecover::EcRecoverImpl; @@ -47,6 +49,8 @@ impl SystemFunctions for NoStdSystemFunctions { type PointEvaluation = point_evaluation::PointEvaluationImpl; } -impl SystemFunctionsExt for NoStdSystemFunctions { - type ModExp = modexp::ModExpImpl; +impl SystemFunctionsExt + for NoStdSystemFunctions +{ + type ModExp = modexp::ModExpImpl; } diff --git a/basic_system/src/system_functions/modexp/delegation/bigint.rs b/basic_system/src/system_functions/modexp/advice/bigint.rs similarity index 90% rename from basic_system/src/system_functions/modexp/delegation/bigint.rs rename to basic_system/src/system_functions/modexp/advice/bigint.rs index 4b2b9b33d..540da39cf 100644 --- a/basic_system/src/system_functions/modexp/delegation/bigint.rs +++ b/basic_system/src/system_functions/modexp/advice/bigint.rs @@ -1,15 +1,15 @@ // Representation of big integers using primitives that are friendly for our delegations extern crate alloc; -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] -use super::super::{ModExpAdviceParams, MODEXP_ADVICE_QUERY_ID}; +use super::super::MODEXP_ADVICE_QUERY_ID; use super::u256::*; +use crate::system_functions::modexp::ModExpAdviceParams64; use alloc::vec::Vec; use core::alloc::Allocator; use core::fmt::Debug; use core::mem::MaybeUninit; use crypto::{bigint_op_delegation_raw, bigint_op_delegation_with_carry_bit_raw, BigIntOps}; -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] +use ruint::aliases::U256; use zk_ee::oracle::IOOracle; // There is a small choice to make - either we do exponentiation walking as via LE or BE exponent. @@ -812,32 +812,32 @@ pub(crate) mod naive_advisor { } } -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] pub(crate) struct OracleAdvisor<'a, O: IOOracle> { pub(crate) inner: &'a mut O, } -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] fn write_bigint( it: &mut impl ExactSizeIterator, mut to_consume: usize, dst: &mut BigintRepr, ) { - const { - assert!(core::mem::size_of::() == core::mem::size_of::()); - } // NOTE: even if oracle overstates the number of digits (so - iterator length), it is not important // as long as caller checks that number of digits is within bounds of soundness unsafe { - let num_digits = to_consume.next_multiple_of(8) / 8; + const BIGINT_DIGIT_USIZE_SIZE: usize = U256::BYTES / core::mem::size_of::(); + let num_digits = + to_consume.next_multiple_of(BIGINT_DIGIT_USIZE_SIZE) / BIGINT_DIGIT_USIZE_SIZE; let dst_capacity = dst.clear_as_capacity_mut(); for dst in dst_capacity[..num_digits].iter_mut() { - let dst: *mut u32 = dst.as_mut_ptr().cast::<[u32; 8]>().cast(); - for i in 0..8 { + let dst: *mut usize = dst + .as_mut_ptr() + .cast::<[usize; BIGINT_DIGIT_USIZE_SIZE]>() + .cast(); + for i in 0..BIGINT_DIGIT_USIZE_SIZE { if to_consume > 0 { to_consume -= 1; let digit = it.next().unwrap(); - dst.add(i).write(digit as u32); + dst.add(i).write(digit); } else { dst.add(i).write(0); } @@ -848,7 +848,6 @@ fn write_bigint( } } -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] impl<'a, O: IOOracle> ModexpAdvisor for OracleAdvisor<'a, O> { fn get_reduction_op_advice( &mut self, @@ -857,37 +856,81 @@ impl<'a, O: IOOracle> ModexpAdvisor for OracleAdvisor<'a, O> { quotient_dst: &mut BigintRepr, remainder_dst: &mut BigintRepr, ) { - let arg: ModExpAdviceParams = { - let a_len = a.digits; - let a_ptr = a.backing.as_ptr(); - - let modulus_len = m.digits; - let modulus_ptr = m.backing.as_ptr(); - - assert!(modulus_len > 0); - - ModExpAdviceParams { - op: 0, - a_ptr: a_ptr.addr() as u32, - a_len: a_len as u32, - b_ptr: 0, - b_len: 0, - modulus_ptr: modulus_ptr.addr() as u32, - modulus_len: modulus_len as u32, - } + // We use different advice params depending on architecture + // Both are mostly the same, main difference is the width of pointers + #[cfg(target_arch = "riscv32")] + let (mut it, q_len, r_len) = { + use crate::system_functions::modexp::ModExpAdviceParams; + let arg: ModExpAdviceParams = { + let a_len = a.digits; + let a_ptr = a.backing.as_ptr(); + + let modulus_len = m.digits; + let modulus_ptr = m.backing.as_ptr(); + + assert!(modulus_len > 0); + + ModExpAdviceParams { + op: 0, + a_ptr: a_ptr.addr() as u32, + a_len: a_len as u32, + b_ptr: 0, + b_len: 0, + modulus_ptr: modulus_ptr.addr() as u32, + modulus_len: modulus_len as u32, + } + }; + // We assume that oracle's response is well-formed lengths-wise, and we will check value-wise separately + let mut it = self + .inner + .raw_query( + MODEXP_ADVICE_QUERY_ID, + &((&arg as *const ModExpAdviceParams).addr() as u32), + ) + .unwrap(); + let q_len = it.next().expect("quotient length"); + let r_len = it.next().expect("remainder length"); + (it, q_len, r_len) }; - // We assume that oracle's response is well-formed lengths-wise, and we will check value-wise separately - let mut it = self - .inner - .raw_query( - MODEXP_ADVICE_QUERY_ID, - &((&arg as *const ModExpAdviceParams).addr() as u32), - ) - .unwrap(); - - let q_len = it.next().expect("quotient length"); - let r_len = it.next().expect("remainder length"); + #[cfg(not(target_arch = "riscv32"))] + let (mut it, q_len, r_len) = { + let arg: ModExpAdviceParams64 = { + let a_len = a.digits; + let a_ptr = a.backing.as_ptr(); + + let modulus_len = m.digits; + let modulus_ptr = m.backing.as_ptr(); + + assert!(modulus_len > 0); + + ModExpAdviceParams64 { + op: 0, + a_ptr: a_ptr.addr() as u64, + a_len: a_len as u64, + b_ptr: 0, + b_len: 0, + modulus_ptr: modulus_ptr.addr() as u64, + modulus_len: modulus_len as u64, + } + }; + // We assume that oracle's response is well-formed lengths-wise, and we will check value-wise separately + let mut it = self + .inner + .raw_query( + MODEXP_ADVICE_QUERY_ID, + &((&arg as *const ModExpAdviceParams64).addr() as u64), + ) + .unwrap(); + // Oracle provides lengths as u32, so in this case they are + // packed into a single usize + // Note lengths are in 32-bit words, so we have to divide + // by 2 on 64-bit arch. + let packed_lens = it.next().expect("packed lengths"); + let q_len = (packed_lens & 0xFFFF_FFFF) as usize; + let r_len = (packed_lens >> 32) as usize; + (it, q_len / 2, r_len / 2) + }; let max_quotient_digits = if a.digits < m.digits { 0 @@ -899,10 +942,6 @@ impl<'a, O: IOOracle> ModexpAdvisor for OracleAdvisor<'a, O> { let max_remainder_digits = m.digits; - const { - assert!(core::mem::size_of::() == core::mem::size_of::()); - } - // check that hint is "sane" in upper bound assert!(q_len.next_multiple_of(8) / 8 <= max_quotient_digits); diff --git a/basic_system/src/system_functions/modexp/delegation/mod.rs b/basic_system/src/system_functions/modexp/advice/mod.rs similarity index 99% rename from basic_system/src/system_functions/modexp/delegation/mod.rs rename to basic_system/src/system_functions/modexp/advice/mod.rs index aa13cffcb..34cf985f2 100644 --- a/basic_system/src/system_functions/modexp/delegation/mod.rs +++ b/basic_system/src/system_functions/modexp/advice/mod.rs @@ -11,7 +11,6 @@ use zk_ee::system::logger::Logger; #[cfg(feature = "testing")] use zk_ee::system::logger::NullLogger; -#[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] pub(super) fn modexp( base: &[u8], exp: &[u8], diff --git a/basic_system/src/system_functions/modexp/delegation/u256.rs b/basic_system/src/system_functions/modexp/advice/u256.rs similarity index 100% rename from basic_system/src/system_functions/modexp/delegation/u256.rs rename to basic_system/src/system_functions/modexp/advice/u256.rs diff --git a/basic_system/src/system_functions/modexp/mod.rs b/basic_system/src/system_functions/modexp/mod.rs index 32a9caabe..298cf617b 100644 --- a/basic_system/src/system_functions/modexp/mod.rs +++ b/basic_system/src/system_functions/modexp/mod.rs @@ -25,29 +25,32 @@ pub const MODEXP_ADVICE_QUERY_ID: u32 = ADVICE_SUBSPACE_MASK | 0x10; /// Used to request division advice for big integer operations during modexp #[repr(C)] #[derive(Debug, Default)] -pub struct ModExpAdviceParams { - pub op: u32, // Operation type (0 = division) - pub a_ptr: u32, // Pointer to dividend - pub a_len: u32, // Length of dividend in words - pub b_ptr: u32, // Pointer to divisor - pub b_len: u32, // Length of divisor in words - pub modulus_ptr: u32, // Pointer to modulus - pub modulus_len: u32, // Length of modulus in words +pub struct ModExpAdviceParamsGeneric { + pub op: W, // Operation type (0 = division) + pub a_ptr: W, // Pointer to dividend + pub a_len: W, // Length of dividend in words + pub b_ptr: W, // Pointer to divisor + pub b_len: W, // Length of divisor in words + pub modulus_ptr: W, // Pointer to modulus + pub modulus_len: W, // Length of modulus in words } -#[cfg(any( - all(target_arch = "riscv32", feature = "proving"), - test, - feature = "testing" -))] -pub mod delegation; +/// Used for proving (RISC-V 32-bit) +pub type ModExpAdviceParams = ModExpAdviceParamsGeneric; + +/// Used for native execution (64-bit) +pub type ModExpAdviceParams64 = ModExpAdviceParamsGeneric; + +pub mod advice; /// /// modexp system function implementation. /// -pub struct ModExpImpl; +pub struct ModExpImpl; -impl SystemFunctionExt for ModExpImpl { +impl SystemFunctionExt + for ModExpImpl +{ /// If the input size is less than expected - it will be padded with zeroes. /// If the input size is greater - redundant bytes will be ignored. /// @@ -71,7 +74,9 @@ impl SystemFunctionExt for ModExpImpl { allocator: A, ) -> Result<(), SubsystemError> { cycle_marker::wrap_with_resources!("modexp", resources, { - modexp_as_system_function_inner(input, output, resources, oracle, logger, allocator) + modexp_as_system_function_inner::<_, _, _, _, _, USE_ADVICE>( + input, output, resources, oracle, logger, allocator, + ) }) } } @@ -106,6 +111,9 @@ fn modexp_as_system_function_inner< D: ?Sized + TryExtend, A: Allocator + Clone, R: Resources, + // Toggle delegation-based implementation for forward run, to be able + // to capture oracle queries. + const USE_ADVICE: bool, >( input: &[u8], dst: &mut D, @@ -211,7 +219,7 @@ fn modexp_as_system_function_inner< // Call the modexp. #[cfg(any(all(target_arch = "riscv32", feature = "proving"), test))] - let output = self::delegation::modexp( + let output = self::advice::modexp( base.as_slice(), exponent.as_slice(), modulus.as_slice(), @@ -221,12 +229,23 @@ fn modexp_as_system_function_inner< ); #[cfg(not(any(all(target_arch = "riscv32", feature = "proving"), test)))] - let output = ::modexp::modexp( - base.as_slice(), - exponent.as_slice(), - modulus.as_slice(), - allocator, - ); + let output = if USE_ADVICE { + self::advice::modexp( + base.as_slice(), + exponent.as_slice(), + modulus.as_slice(), + oracle, + logger, + allocator, + ) + } else { + ::modexp::modexp( + base.as_slice(), + exponent.as_slice(), + modulus.as_slice(), + allocator, + ) + }; if output.len() >= mod_len { // truncate diff --git a/basic_system/src/system_implementation/flat_storage_model/cost_constants.rs b/basic_system/src/system_implementation/flat_storage_model/cost_constants.rs index 8b8fab6f0..ef17fe52c 100644 --- a/basic_system/src/system_implementation/flat_storage_model/cost_constants.rs +++ b/basic_system/src/system_implementation/flat_storage_model/cost_constants.rs @@ -49,6 +49,11 @@ pub const EVENT_STORAGE_BASE_NATIVE_COST: u64 = 6000; pub const EVENT_TOPIC_NATIVE_COST: u64 = 200; pub const EVENT_DATA_PER_BYTE_COST: u64 = 2; +const INTEROP_ROOT_BYTE_LENGTH: u64 = 32 * 3; +// Same costs as for events, as the same structure is used. +pub const INTEROP_ROOT_STORAGE_NATIVE_COST: u64 = + EVENT_STORAGE_BASE_NATIVE_COST + INTEROP_ROOT_BYTE_LENGTH * EVENT_DATA_PER_BYTE_COST; + // Helper to compute hashing native cost pub fn blake2s_native_cost(len: usize) -> u64 { let num_rounds = (len as u64).div_ceil(BLAKE2S_CHUNK_SIZE); diff --git a/basic_system/src/system_implementation/system/da_commitment_generator/blob_commitment_generator/commitment_and_proof_advice.rs b/basic_system/src/system_implementation/system/da_commitment_generator/blob_commitment_generator/commitment_and_proof_advice.rs index 2d1c57d88..f7b44830f 100644 --- a/basic_system/src/system_implementation/system/da_commitment_generator/blob_commitment_generator/commitment_and_proof_advice.rs +++ b/basic_system/src/system_implementation/system/da_commitment_generator/blob_commitment_generator/commitment_and_proof_advice.rs @@ -70,7 +70,6 @@ pub struct OracleBasedBlobCommitmentAndProofAdvisor<'a, O: zk_ee::oracle::IOOrac pub oracle: &'a mut O, } -// Right now, we run it only on RISC-V, but in theory, it should be possible to make it runnable on native arch as well impl<'a, O: zk_ee::oracle::IOOracle> BlobCommitmentAndProofAdvisor for OracleBasedBlobCommitmentAndProofAdvisor<'a, O> { diff --git a/basic_system/src/system_implementation/system/interop_roots.rs b/basic_system/src/system_implementation/system/interop_roots.rs new file mode 100644 index 000000000..77e4524c2 --- /dev/null +++ b/basic_system/src/system_implementation/system/interop_roots.rs @@ -0,0 +1,36 @@ +use crypto::MiniDigest; +use ruint::aliases::U256; +use zk_ee::{common_structs::interop_root_storage::InteropRoot, utils::Bytes32}; + +use crate::system_functions::keccak256::keccak256_native_cost_u64; + +/// Calculates a rolling keccak256 hash over a sequence of interop roots. +/// This creates a cumulative digest that can be verified on settlement layers. +/// +/// For each root: rolling_hash = keccak256(old_rolling_hash || chain_id || block_number || root_hash) +pub fn calculate_interop_roots_rolling_hash<'a>( + old_rolling_hash: Bytes32, + roots: impl Iterator, + hasher: &mut crypto::sha3::Keccak256, +) -> Bytes32 { + let mut data = [0u8; 96]; + + let mut rolling_hash = old_rolling_hash; + for root in roots { + data[0..32].copy_from_slice(&rolling_hash.as_u8_ref()); + data[32..64].copy_from_slice(&root.chain_id.to_be_bytes::<{ U256::BYTES }>()); + data[64..96].copy_from_slice(&root.block_or_batch_number.to_be_bytes::<{ U256::BYTES }>()); + hasher.update(data); + + // Note: now we have only one side + hasher.update(root.root.as_u8_ref()); + + rolling_hash = hasher.finalize_reset().into() + } + + rolling_hash +} + +pub fn per_root_computational_native_cost() -> u64 { + keccak256_native_cost_u64(96 + 32) +} diff --git a/basic_system/src/system_implementation/system/io_subsystem.rs b/basic_system/src/system_implementation/system/io_subsystem.rs index f333ccfc8..cf56c6202 100644 --- a/basic_system/src/system_implementation/system/io_subsystem.rs +++ b/basic_system/src/system_implementation/system/io_subsystem.rs @@ -11,6 +11,7 @@ use crate::system_implementation::system::public_input::{BlocksOutput, BlocksPub use cost_constants::EVENT_DATA_PER_BYTE_COST; use cost_constants::EVENT_STORAGE_BASE_NATIVE_COST; use cost_constants::EVENT_TOPIC_NATIVE_COST; +use cost_constants::INTEROP_ROOT_STORAGE_NATIVE_COST; use cost_constants::WARM_TSTORAGE_READ_NATIVE_COST; use cost_constants::WARM_TSTORAGE_WRITE_NATIVE_COST; use crypto::blake2s::Blake2s256; @@ -20,14 +21,19 @@ use evm_interpreter::gas_constants::LOGDATA; use evm_interpreter::gas_constants::LOGTOPIC; use evm_interpreter::gas_constants::TLOAD; use evm_interpreter::gas_constants::TSTORE; +use interop_roots::calculate_interop_roots_rolling_hash; +use interop_roots::per_root_computational_native_cost; use storage_models::common_structs::generic_transient_storage::GenericTransientStorage; use storage_models::common_structs::snapshottable_io::SnapshottableIo; use storage_models::common_structs::StorageModel; use zk_ee::common_structs::da_commitment_scheme::DACommitmentScheme; +use zk_ee::common_structs::interop_root_storage::InteropRoot; +use zk_ee::common_structs::interop_root_storage::InteropRootStorage; use zk_ee::common_structs::ProofData; use zk_ee::common_structs::L2_TO_L1_LOG_SERIALIZE_SIZE; use zk_ee::interface_error; use zk_ee::oracle::basic_queries::ZKProofDataQuery; +use zk_ee::oracle::query_ids::DISCONNECT_ORACLE_QUERY_ID; use zk_ee::oracle::simple_oracle_query::SimpleOracleQuery; use zk_ee::out_of_ergs_error; use zk_ee::system::metadata::zk_metadata::BlockMetadataFromOracle; @@ -55,6 +61,7 @@ pub struct FullIO< pub(crate) transient_storage: GenericTransientStorage, pub(crate) logs_storage: LogsStorage, pub(crate) events_storage: EventsStorage, + pub(crate) interop_root_storage: InteropRootStorage, pub(crate) allocator: A, pub(crate) oracle: O, pub(crate) tx_number: u32, @@ -66,6 +73,7 @@ pub struct FullIOStateSnapshot { transient: CacheSnapshotId, messages: usize, events: usize, + interop_roots: usize, } impl< @@ -226,6 +234,24 @@ impl< Ok(data_hash) } + fn add_interop_root( + &mut self, + _ee_type: ExecutionEnvironmentType, + resources: &mut Self::Resources, + interop_root: InteropRoot, + ) -> Result<(), SystemError> { + // For native we charge for the storage and the computation of the rolling + // hash (keccak of old hash || new root). + let native = ::Native::from_computational( + INTEROP_ROOT_STORAGE_NATIVE_COST + per_root_computational_native_cost(), + ); + + let to_charge = Self::Resources::from_native(native); + resources.charge(&to_charge)?; + + self.interop_root_storage.push_root(interop_root) + } + fn get_nominal_token_balance( &mut self, ee_type: ExecutionEnvironmentType, @@ -369,12 +395,14 @@ impl< let transient = self.transient_storage.start_frame(); let messages = self.logs_storage.start_frame(); let events = self.events_storage.start_frame(); + let interop_roots = self.interop_root_storage.start_frame(); Ok(FullIOStateSnapshot { io, transient, messages, events, + interop_roots, }) } @@ -389,6 +417,8 @@ impl< .finish_frame(rollback_handle.map(|x| x.messages)); self.events_storage .finish_frame(rollback_handle.map(|x| x.events)); + self.interop_root_storage + .finish_frame(rollback_handle.map(|x| x.interop_roots)); Ok(()) } @@ -531,6 +561,9 @@ impl< // finishing IO, applying changes let mut da_commitment_generator = crate::system_implementation::system::da_commitment_generator::Blake2sCommitmentGenerator::new(); // Write version byte first to enable future pubdata format upgrades + result_keeper.pubdata(&[PUBDATA_ENCODING_VERSION]); + result_keeper.pubdata(current_block_hash.as_u8_ref()); + result_keeper.pubdata(&block_metadata.timestamp.to_be_bytes()); da_commitment_generator.write(&[PUBDATA_ENCODING_VERSION]); da_commitment_generator.write(current_block_hash.as_u8_ref()); da_commitment_generator.write(&block_metadata.timestamp.to_be_bytes()); @@ -571,6 +604,12 @@ impl< last_block_timestamp: block_metadata.timestamp, }; + let interop_roots_rolling_hash = calculate_interop_roots_rolling_hash( + Bytes32::zero(), + self.interop_root_storage.iter(), + &mut crypto::sha3::Keccak256::new(), + ); + // other outputs to be opened on the settlement layer/aggregation program let block_output = BlocksOutput { chain_id: U256::try_from(block_metadata.chain_id).unwrap(), @@ -580,6 +619,7 @@ impl< priority_ops_hashes_hash: l1_to_l2_txs_hash, l2_to_l1_logs_hashes_hash: l2_to_l1_logs_hashes_hash.into(), upgrade_tx_hash, + interop_roots_rolling_hash, }; let public_input = BlocksPublicInput { @@ -588,6 +628,11 @@ impl< blocks_output: block_output.hash().into(), }; + #[allow(unused_must_use)] + self.oracle + .raw_query_with_empty_input(DISCONNECT_ORACLE_QUERY_ID) + .expect("must disconnect an oracle before performing arbitrary CSR access"); + (self.oracle, public_input.hash().into()) } } @@ -607,7 +652,7 @@ impl< O: IOOracle, > FinishIO for FullIO { - type FinalData = (O, Bytes32); + type FinalData = (O, Bytes32, public_input::BatchOutput); fn finish( mut self, block_metadata: BlockMetadataFromOracle, @@ -647,6 +692,9 @@ impl< .unwrap(); // Write version byte first to enable future pubdata format upgrades + result_keeper.pubdata(&[PUBDATA_ENCODING_VERSION]); + result_keeper.pubdata(current_block_hash.as_u8_ref()); + result_keeper.pubdata(&block_metadata.timestamp.to_be_bytes()); da_commitment_generator.write(&[PUBDATA_ENCODING_VERSION]); da_commitment_generator.write(current_block_hash.as_u8_ref()); da_commitment_generator.write(&block_metadata.timestamp.to_be_bytes()); @@ -704,6 +752,13 @@ impl< let _ = logger.write_fmt(format_args!( "PI calculation: state commitment after {chain_state_commitment_after:?}\n", )); + + let interop_roots_rolling_hash = calculate_interop_roots_rolling_hash( + Bytes32::zero(), + self.interop_root_storage.iter(), + &mut crypto::sha3::Keccak256::new(), + ); + let batch_output = public_input::BatchOutput { chain_id: U256::try_from(block_metadata.chain_id).unwrap(), first_block_timestamp: block_metadata.timestamp, @@ -714,7 +769,7 @@ impl< priority_operations_hash: l1_txs_commitment.1, l2_logs_tree_root: full_l2_to_l1_logs_root.into(), upgrade_tx_hash, - interop_root_rolling_hash: Bytes32::from([0u8; 32]), // for now no interop roots + interop_roots_rolling_hash, }; let _ = logger.write_fmt(format_args!( "PI calculation: batch output {batch_output:?}\n", @@ -733,10 +788,15 @@ impl< "PI calculation: final batch public input hash {public_input_hash:?}\n", )); + #[allow(unused_must_use)] + self.oracle + .raw_query_with_empty_input(DISCONNECT_ORACLE_QUERY_ID) + .expect("must disconnect an oracle before performing arbitrary CSR access"); + if cfg!(feature = "state-diffs-pi") { - (self.oracle, state_diffs_hash) + (self.oracle, state_diffs_hash, batch_output) } else { - (self.oracle, public_input_hash) + (self.oracle, public_input_hash, batch_output) } } } @@ -884,14 +944,22 @@ where last_block_timestamp: block_metadata.timestamp, }; + let interop_roots_iter = self.interop_root_storage.iter(); + builder.apply_block( chain_state_commitment_before.hash().into(), chain_state_commitment_after.hash().into(), block_metadata.timestamp, U256::try_from(block_metadata.chain_id).unwrap(), upgrade_tx_hash, + interop_roots_iter, ); + #[allow(unused_must_use)] + self.oracle + .raw_query_with_empty_input(DISCONNECT_ORACLE_QUERY_ID) + .expect("must disconnect an oracle before performing arbitrary CSR access"); + self.oracle } } @@ -924,6 +992,8 @@ where let logs_storage = LogsStorage::::new_from_parts(allocator.clone()); let events_storage = EventsStorage::::new_from_parts(allocator.clone()); + let interop_root_storage = + InteropRootStorage::::new_from_parts(allocator.clone()); let da_commitment_scheme = if PROOF_ENV { Some(DACommitmentScheme::try_from_oracle(&mut oracle)?) @@ -935,6 +1005,7 @@ where transient_storage, events_storage, logs_storage, + interop_root_storage, allocator, oracle, tx_number: 0u32, diff --git a/basic_system/src/system_implementation/system/mod.rs b/basic_system/src/system_implementation/system/mod.rs index 86866d81a..c7292a56c 100644 --- a/basic_system/src/system_implementation/system/mod.rs +++ b/basic_system/src/system_implementation/system/mod.rs @@ -23,6 +23,7 @@ use zk_ee::{ }; pub mod da_commitment_generator; +pub mod interop_roots; mod io_subsystem; pub mod pubdata; mod public_input; diff --git a/basic_system/src/system_implementation/system/public_input.rs b/basic_system/src/system_implementation/system/public_input.rs index f5355ae6b..0540a5adf 100644 --- a/basic_system/src/system_implementation/system/public_input.rs +++ b/basic_system/src/system_implementation/system/public_input.rs @@ -1,10 +1,12 @@ use crate::system_implementation::system::da_commitment_generator::DACommitmentGenerator; +use crate::system_implementation::system::interop_roots::calculate_interop_roots_rolling_hash; use crate::system_implementation::system::public_input; use arrayvec::ArrayVec; use crypto::sha3::Keccak256; use crypto::MiniDigest; use ruint::aliases::U256; use zk_ee::common_structs::da_commitment_scheme::DACommitmentScheme; +use zk_ee::common_structs::interop_root_storage::InteropRoot; use zk_ee::oracle::IOOracle; use zk_ee::system::logger::Logger; use zk_ee::utils::Bytes32; @@ -71,6 +73,8 @@ pub struct BlocksOutput { pub l2_to_l1_logs_hashes_hash: Bytes32, /// Protocol upgrade tx hash (0 if there wasn't) pub upgrade_tx_hash: Bytes32, + /// Linear keccak256 hash of interop roots + pub interop_roots_rolling_hash: Bytes32, } #[cfg(feature = "aggregation")] @@ -90,6 +94,7 @@ impl BlocksOutput { hasher.update(self.priority_ops_hashes_hash.as_u8_ref()); hasher.update(self.l2_to_l1_logs_hashes_hash.as_u8_ref()); hasher.update(self.upgrade_tx_hash.as_u8_ref()); + hasher.update(self.interop_roots_rolling_hash.as_u8_ref()); hasher.finalize() } } @@ -152,8 +157,8 @@ pub struct BatchOutput { pub l2_logs_tree_root: Bytes32, /// Protocol upgrade tx hash (0 if there wasn't) pub upgrade_tx_hash: Bytes32, - /// Rolling hash of all the interop roots included in this batch. - pub interop_root_rolling_hash: Bytes32, + /// Linear keccak256 hash of interop roots + pub interop_roots_rolling_hash: Bytes32, } impl BatchOutput { @@ -173,7 +178,7 @@ impl BatchOutput { hasher.update(self.priority_operations_hash.as_u8_ref()); hasher.update(self.l2_logs_tree_root.as_u8_ref()); hasher.update(self.upgrade_tx_hash.as_u8_ref()); - hasher.update(self.interop_root_rolling_hash.as_u8_ref()); + hasher.update(self.interop_roots_rolling_hash.as_u8_ref()); hasher.finalize() } } @@ -218,6 +223,7 @@ pub struct BatchPublicInputBuilder { pub number_of_layer_1_txs: U256, pub l1_txs_rolling_hash: Bytes32, upgrade_tx_hash: Option, + interop_roots_rolling_hash: Bytes32, } impl BatchPublicInputBuilder { @@ -240,6 +246,7 @@ impl BatchPublicInputBuilder { 0x5d, 0x85, 0xa4, 0x70, ]), upgrade_tx_hash: None, + interop_roots_rolling_hash: Bytes32::ZERO, } } @@ -247,13 +254,14 @@ impl BatchPublicInputBuilder { /// Apply information about a processed block. /// Please note, that pubdata, l2 -> l1 logs, and l1 -> l2 txs commitment should be handled separately using corresponding public fields of this structure. /// - pub fn apply_block( + pub fn apply_block<'a>( &mut self, state_commitment_before: Bytes32, state_commitment_after: Bytes32, block_timestamp: u64, chain_id: U256, upgrade_tx_hash: Bytes32, + interop_roots: impl Iterator, ) { if self.is_first_block { self.initial_state_commitment = Some(state_commitment_before); @@ -273,6 +281,12 @@ impl BatchPublicInputBuilder { assert_eq!(self.chain_id.unwrap(), chain_id); assert!(upgrade_tx_hash.is_zero()); } + + self.interop_roots_rolling_hash = calculate_interop_roots_rolling_hash( + self.interop_roots_rolling_hash, + interop_roots, + &mut crypto::sha3::Keccak256::new(), + ); } /// @@ -296,7 +310,7 @@ impl BatchPublicInputBuilder { priority_operations_hash: self.l1_txs_rolling_hash, l2_logs_tree_root: full_l2_to_l1_logs_root.into(), upgrade_tx_hash: self.upgrade_tx_hash.unwrap(), - interop_root_rolling_hash: Bytes32::from([0u8; 32]), // for now no interop roots + interop_roots_rolling_hash: self.interop_roots_rolling_hash, }; let public_input = BatchPublicInput { state_before: self.initial_state_commitment.unwrap(), diff --git a/callable_oracles/src/arithmetic/mod.rs b/callable_oracles/src/arithmetic/mod.rs index c5be8b62a..e1faa7a0e 100644 --- a/callable_oracles/src/arithmetic/mod.rs +++ b/callable_oracles/src/arithmetic/mod.rs @@ -1,12 +1,57 @@ -use basic_system::system_functions::modexp::{ModExpAdviceParams, MODEXP_ADVICE_QUERY_ID}; +use basic_system::system_functions::modexp::{ + ModExpAdviceParams, ModExpAdviceParams64, MODEXP_ADVICE_QUERY_ID, +}; use oracle_provider::OracleQueryProcessor; use risc_v_simulator::abstractions::memory::MemorySource; +use crate::read_u64_words; use crate::utils::{ evaluate::{read_memory_as_u64, read_struct}, usize_slice_iterator::UsizeSliceIteratorOwned, }; +struct ArithmeticQueryOutput { + quotient: Vec, + remainder: Vec, +} + +impl ArithmeticQueryOutput { + fn into_usize_iterator(self) -> Box + 'static> { + // Trim zeros + fn strip_leading_zeroes(input: &[u64]) -> &[u64] { + let mut digits = input.len(); + for el in input.iter().rev() { + if *el == 0 { + digits -= 1; + } else { + break; + } + } + &input[..digits] + } + let quotient = strip_leading_zeroes(&self.quotient); + let remainder = strip_leading_zeroes(&self.remainder); + + // account for usize being u64 here + let q_len_in_u32_words = quotient.len() * 2; + let r_len_in_u32_words = remainder.len() * 2; + // account for LE, and we will ask quotient first, then remainder + let header = [(q_len_in_u32_words as u64) | ((r_len_in_u32_words as u64) << 32)]; + + let r = header + .iter() + .chain(quotient.iter()) + .chain(remainder.iter()) + .map(|x| *x as usize) + .collect::>(); + let r = Vec::into_boxed_slice(r); + + let n = UsizeSliceIteratorOwned::new(r); + + Box::new(n) + } +} + pub struct ArithmeticQuery { _marker: std::marker::PhantomData, } @@ -58,37 +103,75 @@ impl OracleQueryProcessor for ArithmeticQuery { ruint::algorithms::div(&mut n, &mut d); - // Trim zeros - fn strip_leading_zeroes(input: &[u64]) -> &[u64] { - let mut digits = input.len(); - for el in input.iter().rev() { - if *el == 0 { - digits -= 1; - } else { - break; - } - } - &input[..digits] + ArithmeticQueryOutput { + quotient: n, + remainder: d, + } + .into_usize_iterator() + } +} + +/// Query processor to be used for prover input native run +/// Works in a similar way as the ArithmeticQuery, but with +/// 64 bit pointers. Importantly, the query response is the +/// same. +/// +/// This processor explicitly reads the process memory +/// using a raw pointer to get the input. +pub struct NativeArithmeticQuery { + _marker: std::marker::PhantomData, +} + +impl Default for NativeArithmeticQuery { + fn default() -> Self { + Self { + _marker: std::marker::PhantomData, } - let quotient = strip_leading_zeroes(&n); - let remainder = strip_leading_zeroes(&d); + } +} - // account for usize being u64 here - let q_len_in_u32_words = quotient.len() * 2; - let r_len_in_u32_words = remainder.len() * 2; - // account for LE, and we will ask quotient first, then remainder - let header = [(q_len_in_u32_words as u64) | ((r_len_in_u32_words as u64) << 32)]; +impl OracleQueryProcessor for NativeArithmeticQuery { + fn supported_query_ids(&self) -> Vec { + vec![MODEXP_ADVICE_QUERY_ID] + } - let r = header - .iter() - .chain(quotient.iter()) - .chain(remainder.iter()) - .map(|x| *x as usize) - .collect::>(); - let r = Vec::into_boxed_slice(r); + fn process_buffered_query( + &mut self, + query_id: u32, + query: Vec, + _memory: &M, + ) -> Box + 'static> { + debug_assert!(self.supports_query_id(query_id)); - let n = UsizeSliceIteratorOwned::new(r); + let mut it = query.into_iter(); - Box::new(n) + let arg_ptr = it.next().expect("A u64 should've been passed in."); + + assert!(it.next().is_none(), "A single ptr should've been passed."); + + let arg = unsafe { + let p = arg_ptr as *const ModExpAdviceParams64; + core::ptr::read_unaligned(p) + }; + + assert!(arg.a_ptr > 0); + assert!(arg.a_len > 0); + + assert_eq!(arg.b_ptr, 0); + assert_eq!(arg.b_len, 0); + + assert!(arg.modulus_ptr > 0); + assert!(arg.modulus_len > 0); + + let mut n: Vec = unsafe { read_u64_words(arg.a_ptr, arg.a_len * 4) }; + let mut d: Vec = unsafe { read_u64_words(arg.modulus_ptr, arg.modulus_len * 4) }; + + ruint::algorithms::div(&mut n, &mut d); + + ArithmeticQueryOutput { + quotient: n, + remainder: d, + } + .into_usize_iterator() } } diff --git a/callable_oracles/src/blob_kzg_commitment/mod.rs b/callable_oracles/src/blob_kzg_commitment/mod.rs index 412e61545..68dbe4a09 100644 --- a/callable_oracles/src/blob_kzg_commitment/mod.rs +++ b/callable_oracles/src/blob_kzg_commitment/mod.rs @@ -48,7 +48,7 @@ impl OracleQueryProcessor for BlobCommitmentAndProofQuery let data_len = it.next().unwrap() as u32; assert!( it.next().is_none(), - "A single RISC-V ptr should've been passed." + "Only a pointer and the length are expected." ); assert!(data_ptr.is_multiple_of(4)); @@ -63,6 +63,58 @@ impl OracleQueryProcessor for BlobCommitmentAndProofQuery } } +/// Query processor to be used for prover input native run +/// Works in a similar way as the NativeBlobCommitmentAndProof, but with +/// 64 bit pointers. Importantly, the query response is the +/// same. +/// +/// This processor explicitly reads the process memory +/// using a raw pointer to get the input. +pub struct NativeBlobCommitmentAndProofQuery { + _marker: std::marker::PhantomData, +} + +impl Default for NativeBlobCommitmentAndProofQuery { + fn default() -> Self { + Self { + _marker: std::marker::PhantomData, + } + } +} + +impl OracleQueryProcessor for NativeBlobCommitmentAndProofQuery { + fn supported_query_ids(&self) -> Vec { + vec![BLOB_COMMITMENT_AND_PROOF_QUERY_ID] + } + + fn process_buffered_query( + &mut self, + query_id: u32, + query: Vec, + _memory: &M, + ) -> Box + 'static> { + debug_assert!(self.supports_query_id(query_id)); + + // this query processor supposed to work only on "host" architecture, which is always 64 bit + const { assert!(8 == core::mem::size_of::()) }; + let mut it = query.into_iter(); + + let data_ptr = it.next().unwrap(); + let data_len = it.next().unwrap(); + assert!( + it.next().is_none(), + "Only a pointer and the length are expected." + ); + let data = unsafe { crate::read_u8_words(data_ptr as u64, data_len as u64) }; + let result = blob_kzg_commitment_and_proof(&data); + + let r = result.iter().collect::>(); + let r = Vec::into_boxed_slice(r); + let n = UsizeSliceIteratorOwned::new(r); + Box::new(n) + } +} + /// /// Calculate kzg commitment and proof at the point `blake2s(versioned_hash & data)` for blob created from passed data. /// diff --git a/callable_oracles/src/lib.rs b/callable_oracles/src/lib.rs index bd63748d2..8c834c0bd 100644 --- a/callable_oracles/src/lib.rs +++ b/callable_oracles/src/lib.rs @@ -59,3 +59,23 @@ impl UsizeDeserializable for MemoryRegionDescriptionParams { Ok(new) } } + +#[inline(always)] +unsafe fn read_u64_words(ptr_u64: u64, len_words_u64: u64) -> Vec { + if ptr_u64 == 0 || len_words_u64 == 0 { + return vec![]; + } + let addr = ptr_u64 as usize; + let len_words = len_words_u64 as usize; + core::slice::from_raw_parts(addr as *const u64, len_words).to_vec() +} + +#[inline(always)] +unsafe fn read_u8_words(ptr_u64: u64, len_words_u8: u64) -> Vec { + if ptr_u64 == 0 || len_words_u8 == 0 { + return vec![]; + } + let addr = ptr_u64 as usize; + let len_words = len_words_u8 as usize; + core::slice::from_raw_parts(addr as *const u8, len_words).to_vec() +} diff --git a/crypto/src/bigint_delegation/mod.rs b/crypto/src/bigint_delegation/mod.rs index 30ae6639d..261820242 100644 --- a/crypto/src/bigint_delegation/mod.rs +++ b/crypto/src/bigint_delegation/mod.rs @@ -1,4 +1,4 @@ -use crate::ark_ff_delegation::BigInt; +use crate::BigInt; pub(crate) mod delegation; pub mod u256; diff --git a/crypto/src/bigint_delegation/u256.rs b/crypto/src/bigint_delegation/u256.rs index 20430b37e..c9091038e 100644 --- a/crypto/src/bigint_delegation/u256.rs +++ b/crypto/src/bigint_delegation/u256.rs @@ -1,5 +1,5 @@ use super::{delegation, DelegatedBarretParams, DelegatedModParams, DelegatedMontParams}; -use crate::ark_ff_delegation::{BigInt, BigInteger}; +use crate::{BigInt, BigInteger}; use core::{fmt::Debug, marker::PhantomData}; pub(super) type U256 = BigInt<4>; diff --git a/crypto/src/bigint_delegation/u512.rs b/crypto/src/bigint_delegation/u512.rs index f372ee08b..45e08c53a 100644 --- a/crypto/src/bigint_delegation/u512.rs +++ b/crypto/src/bigint_delegation/u512.rs @@ -3,7 +3,7 @@ use super::{ u256::{self, U256}, DelegatedModParams, DelegatedMontParams, }; -use crate::ark_ff_delegation::{BigInt, BigInteger}; +use crate::{BigInt, BigInteger}; pub(super) type U512 = BigInt<8>; diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f77607622..d46309d90 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -16,12 +16,6 @@ pub mod ark_ff_delegation; #[allow(clippy::all)] #[allow(unused_imports, dead_code)] -#[cfg(any( - all(target_arch = "riscv32", feature = "bigint_ops"), - feature = "proving", - feature = "testing", - test -))] mod bigint_delegation; #[allow(unexpected_cfgs)] pub mod blake2s; @@ -66,12 +60,6 @@ pub use crate::ark_ff_delegation::Fp; )))] pub use ark_ff::Fp; -#[cfg(any( - all(target_arch = "riscv32", feature = "bigint_ops"), - feature = "proving", - feature = "testing", - test -))] mod raw_delegation_interface; pub use blake2 as blake2_ext; @@ -80,12 +68,6 @@ pub use ark_ec; pub use ark_ff; pub use ark_serialize; -#[cfg(any( - all(target_arch = "riscv32", feature = "bigint_ops"), - feature = "proving", - feature = "testing", - test -))] pub use self::raw_delegation_interface::{ bigint_op_delegation_raw, bigint_op_delegation_with_carry_bit_raw, }; diff --git a/cycle_marker/src/lib.rs b/cycle_marker/src/lib.rs index a08909f01..13f8b9df9 100644 --- a/cycle_marker/src/lib.rs +++ b/cycle_marker/src/lib.rs @@ -158,6 +158,74 @@ macro_rules! wrap_with_resources { }}; } +// Snapshotting mechanism, used for tests +// We run multiple native runs of the program, so labels can be duplicated. +// This is a way to ignore some of those side effects. +#[cfg(not(target_arch = "riscv32"))] +pub struct Snapshot { + labels_len: usize, + #[cfg(feature = "log_to_file")] + file_len: u64, +} + +#[cfg(target_arch = "riscv32")] +pub struct Snapshot; + +#[cfg(not(target_arch = "riscv32"))] +pub fn snapshot() -> Snapshot { + let labels_len = LABELS.with(|l| l.borrow().len()); + + #[cfg(feature = "log_to_file")] + let file_len = MARKER_FILE.with(|f| { + use std::io::Seek; + use std::io::SeekFrom; + + let mut file = f.borrow_mut(); + // Get current position; writing always appends, so this is effectively the "index" + file.seek(SeekFrom::Current(0)) + .expect("Failed to seek marker file") + }); + + Snapshot { + labels_len, + #[cfg(feature = "log_to_file")] + file_len, + } +} + +#[cfg(target_arch = "riscv32")] +pub fn snapshot() -> Snapshot { + Snapshot +} + +#[cfg(not(target_arch = "riscv32"))] +pub fn revert(snap: Snapshot) { + // Restore LABELS length + LABELS.with(|l| { + let mut v = l.borrow_mut(); + if v.len() > snap.labels_len { + v.truncate(snap.labels_len); + } + }); + + // Restore file length/position if logging to file + #[cfg(feature = "log_to_file")] + { + use std::io::{Seek, SeekFrom}; + + MARKER_FILE.with(|f| { + let mut file = f.borrow_mut(); + file.set_len(snap.file_len) + .expect("Failed to truncate marker file"); + file.seek(SeekFrom::Start(snap.file_len)) + .expect("Failed to seek marker file"); + }); + } +} + +#[cfg(target_arch = "riscv32")] +pub fn revert(_: Snapshot) {} + #[cfg(all(feature = "use_risc_v_simulator", not(target_arch = "riscv32")))] pub fn print_cycle_markers() -> Option { const BLAKE_DELEGATION_ID: u32 = 1991; diff --git a/docs/bootloader/transaction_format.md b/docs/bootloader/transaction_format.md index 41040490f..de4fae841 100644 --- a/docs/bootloader/transaction_format.md +++ b/docs/bootloader/transaction_format.md @@ -3,7 +3,7 @@ ZKsyncOS transactions have two encoding formats: 1. ABI-encoded: used for L1->L2 transactions and upgrade transactions. This format is defined in the next section. -2. RLP-encoded: used for L2 transactions. These follow the standard Ethereum RLP encoding for legacy, EIP-2930, EIP-1559 and EIP-7702 transactions. +2. RLP-encoded: used for L2 transactions. These follow the standard Ethereum RLP encoding for legacy, EIP-2930, EIP-1559 and EIP-7702 transactions. In addition, we include a custom "service transaction", used for system work. These service transactions aren't signed and have whitelist of allowed destinations. The encoding for these is `0x7D || rlp([nonce, gas_limit, destination, data])`. ## ABI-encoded ZKsync-specific transactions @@ -28,10 +28,11 @@ ZKsyncOS transactions have two encoding formats: ### Transaction Types -Note that transaction types 0,1,2 and 4 are used for RLP-encoded L2 transactions, representing legacy, EIP-2930, EIP-1559 and EIP-7702 transactions.s +Note that transaction types 0,1,2 and 4 are used for RLP-encoded L2 transactions, representing legacy, EIP-2930, EIP-1559 and EIP-7702 transactions. | Value | Description | |---------|---------------------------------------------------------------------------------------------------| +| `0x7D` | Service transaction. | `0x7E` | Upgrade transaction. | | `0x7F` | L1 -> L2 transaction. | diff --git a/evm_interpreter/src/ee_trait_impl.rs b/evm_interpreter/src/ee_trait_impl.rs index 77be540b3..fcee185a7 100644 --- a/evm_interpreter/src/ee_trait_impl.rs +++ b/evm_interpreter/src/ee_trait_impl.rs @@ -4,6 +4,7 @@ use crate::gas::gas_utils; use crate::gas_constants::{CALLVALUE, CALL_STIPEND, NEWACCOUNT}; use core::fmt::Write; use core::mem; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::common_structs::CalleeAccountProperties; use zk_ee::system::errors::interface::InterfaceError; use zk_ee::system::errors::runtime::RuntimeError; @@ -51,6 +52,7 @@ impl<'ee, S: EthereumLikeTypes> ExecutionEnvironment<'ee, S, EvmErrors> for Inte fn start_executing_frame<'a, 'i: 'ee, 'h: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, frame_state: ExecutionEnvironmentLaunchParams<'i, S>, heap: SliceVec<'h, u8>, tracer: &mut impl Tracer, @@ -198,13 +200,14 @@ impl<'ee, S: EthereumLikeTypes> ExecutionEnvironment<'ee, S, EvmErrors> for Inte self.heap = heap; self.call_value = nominal_token_value; - self.execute_till_yield_point(system, tracer) + self.execute_till_yield_point(system, hooks, tracer) } /// Note: panics if `pending_os_request` is None fn continue_after_preemption<'a, 'res: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, returned_resources: S::Resources, call_request_result: CallResult<'res, S>, tracer: &mut impl Tracer, @@ -279,7 +282,7 @@ impl<'ee, S: EthereumLikeTypes> ExecutionEnvironment<'ee, S, EvmErrors> for Inte } } - self.execute_till_yield_point(system, tracer) + self.execute_till_yield_point(system, hooks, tracer) } fn calculate_resources_passed_in_external_call( diff --git a/evm_interpreter/src/instructions/host.rs b/evm_interpreter/src/instructions/host.rs index 0905791fe..4d06e353b 100644 --- a/evm_interpreter/src/instructions/host.rs +++ b/evm_interpreter/src/instructions/host.rs @@ -4,6 +4,7 @@ use core::hint::unreachable_unchecked; use gas_constants::{CALL_STIPEND, INITCODE_WORD_COST, SHA3WORD}; use native_resource_constants::*; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::storage_types::MAX_EVENT_TOPICS; use zk_ee::system::tracer::evm_tracer::EvmTracer; use zk_ee::system::tracer::Tracer; @@ -208,6 +209,7 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> { pub fn log( &mut self, system: &mut System, + hooks: &mut HooksStorage, tracer: &mut impl Tracer, ) -> InstructionResult { assert!(N <= MAX_EVENT_TOPICS); @@ -231,7 +233,8 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> { tracer.on_event(THIS_EE_TYPE, &self.address, &topics, data); - system.io.emit_event( + system.emit_event( + hooks, ExecutionEnvironmentType::EVM, self.gas.resources_mut(), &self.address, diff --git a/evm_interpreter/src/interpreter.rs b/evm_interpreter/src/interpreter.rs index 34df949a4..133f8d96b 100644 --- a/evm_interpreter/src/interpreter.rs +++ b/evm_interpreter/src/interpreter.rs @@ -4,6 +4,7 @@ use core::ops::Range; use errors::EvmSubsystemError; use native_resource_constants::STEP_NATIVE_COST; use ruint::aliases::B160; +use zk_ee::common_structs::system_hooks::HooksStorage; use zk_ee::memory::ArrayBuilder; use zk_ee::system::tracer::evm_tracer::EvmTracer; use zk_ee::system::tracer::Tracer; @@ -23,13 +24,14 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> { pub fn execute_till_yield_point<'a>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, tracer: &mut impl Tracer, ) -> Result, EvmSubsystemError> where S::IO: IOSubsystemExt, { let mut external_call = None; - let exit_code = self.run(system, &mut external_call, tracer)?; + let exit_code = self.run(system, hooks, &mut external_call, tracer)?; if let ExitCode::FatalError(e) = exit_code { return Err(e); @@ -112,6 +114,7 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> { pub fn run( &mut self, system: &mut System, + hooks: &mut HooksStorage, external_call_dest: &mut Option>, tracer: &mut impl Tracer, ) -> Result { @@ -286,11 +289,11 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> { opcodes::TSTORE => self.tstore(system, tracer), opcodes::MCOPY => self.mcopy(), opcodes::GAS => self.gas(), - opcodes::LOG0 => self.log::<0>(system, tracer), - opcodes::LOG1 => self.log::<1>(system, tracer), - opcodes::LOG2 => self.log::<2>(system, tracer), - opcodes::LOG3 => self.log::<3>(system, tracer), - opcodes::LOG4 => self.log::<4>(system, tracer), + opcodes::LOG0 => self.log::<0>(system, hooks, tracer), + opcodes::LOG1 => self.log::<1>(system, hooks, tracer), + opcodes::LOG2 => self.log::<2>(system, hooks, tracer), + opcodes::LOG3 => self.log::<3>(system, hooks, tracer), + opcodes::LOG4 => self.log::<4>(system, hooks, tracer), opcodes::SELFDESTRUCT => self.selfdestruct(system, tracer), opcodes::CHAINID => self.chainid(system), opcodes::BLOBHASH => self.blobhash(system), diff --git a/forward_system/src/run/mod.rs b/forward_system/src/run/mod.rs index 26339875a..9726d5ac5 100644 --- a/forward_system/src/run/mod.rs +++ b/forward_system/src/run/mod.rs @@ -21,17 +21,21 @@ use crate::run::query_processors::ZKProofDataResponder; use crate::run::query_processors::{BlockMetadataResponder, DACommitmentSchemeResponder}; use crate::run::result_keeper::ForwardRunningResultKeeper; use crate::system::bootloader::run_forward; +use crate::system::bootloader::run_prover_input_no_panic; use crate::system::system::CallSimulationBootloader; use crate::system::system::CallSimulationSystem; use crate::system::system::ForwardRunningSystem; use basic_bootloader::bootloader::config::{ BasicBootloaderCallSimulationConfig, BasicBootloaderForwardSimulationConfig, + BasicBootloaderProvingExecutionConfig, }; use errors::ForwardSubsystemError; use oracle_provider::MemorySource; use oracle_provider::ReadWitnessSource; use oracle_provider::ZkEENonDeterminismSource; +use result_keeper::ProverInputResultKeeper; use zk_ee::common_structs::ProofData; +use zk_ee::system::tracer::NopTracer; use zk_ee::system::tracer::Tracer; use zk_ee::utils::Bytes32; @@ -45,7 +49,6 @@ pub use preimage_source::PreimageSource; use zk_ee::wrap_error; use zksync_os_interface::traits::EncodedTx; -use std::path::PathBuf; pub use tx_result_callback::TxResultCallback; pub use tx_source::NextTxResponse; pub use tx_source::TxSource; @@ -94,16 +97,21 @@ pub fn run_block( - zk_os_program_path: PathBuf, +// Returns (prover_input, block_output, pubdata) +pub fn generate_proof_input< + T: ReadStorageTree, + PS: PreimageSource, + TS: TxSource, + TR: TxResultCallback, +>( block_context: BlockContext, proof_data: ProofData, da_commitment_scheme: DACommitmentScheme, tree: T, preimage_source: PS, tx_source: TS, -) -> Result, ForwardSubsystemError> { + tx_result_callback: TR, +) -> Result<(Vec, BlockOutput, Vec), ForwardSubsystemError> { let block_metadata_responder = BlockMetadataResponder { block_metadata: block_context, }; @@ -129,19 +137,25 @@ pub fn generate_proof_input( + copy_source, + &mut result_keeper, + &mut tracer, + ) + .map_err(|e| wrap_error!(e))?; + // Take pubdata, as it's not part of BlockOutput + let pubdata = std::mem::take(&mut result_keeper.pubdata); + + Ok((prover_input, result_keeper.into(), pubdata)) } // TODO(EVM-1184): in future we should generate input per batch @@ -220,6 +234,7 @@ pub fn make_oracle_for_proofs_and_dumps< proof_data: Option>, da_commitment_scheme: Option, add_uart: bool, + use_native_callable_oracles: bool, ) -> ZkEENonDeterminismSource { make_oracle_for_proofs_and_dumps_for_init_data( block_context, @@ -229,6 +244,7 @@ pub fn make_oracle_for_proofs_and_dumps< proof_data, da_commitment_scheme, add_uart, + use_native_callable_oracles, ) } @@ -245,6 +261,7 @@ pub fn make_oracle_for_proofs_and_dumps_for_init_data< proof_data: Option>, da_commitment_scheme: Option, add_uart: bool, + use_native_callable_oracles: bool, ) -> ZkEENonDeterminismSource { let block_metadata_responder = BlockMetadataResponder { block_metadata: block_context, @@ -269,10 +286,18 @@ pub fn make_oracle_for_proofs_and_dumps_for_init_data< oracle.add_external_processor(tree_responder); oracle.add_external_processor(zk_proof_data_responder); oracle.add_external_processor(da_commitment_scheme_responder); - oracle.add_external_processor(callable_oracles::arithmetic::ArithmeticQuery::default()); - oracle.add_external_processor( - callable_oracles::blob_kzg_commitment::BlobCommitmentAndProofQuery::default(), - ); + if use_native_callable_oracles { + oracle + .add_external_processor(callable_oracles::arithmetic::NativeArithmeticQuery::default()); + oracle.add_external_processor( + callable_oracles::blob_kzg_commitment::NativeBlobCommitmentAndProofQuery::default(), + ); + } else { + oracle.add_external_processor(callable_oracles::arithmetic::ArithmeticQuery::default()); + oracle.add_external_processor( + callable_oracles::blob_kzg_commitment::BlobCommitmentAndProofQuery::default(), + ); + } if add_uart { let uart_responder = UARTPrintResponder; diff --git a/forward_system/src/run/output.rs b/forward_system/src/run/output.rs index b483a9dcd..617ad68e6 100644 --- a/forward_system/src/run/output.rs +++ b/forward_system/src/run/output.rs @@ -24,6 +24,8 @@ use zk_ee::types_config::EthereumIOTypesConfig; pub use zksync_os_interface::types::BlockOutput; use zksync_os_interface::types::L2ToL1LogWithPreimage; +use super::result_keeper::ProverInputResultKeeper; + pub type TxResult = Result; /// Extension trait to create `StorageWrite` from components. @@ -53,7 +55,6 @@ impl From> for BlockOutput storage_writes, tx_results, new_preimages, - pubdata, .. } = value; @@ -134,12 +135,17 @@ impl From> for BlockOutput storage_writes, account_diffs, published_preimages, - pubdata, computaional_native_used: block_computaional_native_used, } } } +impl From> for BlockOutput { + fn from(value: ProverInputResultKeeper) -> Self { + BlockOutput::from(value.forward_running_rk) + } +} + /// Extract account diffs from a BlockOutput. /// /// This method processes the published preimages and storage writes to extract diff --git a/forward_system/src/run/result_keeper.rs b/forward_system/src/run/result_keeper.rs index 00dbe314e..85278131c 100644 --- a/forward_system/src/run/result_keeper.rs +++ b/forward_system/src/run/result_keeper.rs @@ -24,8 +24,6 @@ pub struct ForwardRunningResultKeeper { Result, >, pub new_preimages: Vec<(Bytes32, Vec, PreimageType)>, - pub pubdata: Vec, - pub tx_result_callback: TR, } @@ -38,7 +36,6 @@ impl ForwardRunningResultKeeper { storage_writes: vec![], tx_results: vec![], new_preimages: vec![], - pubdata: vec![], tx_result_callback, } } @@ -50,7 +47,7 @@ impl IOResultKeeper fn events<'a>( &mut self, iter: impl Iterator< - Item = GenericEventContentWithTxRef<'a, { MAX_EVENT_TOPICS }, EthereumIOTypesConfig>, + Item = GenericEventContentWithTxRef<'a, MAX_EVENT_TOPICS, EthereumIOTypesConfig>, >, ) { self.events = iter @@ -84,10 +81,6 @@ impl IOResultKeeper .map(|(hash, preimage, preimage_type)| (*hash, preimage.to_vec(), preimage_type)) .collect(); } - - fn pubdata(&mut self, value: &[u8]) { - self.pubdata.extend_from_slice(value); - } } impl ResultKeeperExt for ForwardRunningResultKeeper { @@ -123,3 +116,84 @@ impl ResultKeeperExt for ForwardRunningResultKeeper { .sum() } } + +/// +/// Result keeper for prover input run. +/// Adds pubdata to ForwardRunningResultKeeper +/// +pub struct ProverInputResultKeeper { + pub forward_running_rk: ForwardRunningResultKeeper, + pub pubdata: Vec, +} + +impl ProverInputResultKeeper { + pub fn new(tx_result_callback: TR) -> Self { + Self { + forward_running_rk: ForwardRunningResultKeeper::new(tx_result_callback), + pubdata: vec![], + } + } +} + +// Delegate to ForwardRunningResultKeeper, except for pubdata +impl IOResultKeeper for ProverInputResultKeeper { + fn events<'a>( + &mut self, + iter: impl Iterator< + Item = GenericEventContentWithTxRef<'a, MAX_EVENT_TOPICS, EthereumIOTypesConfig>, + >, + ) { + // TODO: delegating here causes an ICE. We reimplement this method. + self.forward_running_rk.events = iter + .map(|e| GenericEventContent { + tx_number: e.tx_number, + address: *e.address, + topics: e.topics.clone(), + data: UsizeAlignedByteBox::from_slice_in(e.data, Global), + }) + .collect(); + } + + fn logs<'a>( + &mut self, + iter: impl Iterator>, + ) { + self.forward_running_rk.logs(iter) + } + + fn storage_diffs(&mut self, iter: impl Iterator) { + self.forward_running_rk.storage_diffs(iter) + } + + fn new_preimages<'a>( + &mut self, + iter: impl Iterator, + ) { + self.forward_running_rk.new_preimages(iter) + } + + fn pubdata(&mut self, value: &[u8]) { + self.pubdata.extend_from_slice(value); + } +} + +// Delegate to ForwardRunningResultKeeper +impl ResultKeeperExt for ProverInputResultKeeper { + fn tx_processed( + &mut self, + tx_result: Result< + TxProcessingOutput, + basic_bootloader::bootloader::errors::InvalidTransaction, + >, + ) { + self.forward_running_rk.tx_processed(tx_result) + } + + fn block_sealed(&mut self, block_header: BlockHeader) { + self.forward_running_rk.block_sealed(block_header) + } + + fn get_gas_used(&self) -> u64 { + self.forward_running_rk.get_gas_used() + } +} diff --git a/forward_system/src/system/bootloader.rs b/forward_system/src/system/bootloader.rs index c13bd7fb6..7c953b526 100644 --- a/forward_system/src/system/bootloader.rs +++ b/forward_system/src/system/bootloader.rs @@ -2,7 +2,9 @@ use crate::system::system::*; use basic_bootloader::bootloader::config::BasicBootloaderExecutionConfig; use basic_bootloader::bootloader::errors::BootloaderSubsystemError; use basic_bootloader::bootloader::result_keeper::ResultKeeperExt; +use basic_system::system_implementation::system::BatchOutput; use oracle_provider::DummyMemorySource; +use oracle_provider::ReadWitnessSource; use oracle_provider::ZkEENonDeterminismSource; use zk_ee::system::tracer::Tracer; @@ -27,3 +29,12 @@ pub fn run_forward_no_panic( ) -> Result<(), BootloaderSubsystemError> { ForwardBootloader::run_prepared::(oracle, result_keeper, tracer).map(|_| ()) } + +pub fn run_prover_input_no_panic( + oracle: ReadWitnessSource, + result_keeper: &mut impl ResultKeeperExt, + tracer: &mut impl Tracer, +) -> Result<(Vec, BatchOutput), BootloaderSubsystemError> { + ProverInputBootloader::run_prepared::(oracle, result_keeper, tracer) + .map(|o| (o.0.get_read_items().borrow().clone(), o.2)) +} diff --git a/forward_system/src/system/system.rs b/forward_system/src/system/system.rs index 428c478f2..df90a5c12 100644 --- a/forward_system/src/system/system.rs +++ b/forward_system/src/system/system.rs @@ -19,11 +19,11 @@ type Logger = crate::system::logger::StdIOLogger; #[cfg(feature = "no_print")] type Logger = zk_ee::system::NullLogger; -pub struct ForwardSystemTypes(O); +pub struct ForwardSystemTypes(O); type Native = zk_ee::reference_implementations::DecreasingNative; -impl SystemTypes for ForwardSystemTypes { +impl SystemTypes for ForwardSystemTypes { type IOTypes = EthereumIOTypesConfig; type Resources = BaseResources; type IO = FullIO< @@ -33,21 +33,29 @@ impl SystemTypes for ForwardSystemTypes { VecStackFactory, 0, O, - false, + PROOF_ENV, >; - type SystemFunctions = NoStdSystemFunctions; - type SystemFunctionsExt = NoStdSystemFunctions; + // For PROOF_ENV=true, we can enable delegation-based modexp to capture prover input. + type SystemFunctions = NoStdSystemFunctions; + type SystemFunctionsExt = NoStdSystemFunctions; type Allocator = Global; type Logger = Logger; type Metadata = zk_ee::system::metadata::zk_metadata::ZkMetadata; } -impl EthereumLikeTypes for ForwardSystemTypes {} +impl EthereumLikeTypes for ForwardSystemTypes {} -pub type ForwardRunningSystem = ForwardSystemTypes>; +pub type ForwardRunningSystem = + ForwardSystemTypes, false>; -pub type CallSimulationSystem = ForwardSystemTypes>; +pub type CallSimulationSystem = + ForwardSystemTypes, false>; + +pub type ProverInputSystem = + ForwardSystemTypes, true>; pub type ForwardBootloader = BasicBootloader; pub type CallSimulationBootloader = BasicBootloader; + +pub type ProverInputBootloader = BasicBootloader; diff --git a/oracle_provider/src/lib.rs b/oracle_provider/src/lib.rs index 5d3308baf..17af59550 100644 --- a/oracle_provider/src/lib.rs +++ b/oracle_provider/src/lib.rs @@ -322,3 +322,42 @@ impl NonDeterminismCSRSource for ReadWitnessSource { self.original_source.write_with_memory_access(memory, value); } } + +impl IOOracle for ReadWitnessSource { + type RawIterator<'a> = + as IOOracle>::RawIterator<'a>; + + fn raw_query<'a, I>( + &'a mut self, + query_type: u32, + input: &I, + ) -> Result, InternalError> + where + I: UsizeSerializable + UsizeDeserializable, + { + let inner = self.original_source.raw_query(query_type, input)?; + // First add the length of the iterator. + let len = inner.len(); + { + let mut read_items = self.read_items.borrow_mut(); + // Len is multiplied by 2 to account for 32/64-bit mismatch + let len_u32 = u32::try_from(len * 2).expect("iterator length does not fit into u32"); + read_items.push(len_u32); + } + let read_items = Rc::clone(&self.read_items); + let wrapped: Self::RawIterator<'a> = Box::new(inner.inspect(move |v| { + record_usize_as_u32_words(&mut read_items.borrow_mut(), *v); + })); + + Ok(wrapped) + } +} + +fn record_usize_as_u32_words(dst: &mut Vec, value: usize) { + { + let v = value as u64; + // LE + dst.push((v & 0xFFFF_FFFF) as u32); + dst.push((v >> 32) as u32); + } +} diff --git a/proof_running_system/src/system/bootloader.rs b/proof_running_system/src/system/bootloader.rs index 07698a0f5..df8300802 100644 --- a/proof_running_system/src/system/bootloader.rs +++ b/proof_running_system/src/system/bootloader.rs @@ -5,7 +5,6 @@ use basic_bootloader::bootloader::config::BasicBootloaderProvingExecutionConfig; use core::alloc::Allocator; use core::mem::MaybeUninit; use zk_ee::memory::ZSTAllocator; -use zk_ee::oracle::query_ids::DISCONNECT_ORACLE_QUERY_ID; use zk_ee::oracle::IOOracle; use zk_ee::system::tracer::NopTracer; use zk_ee::system::{logger::Logger, NopResultKeeper}; @@ -179,17 +178,13 @@ pub fn run_proving_inner< let _ = L::default().write_fmt(format_args!("IO implementer init is complete")); // Load all transactions from oracle and apply them. - let (mut oracle, public_input) = ProvingBootloader::::run_prepared::< - BasicBootloaderProvingExecutionConfig, - >(oracle, &mut NopResultKeeper, &mut NopTracer::default()) - .expect("Tried to prove a failing batch"); - - // disconnect oracle before returning, if some other post-logic is needed that doesn't use Oracle trait - // TODO: check this is the intended behaviour (ignoring the result) - #[allow(unused_must_use)] - oracle - .raw_query_with_empty_input(DISCONNECT_ORACLE_QUERY_ID) - .expect("must disconnect an oracle before performing arbitrary CSR access"); + let (_oracle, public_input, _batch_output) = + ProvingBootloader::::run_prepared::( + oracle, + &mut NopResultKeeper, + &mut NopTracer::default(), + ) + .expect("Tried to prove a failing batch"); unsafe { core::mem::transmute(public_input) } } @@ -224,12 +219,6 @@ pub fn run_proving_inner< upgrade_tx_hash, &mut batch_pi_builder, ); - // we do this query for consistency with block based input generation(there is empty iterator as response to this query) - // but during proving this request shouldn't have the effect with "u32 array based" oracle - #[allow(unused_must_use)] - oracle - .raw_query_with_empty_input(DISCONNECT_ORACLE_QUERY_ID) - .expect("must disconnect an oracle before performing arbitrary CSR access"); } unsafe { diff --git a/proof_running_system/src/system/mod.rs b/proof_running_system/src/system/mod.rs index c2aa4d8f4..9c845e2a8 100644 --- a/proof_running_system/src/system/mod.rs +++ b/proof_running_system/src/system/mod.rs @@ -42,8 +42,8 @@ impl SystemTypes for ProofRunningSystemTypes; - type SystemFunctions = NoStdSystemFunctions; - type SystemFunctionsExt = NoStdSystemFunctions; + type SystemFunctions = NoStdSystemFunctions; + type SystemFunctionsExt = NoStdSystemFunctions; type Allocator = BootloaderAllocator; type Logger = L; type Metadata = zk_ee::system::metadata::zk_metadata::ZkMetadata; diff --git a/system_hooks/src/addresses_constants.rs b/system_hooks/src/addresses_constants.rs index 91ed4ba76..7ae46c940 100644 --- a/system_hooks/src/addresses_constants.rs +++ b/system_hooks/src/addresses_constants.rs @@ -32,6 +32,11 @@ pub const L1_MESSENGER_ADDRESS: B160 = B160::from_limbs([L1_MESSENGER_ADDRESS_LO pub const L2_BASE_TOKEN_ADDRESS_LOW: u16 = 0x800a; pub const L2_BASE_TOKEN_ADDRESS: B160 = B160::from_limbs([L2_BASE_TOKEN_ADDRESS_LOW as u64, 0, 0]); +// L2 interop root storage system contract +pub const L2_INTEROP_ROOT_STORAGE_ADDRESS_LOW: u32 = 0x10008; +pub const L2_INTEROP_ROOT_STORAGE_ADDRESS: B160 = + B160::from_limbs([L2_INTEROP_ROOT_STORAGE_ADDRESS_LOW as u64, 0, 0]); + // ERA VM system contracts (in fact we need implement only the methods that should be available for user contracts) // TODO: may be better to implement as ifs inside EraVM EE pub const ACCOUNT_CODE_STORAGE_STORAGE_ADDRESS: B160 = B160::from_limbs([0x8002, 0, 0]); diff --git a/system_hooks/src/interop_root_reporter.rs b/system_hooks/src/interop_root_reporter.rs new file mode 100644 index 000000000..04357b378 --- /dev/null +++ b/system_hooks/src/interop_root_reporter.rs @@ -0,0 +1,78 @@ +//! +//! Interop root reporter system hook implementation. +//! +use super::*; +use ruint::aliases::U256; +use zk_ee::types_config::SystemIOTypesConfig; +use zk_ee::{ + common_structs::interop_root_storage::InteropRoot, + execution_environment_type::ExecutionEnvironmentType, internal_error, + storage_types::MAX_EVENT_TOPICS, system::errors::system::SystemError, utils::Bytes32, +}; + +// InteropRootAdded(uint256,uint256,bytes32[]) - 6b451b8422636e45b93bf7f594fa2c1769d039766c4254a6e7f9c0ee1715cdb0 +pub const INTEROP_ROOT_ADDED_EVENT_SIG: [u8; 32] = [ + 0x6b, 0x45, 0x1b, 0x84, 0x22, 0x63, 0x6e, 0x45, 0xb9, 0x3b, 0xf7, 0xf5, 0x94, 0xfa, 0x2c, 0x17, + 0x69, 0xd0, 0x39, 0x76, 0x6c, 0x42, 0x54, 0xa6, 0xe7, 0xf9, 0xc0, 0xee, 0x17, 0x15, 0xcd, 0xb0, +]; + +pub fn interop_root_reporter_event_hook( + topics: &arrayvec::ArrayVec<::EventKey, MAX_EVENT_TOPICS>, + data: &[u8], + _caller_ee: u8, + system: &mut System, + resources: &mut S::Resources, +) -> Result<(), SystemError> +where +{ + // First, ensure we're capturing the InteropRootAdded event + if topics[0].as_u8_array() != INTEROP_ROOT_ADDED_EVENT_SIG { + return Ok(()); + } + // Internal error if the data supplied doesn't match the expected value + if data.len() != 96 { + return Err(internal_error!("Interop root reporter event hook received bad data").into()); + } + + // Parse data + let offset: u32 = match U256::from_be_slice(&data[..32]).try_into() { + Ok(offset) => offset, + Err(_) => { + return Err( + internal_error!("Interop root reporter event hook received bad offset").into(), + ); + } + }; + // This event is part of the system, but we check it anyways + if offset != 32 { + return Err(internal_error!("Interop root reporter event hook received bad offset").into()); + } + + let len: u32 = match U256::from_be_slice(&data[32..64]).try_into() { + Ok(offset) => offset, + Err(_) => { + return Err( + internal_error!("Interop root reporter event hook received bad length").into(), + ); + } + }; + // It should have exactly one side + if len != 1 { + return Err(internal_error!("Interop root reporter event hook received bad length").into()); + } + + let root = Bytes32::from_array(data[64..96].try_into().unwrap()); + let chain_id = U256::from_be_bytes(topics[1].as_u8_array()); + let block_or_batch_number = U256::from_be_bytes(topics[2].as_u8_array()); + system.io.add_interop_root( + ExecutionEnvironmentType::NoEE, + resources, + InteropRoot { + root, + block_or_batch_number, + chain_id, + }, + )?; + + Ok(()) +} diff --git a/system_hooks/src/lib.rs b/system_hooks/src/lib.rs index 61b3b3217..b1976299b 100644 --- a/system_hooks/src/lib.rs +++ b/system_hooks/src/lib.rs @@ -30,16 +30,16 @@ extern crate alloc; use crate::addresses_constants::*; use crate::contract_deployer::contract_deployer_hook; +use crate::interop_root_reporter::interop_root_reporter_event_hook; use crate::l1_messenger::l1_messenger_hook; use crate::l2_base_token::l2_base_token_hook; -use alloc::collections::BTreeMap; use core::marker::PhantomData; use core::{alloc::Allocator, mem::MaybeUninit}; use evm_interpreter::ERGS_PER_GAS; use precompiles::{pure_system_function_hook_impl, IdentityPrecompile, IdentityPrecompileErrors}; +use zk_ee::common_structs::system_hooks::{HooksStorage, SystemCallHook, SystemEventHook}; use zk_ee::common_traits::TryExtend; use zk_ee::system::errors::subsystem::SubsystemError; -use zk_ee::system::errors::system::SystemError; #[cfg(feature = "mock-unsupported-precompiles")] use zk_ee::system::MissingSystemFunctionErrors; use zk_ee::{ @@ -59,26 +59,11 @@ pub mod addresses_constants; mod mock_precompiles; pub mod contract_deployer; +pub mod interop_root_reporter; pub mod l1_messenger; pub mod l2_base_token; mod precompiles; -/// System hooks process the given call request. -/// -/// The inputs are: -/// - call request -/// - caller ee(logic may depend on it some cases) -/// - system -/// - output buffer -pub struct SystemHook( - for<'a> fn( - ExternalCallRequest, - u8, - &mut System, - &'a mut [MaybeUninit], - ) -> Result<(CompletedExecution<'a, S>, &'a mut [MaybeUninit]), SystemError>, -); - pub trait SystemFunctionInvocation where S::IO: IOSubsystemExt, @@ -139,162 +124,158 @@ where } /// -/// System hooks storage. -/// Stores hooks implementations and processes calls to system addresses. +/// Adds EVM precompiles hooks. /// -pub struct HooksStorage { - inner: BTreeMap, A>, -} - -impl HooksStorage { - /// - /// Creates empty hooks storage with a given allocator. - /// - pub fn new_in(allocator: A) -> Self { - Self { - inner: BTreeMap::new_in(allocator), - } - } +pub fn add_precompiles(hooks: &mut HooksStorage) +where + S::IO: IOSubsystemExt, +{ + add_precompile::< + _, + _, + >::Secp256k1ECRecover, + Secp256k1ECRecoverErrors, + >(hooks, ECRECOVER_HOOK_ADDRESS_LOW); + add_precompile::<_, _, >::Sha256, Sha256Errors>( + hooks, + SHA256_HOOK_ADDRESS_LOW, + ); + add_precompile::<_, _, >::RipeMd160, RipeMd160Errors>( + hooks, + RIPEMD160_HOOK_ADDRESS_LOW, + ); + add_precompile::<_, _, IdentityPrecompile, IdentityPrecompileErrors>( + hooks, + ID_HOOK_ADDRESS_LOW, + ); + add_precompile_ext::< + _, + _, + >::ModExp, + ModExpErrors, + >(hooks, MODEXP_HOOK_ADDRESS_LOW); + add_precompile::<_, _, >::Bn254Add, Bn254AddErrors>( + hooks, + ECADD_HOOK_ADDRESS_LOW, + ); + add_precompile::<_, _, >::Bn254Mul, Bn254MulErrors>( + hooks, + ECMUL_HOOK_ADDRESS_LOW, + ); + add_precompile::< + _, + _, + >::Bn254PairingCheck, + Bn254PairingCheckErrors, + >(hooks, ECPAIRING_HOOK_ADDRESS_LOW); + #[cfg(feature = "mock-unsupported-precompiles")] + { + add_precompile::< + _, + _, + crate::mock_precompiles::mock_precompiles::Blake2f, + MissingSystemFunctionErrors, + >(hooks, BLAKE2F_HOOK_ADDRESS_LOW); - /// - /// Adds a new hook into a given address. - /// Fails if there was another hook registered there before. - /// - pub fn add_hook(&mut self, for_address_low: u16, hook: SystemHook) { - let existing = self.inner.insert(for_address_low, hook); - // TODO: internal error? - assert!(existing.is_none()); + #[cfg(not(feature = "point_eval_precompile"))] + add_precompile::< + _, + _, + crate::mock_precompiles::mock_precompiles::PointEvaluation, + MissingSystemFunctionErrors, + >(hooks, POINT_EVAL_HOOK_ADDRESS_LOW); } + #[cfg(feature = "point_eval_precompile")] + add_precompile::< + _, + _, + >::PointEvaluation, + PointEvaluationErrors, + >(hooks, POINT_EVAL_HOOK_ADDRESS_LOW); - /// - /// Intercepts calls to low addresses (< 2^16) and executes hooks - /// stored under that address. If no hook is stored there, return `Ok(None)`. - /// Always return unused return_memory. - /// - pub fn try_intercept<'a>( - &mut self, - address_low: u16, - request: ExternalCallRequest, - caller_ee: u8, - system: &mut System, - return_memory: &'a mut [MaybeUninit], - ) -> Result<(Option>, &'a mut [MaybeUninit]), SystemError> { - let Some(hook) = self.inner.get(&address_low) else { - return Ok((None, return_memory)); - }; - let (res, remaining_memory) = hook.0(request, caller_ee, system, return_memory)?; - - Ok((Some(res), remaining_memory)) + #[cfg(feature = "p256_precompile")] + { + add_precompile::< + _, + _, + >::P256Verify, + P256VerifyErrors, + >(hooks, P256_VERIFY_PREHASH_HOOK_ADDRESS_LOW); } +} - /// - /// Checks if there is a hook stored for a given low address (<16 bits). - /// - pub fn has_hook_for(&mut self, address_low: u16) -> bool { - self.inner.contains_key(&address_low) - } +pub fn add_l1_messenger( + hooks: &mut HooksStorage, +) { + hooks.add_call_hook( + L1_MESSENGER_ADDRESS_LOW, + SystemCallHook::new(l1_messenger_hook), + ) } -impl HooksStorage +pub fn add_l2_base_token(hooks: &mut HooksStorage) where S::IO: IOSubsystemExt, { - /// - /// Adds EVM precompiles hooks. - /// - pub fn add_precompiles(&mut self) { - self.add_precompile::<>::Secp256k1ECRecover, Secp256k1ECRecoverErrors>( - ECRECOVER_HOOK_ADDRESS_LOW, - ); - self.add_precompile::<>::Sha256, Sha256Errors>( - SHA256_HOOK_ADDRESS_LOW, - ); - self.add_precompile::<>::RipeMd160, RipeMd160Errors>( - RIPEMD160_HOOK_ADDRESS_LOW, - ); - self.add_precompile::(ID_HOOK_ADDRESS_LOW); - self.add_precompile_ext::<>::ModExp, ModExpErrors>( - MODEXP_HOOK_ADDRESS_LOW, - ); - self.add_precompile::<>::Bn254Add, Bn254AddErrors>( - ECADD_HOOK_ADDRESS_LOW, - ); - self.add_precompile::<>::Bn254Mul, Bn254MulErrors>( - ECMUL_HOOK_ADDRESS_LOW, - ); - self.add_precompile::<>::Bn254PairingCheck, Bn254PairingCheckErrors>( - ECPAIRING_HOOK_ADDRESS_LOW, - ); - #[cfg(feature = "mock-unsupported-precompiles")] - { - self.add_precompile::( - BLAKE2F_HOOK_ADDRESS_LOW, - ); - - #[cfg(not(feature = "point_eval_precompile"))] - self.add_precompile::( - POINT_EVAL_HOOK_ADDRESS_LOW, - ); - } - #[cfg(feature = "point_eval_precompile")] - self.add_precompile::<>::PointEvaluation, PointEvaluationErrors>( - POINT_EVAL_HOOK_ADDRESS_LOW, - ); - - #[cfg(feature = "p256_precompile")] - { - self.add_precompile::<>::P256Verify, P256VerifyErrors>( - P256_VERIFY_PREHASH_HOOK_ADDRESS_LOW, - ); - } - } - - pub fn add_l1_messenger(&mut self) { - self.add_hook(L1_MESSENGER_ADDRESS_LOW, SystemHook(l1_messenger_hook)) - } - - pub fn add_l2_base_token(&mut self) { - self.add_hook(L2_BASE_TOKEN_ADDRESS_LOW, SystemHook(l2_base_token_hook)) - } + hooks.add_call_hook( + L2_BASE_TOKEN_ADDRESS_LOW, + SystemCallHook::new(l2_base_token_hook), + ) +} - pub fn add_contract_deployer(&mut self) { - self.add_hook( - CONTRACT_DEPLOYER_ADDRESS_LOW, - SystemHook(contract_deployer_hook), - ) - } +pub fn add_contract_deployer( + hooks: &mut HooksStorage, +) where + S::IO: IOSubsystemExt, +{ + hooks.add_call_hook( + CONTRACT_DEPLOYER_ADDRESS_LOW, + SystemCallHook::new(contract_deployer_hook), + ) +} - fn add_precompile(&mut self, address_low: u16) - where - P: SystemFunction, - E: Subsystem, - { - self.add_hook( - address_low, - SystemHook( - pure_system_function_hook_impl::, E, S>, - ), - ) - } +pub fn add_interop_root_reporter( + hooks: &mut HooksStorage, +) { + hooks.add_event_hook( + L2_INTEROP_ROOT_STORAGE_ADDRESS_LOW, + SystemEventHook::new(interop_root_reporter_event_hook), + ) +} - fn add_precompile_ext, E: Subsystem>( - &mut self, - address_low: u16, - ) { - self.add_hook( - address_low, - SystemHook( - pure_system_function_hook_impl::, E, S>, - ), - ) - } +fn add_precompile( + hooks: &mut HooksStorage, + address_low: u16, +) where + S::IO: IOSubsystemExt, + P: SystemFunction, + E: Subsystem, +{ + hooks.add_call_hook( + address_low, + SystemCallHook::new( + pure_system_function_hook_impl::, E, S>, + ), + ) +} - // /// - // /// Adds nonce holder system hook. - // /// - // pub fn add_nonce_holder(&mut self) { - // self.add_hook(NONCE_HOLDER_HOOK_ADDRESS_LOW, nonce_holder_hook) - // } +fn add_precompile_ext< + S: EthereumLikeTypes, + A: Allocator + Clone, + P: SystemFunctionExt, + E: Subsystem, +>( + hooks: &mut HooksStorage, + address_low: u16, +) where + S::IO: IOSubsystemExt, +{ + hooks.add_call_hook( + address_low, + SystemCallHook::new( + pure_system_function_hook_impl::, E, S>, + ), + ) } /// diff --git a/tests/evm_tester/Cargo.lock b/tests/evm_tester/Cargo.lock index ed066f6f1..028ac2f41 100644 --- a/tests/evm_tester/Cargo.lock +++ b/tests/evm_tester/Cargo.lock @@ -8782,6 +8782,8 @@ name = "zksync_os_api" version = "0.1.0" dependencies = [ "alloy", + "alloy-sol-types", + "basic_bootloader", "basic_system", "callable_oracles", "crypto", @@ -8798,14 +8800,12 @@ dependencies = [ [[package]] name = "zksync_os_evm_errors" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3ad55a8aa3498e304565de55b61dc930bac227c8c5e81284b623c69f2143d2" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" [[package]] name = "zksync_os_interface" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3476c481db1ea2db28ed8cbdfef391576288cb9e13241f7944274b72cf1e251e" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" dependencies = [ "alloy-consensus", "alloy-primitives", diff --git a/tests/fuzzer/Cargo.lock b/tests/fuzzer/Cargo.lock index bf6c91e3e..e6b3faf77 100644 --- a/tests/fuzzer/Cargo.lock +++ b/tests/fuzzer/Cargo.lock @@ -1869,6 +1869,7 @@ dependencies = [ "serde", "storage_models", "strum_macros 0.27.2", + "zerocopy", "zk_ee", ] @@ -10080,6 +10081,8 @@ name = "zksync_os_api" version = "0.1.0" dependencies = [ "alloy", + "alloy-sol-types", + "basic_bootloader", "basic_system", "callable_oracles", "crypto", @@ -10102,14 +10105,12 @@ checksum = "95ddc4ac82441385017e569ee1b060502a2af4d78c4fb7b24bcee70b0b96e6a3" [[package]] name = "zksync_os_evm_errors" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3ad55a8aa3498e304565de55b61dc930bac227c8c5e81284b623c69f2143d2" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" [[package]] name = "zksync_os_interface" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3476c481db1ea2db28ed8cbdfef391576288cb9e13241f7944274b72cf1e251e" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" dependencies = [ "alloy-consensus", "alloy-primitives", diff --git a/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_execute_frame.rs b/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_execute_frame.rs index 48a85c99f..22e561d3c 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_execute_frame.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_execute_frame.rs @@ -120,9 +120,14 @@ fn fuzz(input: FuzzInput) { ) else { return; }; + let mut hooks_storage = zk_ee::common_structs::system_hooks::HooksStorage::< + ForwardRunningSystem, + _, + >::new_in(system.get_allocator()); let _ = vm_state.start_executing_frame( &mut system, + &mut hooks_storage, ee_launch_params, heap, &mut NopTracer::default(), diff --git a/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_supported_ees.rs b/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_supported_ees.rs index a71badb53..baa127557 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_supported_ees.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/bootloader/bootloader_supported_ees.rs @@ -169,8 +169,14 @@ fn fuzz(input: FuzzInput) { return; }; + let mut hooks_storage = zk_ee::common_structs::system_hooks::HooksStorage::< + ForwardRunningSystem, + _, + >::new_in(system.get_allocator()); + let _ = vm_state.start_executing_frame( &mut system, + &mut hooks_storage, ee_launch_params, heap, &mut NopTracer::default(), diff --git a/tests/fuzzer/fuzz/fuzz_targets/bootloader/common/mod.rs b/tests/fuzzer/fuzz/fuzz_targets/bootloader/common/mod.rs index 800cd1ccc..71788387e 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/bootloader/common/mod.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/bootloader/common/mod.rs @@ -214,6 +214,7 @@ pub fn mock_oracle() -> ( init_data, None, true, + false, ), ) } @@ -275,6 +276,7 @@ pub fn mock_oracle_balance( init_data, None, true, + false, ), ) } diff --git a/tests/fuzzer/fuzz/fuzz_targets/precompiles_diff/modexp.rs b/tests/fuzzer/fuzz/fuzz_targets/precompiles_diff/modexp.rs index 4c2727cc4..b8981df1a 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/precompiles_diff/modexp.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/precompiles_diff/modexp.rs @@ -1,18 +1,18 @@ #![no_main] #![feature(allocator_api)] +use arbitrary::{Arbitrary, Unstructured}; +use basic_system::system_functions::modexp::ModExpImpl; +use basic_system_proving::system_functions::modexp::advice::delegated_modexp_with_naive_advisor; use libfuzzer_sys::fuzz_target; use revm::primitives::U256; use revm_precompile::modexp::berlin_run; -use arbitrary::{Arbitrary, Unstructured}; -use zk_ee::system::Resource; -use basic_system_proving::system_functions::modexp::delegation::delegated_modexp_with_naive_advisor; -use zk_ee::system::base_system_functions::ModExpErrors; -use zk_ee::system::errors::subsystem::SubsystemError; -use basic_system::system_functions::modexp::ModExpImpl; use zk_ee::reference_implementations::BaseResources; -use zk_ee::system::{SystemFunction,SystemFunctionExt}; use zk_ee::reference_implementations::DecreasingNative; +use zk_ee::system::base_system_functions::ModExpErrors; +use zk_ee::system::errors::subsystem::SubsystemError; +use zk_ee::system::Resource; +use zk_ee::system::{SystemFunction, SystemFunctionExt}; #[derive(Arbitrary, Debug, Clone, Copy)] enum LenMode { @@ -63,8 +63,8 @@ struct Input { // Raw entropy to fill buffers base_seed: Vec, - exp_seed: Vec, - mod_seed: Vec, + exp_seed: Vec, + mod_seed: Vec, // Random lengths used when LenMode::Random bl_rand: u32, @@ -95,13 +95,17 @@ fn shape_exponent(mut exp: Vec, ek: ExpKind) -> Vec { ExpKind::Random => exp, ExpKind::Zero => vec![0u8; exp.len()], ExpKind::One => { - if exp.is_empty() { return exp; } + if exp.is_empty() { + return exp; + } let mut v = vec![0u8; exp.len()]; *v.last_mut().unwrap() = 1; v } ExpKind::Two => { - if exp.is_empty() { return exp; } + if exp.is_empty() { + return exp; + } let mut v = vec![0u8; exp.len()]; *v.last_mut().unwrap() = 2; v @@ -112,8 +116,8 @@ fn shape_exponent(mut exp: Vec, ek: ExpKind) -> Vec { } let mut v = vec![0u8; exp.len()]; let n = v.len(); - v[n-3] = 0x01; - v[n-1] = 0x01; + v[n - 3] = 0x01; + v[n - 1] = 0x01; v } } @@ -124,20 +128,26 @@ fn shape_modulus(mut m: Vec, mk: ModKind) -> Vec { ModKind::Random => m, ModKind::Zero => vec![0u8; m.len()], ModKind::One => { - if m.is_empty() { return m; } + if m.is_empty() { + return m; + } let mut v = vec![0u8; m.len()]; *v.last_mut().unwrap() = 1; v } ModKind::PowerOfTwo => { - if m.is_empty() { return m; } + if m.is_empty() { + return m; + } let mut v = vec![0u8; m.len()]; let bit = 1u8 << (v.len() as u8 % 8); *v.last_mut().unwrap() = bit.max(1); v } ModKind::Odd => { - if m.is_empty() { return m; } + if m.is_empty() { + return m; + } let mut v = m; *v.last_mut().unwrap() |= 1; v @@ -157,12 +167,12 @@ fn fill_len(u: &mut Unstructured<'_>, len: usize, seed: &[u8]) -> Vec { fn mutate_len(chosen: u32, mode: LenMode, rnd: u32) -> u32 { match mode { - LenMode::Exact => chosen, + LenMode::Exact => chosen, LenMode::OffByOneShort => chosen.saturating_sub(1), - LenMode::OffByOneLong => chosen.saturating_add(1).min(MAX_COMPONENT_LEN), - LenMode::MuchShorter => chosen / 2, - LenMode::MuchLonger => chosen.saturating_mul(2).min(MAX_COMPONENT_LEN), - LenMode::Random => rnd.min(MAX_DECL_LEN), + LenMode::OffByOneLong => chosen.saturating_add(1).min(MAX_COMPONENT_LEN), + LenMode::MuchShorter => chosen / 2, + LenMode::MuchLonger => chosen.saturating_mul(2).min(MAX_COMPONENT_LEN), + LenMode::Random => rnd.min(MAX_DECL_LEN), } } @@ -179,21 +189,21 @@ fn normalize_be(s: &[u8]) -> &[u8] { fn build_input_bytes( mut u: &mut Unstructured<'_>, - i: &Input + i: &Input, ) -> (Vec, Vec, Vec, Vec) { let bl = i.bl as usize; let el = i.el as usize; let ml = i.ml as usize; let base = fill_len(&mut u, bl, &i.base_seed); - let exp_raw = fill_len(&mut u, el, &i.exp_seed); - let mod_raw = fill_len(&mut u, ml, &i.mod_seed); + let exp_raw = fill_len(&mut u, el, &i.exp_seed); + let mod_raw = fill_len(&mut u, ml, &i.mod_seed); let mbl = mutate_len(bl as u32, i.bl_lm, i.bl_rand); let mel = mutate_len(el as u32, i.el_lm, i.el_rand); let mml = mutate_len(ml as u32, i.ml_lm, i.ml_rand); - let exp = shape_exponent(exp_raw.clone(), i.ek); + let exp = shape_exponent(exp_raw.clone(), i.ek); let modu = shape_modulus(mod_raw.clone(), i.mk); let mut out = Vec::with_capacity(96 + bl + el + ml); @@ -206,7 +216,9 @@ fn build_input_bytes( if let Some(t) = i.max_len { let t = t as usize; - if t < out.len() { out.truncate(t); } + if t < out.len() { + out.truncate(t); + } } (out, base, exp_raw, mod_raw) } @@ -244,7 +256,7 @@ fn fuzz(data: &[u8]) { pub fn modexp_forward(src: &[u8], dst: &mut Vec) -> Result<(), SubsystemError> { let allocator = std::alloc::Global; let mut resource = as Resource>::FORMAL_INFINITE; - ModExpImpl::execute( + ModExpImpl::::execute( &src, dst, &mut resource, @@ -264,11 +276,15 @@ struct DummyOracle {} impl zk_ee::oracle::IOOracle for DummyOracle { type RawIterator<'a> = Box + 'static>; - fn raw_query<'a, I: zk_ee::oracle::usize_serialization::UsizeSerializable + zk_ee::oracle::usize_serialization::UsizeDeserializable>( + fn raw_query< + 'a, + I: zk_ee::oracle::usize_serialization::UsizeSerializable + + zk_ee::oracle::usize_serialization::UsizeDeserializable, + >( &'a mut self, _query_type: u32, _input: &I, ) -> Result, zk_ee::system::errors::internal::InternalError> { unreachable!("oracle should not be consulted on native targets"); } -} \ No newline at end of file +} diff --git a/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp.rs b/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp.rs index 8bc59ec25..da934d203 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp.rs @@ -97,7 +97,7 @@ fn fuzz(data: &[u8]) { let mut dst = dst.clone(); - let _ = ModExpImpl::execute( + let _ = ModExpImpl::::execute( &src.to_bytes().as_slice()[0..src.n], &mut dst, &mut resource, @@ -113,7 +113,11 @@ struct DummyOracle {} impl zk_ee::oracle::IOOracle for DummyOracle { type RawIterator<'a> = Box + 'static>; - fn raw_query<'a, I: zk_ee::oracle::usize_serialization::UsizeSerializable + zk_ee::oracle::usize_serialization::UsizeDeserializable>( + fn raw_query< + 'a, + I: zk_ee::oracle::usize_serialization::UsizeSerializable + + zk_ee::oracle::usize_serialization::UsizeDeserializable, + >( &'a mut self, _query_type: u32, _input: &I, @@ -125,4 +129,4 @@ impl zk_ee::oracle::IOOracle for DummyOracle { fuzz_target!(|data: &[u8]| { // call fuzzing in a separate function, so we can see its coverage fuzz(data); -}); \ No newline at end of file +}); diff --git a/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp_delegation.rs b/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp_delegation.rs index e8eac8008..e5f5f3033 100644 --- a/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp_delegation.rs +++ b/tests/fuzzer/fuzz/fuzz_targets/system_functions/modexp_delegation.rs @@ -2,7 +2,7 @@ #![feature(allocator_api)] use arbitrary::{Arbitrary, Unstructured}; -use basic_system::system_functions::modexp::delegation::delegated_modexp_with_naive_advisor; +use basic_system::system_functions::modexp::advice::delegated_modexp_with_naive_advisor; use libfuzzer_sys::fuzz_target; use ruint::aliases::U256; use std::alloc::Global; diff --git a/tests/instances/eth_runner/Cargo.lock b/tests/instances/eth_runner/Cargo.lock index 616ee269e..0bf00b1ee 100644 --- a/tests/instances/eth_runner/Cargo.lock +++ b/tests/instances/eth_runner/Cargo.lock @@ -75,9 +75,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e15860af634cad451f598712c24ca7fd9b45d84fff68ab8d4967567fa996c64" +checksum = "f07655fedc35188f3c50ff8fc6ee45703ae14ef1bc7ae7d80e23a747012184e3" dependencies = [ "alloy-consensus", "alloy-contract", @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc32535569185cbcb6ad5fa64d989a47bccb9a08e27284b1f2a3ccf16e6d010" +checksum = "ff8c665521d11efbb11d5e5c5d63971426bb63df00d24545baf97e7f3dc91c0c" dependencies = [ "alloy-primitives", "num_enum", @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6440213a22df93a87ed512d2f668e7dc1d62a05642d107f82d61edc9e12370" +checksum = "2e318e25fb719e747a7e8db1654170fc185024f3ed5b10f86c08d448a912f6e2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -129,7 +129,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.0", "either", "k256", "once_cell", @@ -143,9 +143,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d0bea09287942405c4f9d2a4f22d1e07611c2dbd9d5bf94b75366340f9e6e0" +checksum = "364380a845193a317bcb7a5398fc86cdb66c47ebe010771dde05f6869bf9e64a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69af404f1d00ddb42f2419788fa87746a4cd13bab271916d7726fda6c792d94" +checksum = "08d39c80ffc806f27a76ed42f3351a455f3dc4f81d6ff92c8aad2cf36b7d3a34" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -201,7 +201,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", - "derive_more 2.0.1", + "derive_more 2.1.0", "itoa", "serde", "serde_json", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd2c7ae05abcab4483ce821f12f285e01c0b33804e6883dd9ca1569a87ee2be" +checksum = "a4c4d7c5839d9f3a467900c625416b24328450c65702eb3d8caff8813e4d1d33" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -262,7 +262,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.0.1", + "derive_more 2.1.0", "either", "serde", "serde_with", @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc47eaae86488b07ea8e20236184944072a78784a1f4993f8ec17b3aa5d08c21" +checksum = "1ba4b1be0988c11f0095a2380aa596e35533276b8fa6c9e06961bbfe0aebcac5" dependencies = [ "alloy-eips", "alloy-primitives", @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003f46c54f22854a32b9cc7972660a476968008ad505427eabab49225309ec40" +checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4029954d9406a40979f3a3b46950928a0fdcfe3ea8a9b0c17490d57e8aa0e3" +checksum = "12aeb37b6f2e61b93b1c3d34d01ee720207c76fe447e2a2c217e433ac75b17f5" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -331,7 +331,7 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.0.1", + "derive_more 2.1.0", "futures-utils-wasm", "serde", "serde_json", @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7805124ad69e57bbae7731c9c344571700b2a18d351bda9e0eba521c991d1bcb" +checksum = "abd29ace62872083e30929cd9b282d82723196d196db589f3ceda67edcc05552" dependencies = [ "alloy-consensus", "alloy-eips", @@ -361,7 +361,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more 2.0.1", + "derive_more 2.1.0", "foldhash 0.2.0", "hashbrown 0.16.1", "indexmap 2.12.1", @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369e12c92870d069e0c9dc5350377067af8a056e29e3badf8446099d7e00889" +checksum = "9b710636d7126e08003b8217e24c09f0cca0b46d62f650a841736891b1ed1fc1" dependencies = [ "alloy-chains", "alloy-consensus", @@ -414,7 +414,7 @@ dependencies = [ "lru", "parking_lot 0.12.5", "pin-project 1.1.10", - "reqwest 0.12.24", + "reqwest 0.12.25", "serde", "serde_json", "thiserror 2.0.17", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77d20cdbb68a614c7a86b3ffef607b37d087bb47a03c58f4c3f8f99bc3ace3b" +checksum = "cdd4c64eb250a18101d22ae622357c6b505e158e9165d4c7974d59082a600c5e" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -470,9 +470,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c89883fe6b7381744cbe80fef638ac488ead4f1956a4278956a1362c71cd2e" +checksum = "d0882e72d2c1c0c79dcf4ab60a67472d3f009a949f774d4c17d0bdb669cfde05" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -483,7 +483,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project 1.1.10", - "reqwest 0.12.24", + "reqwest 0.12.25", "serde", "serde_json", "tokio", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e279e6d40ee40fe8f76753b678d8d5d260cb276dc6c8a8026099b16d2b43f4" +checksum = "39cf1398cb33aacb139a960fa3d8cf8b1202079f320e77e952a0b95967bf7a9f" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e176c26fdd87893b6afeb5d92099d8f7e7a1fe11d6f4fe0883d6e33ac5f31ba" +checksum = "c3ce4c24e416bd0f17fceeb2f26cd8668df08fe19e1dc02f9d41c3b8ed1e93e0" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -525,9 +525,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b43c1622aac2508d528743fd4cfdac1dea92d5a8fa894038488ff7edd0af0b32" +checksum = "6a63fb40ed24e4c92505f488f9dd256e2afaed17faa1b7a221086ebba74f4122" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -536,28 +536,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2ca3a434a6d49910a7e8e51797eb25db42ef8a5578c52d877fcb26d0afe7bc" +checksum = "4936f579d9d10eae01772b2ab3497f9d568684f05f26f8175e12f9a1a2babc33" dependencies = [ "alloy-primitives", - "derive_more 2.0.1", + "derive_more 2.1.0", "serde", "serde_with", ] [[package]] name = "alloy-rpc-types-engine" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4c53a8b0905d931e7921774a1830609713bd3e8222347963172b03a3ecc68" +checksum = "4c60bdce3be295924122732b7ecd0b2495ce4790bedc5370ca7019c08ad3f26e" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more 2.0.1", + "derive_more 2.1.0", "rand 0.8.5", "serde", "strum 0.27.2", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5fafb741c19b3cca4cdd04fa215c89413491f9695a3e928dee2ae5657f607e" +checksum = "9eae0c7c40da20684548cbc8577b6b7447f7bf4ddbac363df95e3da220e41e72" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55324323aa634b01bdecb2d47462a8dce05f5505b14a6e5db361eef16eda476" +checksum = "ef206a4b8d436fbb7cf2e6a61c692d11df78f9382becc3c9a283bd58e64f0583" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -600,9 +600,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b1aa28effb6854be356ce92ed64cea3b323acd04c3f8bfb5126e2839698043" +checksum = "ecb5a795264a02222f9534435b8f40dcbd88de8e9d586647884aae24f389ebf2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f180c399ca7c1e2fe17ea58343910cad0090878a696ff5a50241aee12fc529" +checksum = "c0df1987ed0ff2d0159d76b52e7ddfc4e4fbddacc54d2fbee765e0d14d7c01b5" dependencies = [ "alloy-primitives", "serde", @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc39ad2c0a3d2da8891f4081565780703a593f090f768f884049aa3aa929cbc" +checksum = "6ff69deedee7232d7ce5330259025b868c5e6a52fa8dffda2c861fb3a5889b24" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75411104af460ca0b306ae998f0a00b5159457780487630f4b24722beae6b690" +checksum = "bc8784b7567d5cfdad7450c5c71ddbdc12b288b82ea559be7a5363c28f774210" dependencies = [ "alloy-consensus", "alloy-network", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd808e99893245e619babcc138af07191ded72dc42877e216006b1ebcae64a7" +checksum = "70059cb6f08c225be3840f8104573fd9342ca29117a735ea09ed8ee1be09fb9f" dependencies = [ "alloy-consensus", "alloy-network", @@ -677,9 +677,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c059a3bbab204a06188ab27efad308b3f549c886a7853eaa64a79f76b453cae" +checksum = "a54e01fb2228c993c9ef7735043bb22c88a84502628fae820d6469c3bebddb84" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -697,9 +697,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930e17cb1e46446a193a593a3bfff8d0ecee4e510b802575ebe300ae2e43ef75" +checksum = "72cfe0be3ec5a8c1a46b2e5a7047ed41121d360d97f4405bb7c1c784880c86cb" dependencies = [ "alloy-consensus", "alloy-network", @@ -713,9 +713,9 @@ dependencies = [ [[package]] name = "alloy-signer-turnkey" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f7c8b58378cede78b4a950a8a372779dfab1b22a2f096f89007616d87ea86" +checksum = "dd65beb3a838bff2e528e2ac4f0cf14f55893c4f0bc67c8339f2752b3d144022" dependencies = [ "alloy-consensus", "alloy-network", @@ -802,14 +802,14 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae82426d98f8bc18f53c5223862907cac30ab8fc5e4cd2bb50808e6d3ab43d8" +checksum = "be98b07210d24acf5b793c99b759e9a696e4a2e67593aec0487ae3b3e1a2478c" dependencies = [ "alloy-json-rpc", "auto_impl", "base64 0.22.1", - "derive_more 2.0.1", + "derive_more 2.1.0", "futures", "futures-utils-wasm", "parking_lot 0.12.5", @@ -825,13 +825,13 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90aa6825760905898c106aba9c804b131816a15041523e80b6d4fe7af6380ada" +checksum = "4198a1ee82e562cab85e7f3d5921aab725d9bd154b6ad5017f82df1695877c97" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest 0.12.24", + "reqwest 0.12.25", "serde_json", "tower", "tracing", @@ -840,9 +840,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ace83a4a6bb896e5894c3479042e6ba78aa5271dde599aa8c36a021d49cc8cc" +checksum = "d8db249779ebc20dc265920c7e706ed0d31dbde8627818d1cbde60919b875bb0" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -860,9 +860,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c9ab4c199e3a8f3520b60ba81aa67bb21fed9ed0d8304e0569094d0758a56f" +checksum = "5ad2344a12398d7105e3722c9b7a7044ea837128e11d453604dec6e3731a86e2" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -885,7 +885,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 2.0.1", + "derive_more 2.1.0", "nybbles", "serde", "smallvec", @@ -894,9 +894,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae109e33814b49fc0a62f2528993aa8a2dd346c26959b151f05441dc0b9da292" +checksum = "333544408503f42d7d3792bfc0f7218b643d968a03d2c0ed383ae558fb4a76d0" dependencies = [ "darling", "proc-macro2", @@ -1249,9 +1249,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473" +checksum = "07a926debf178f2d355197f9caddb08e54a9329d44748034bba349c5848cb519" dependencies = [ "compression-codecs", "compression-core", @@ -1385,7 +1385,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tracing", - "uuid 1.18.1", + "uuid 1.19.0", ] [[package]] @@ -1457,9 +1457,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" +checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" dependencies = [ "futures-util", "pin-project-lite", @@ -1468,9 +1468,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.5" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -1489,27 +1489,27 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.7" +version = "0.61.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54" +checksum = "a6864c190cbb8e30cf4b77b2c8f3b6dfffa697a09b7218d2f7cd3d4c4065a9f7" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" +checksum = "17f616c3f2260612fe44cede278bafa18e73e6479c4e393e2c4518cf2a9a228a" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.8" +version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9" +checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" dependencies = [ "aws-smithy-types", "urlencoding", @@ -1517,9 +1517,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0" +checksum = "a392db6c583ea4a912538afb86b7be7c5d8887d91604f50eb55c262ee1b4a5f5" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1540,9 +1540,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" +checksum = "ab0d43d899f9e508300e587bf582ba54c27a452dd0a9ea294690669138ae14a2" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -1557,9 +1557,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.4" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" +checksum = "905cb13a9895626d49cf2ced759b062d913834c7482c38e49557eac4e6193f01" dependencies = [ "base64-simd", "bytes", @@ -1580,9 +1580,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.12" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" dependencies = [ "xmlparser", ] @@ -1680,9 +1680,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "basic_bootloader" @@ -1812,15 +1812,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -2069,9 +2069,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.47" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -2246,9 +2246,9 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compression-codecs" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad" +checksum = "34a3cbbb8b6eca96f3a5c4bf6938d5b27ced3675d69f95bb51948722870bc323" dependencies = [ "compression-core", "flate2", @@ -2331,6 +2331,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2377,9 +2386,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -2656,11 +2665,11 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl 2.1.0", ] [[package]] @@ -2676,12 +2685,14 @@ dependencies = [ [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.1", "syn 2.0.111", "unicode-xid", ] @@ -3648,7 +3659,7 @@ dependencies = [ "once_cell", "prost 0.13.5", "prost-types 0.13.5", - "reqwest 0.12.24", + "reqwest 0.12.25", "secret-vault-value", "serde", "serde_json", @@ -3839,9 +3850,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -4043,9 +4054,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", @@ -4439,9 +4450,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -4562,9 +4573,9 @@ source = "git+https://github.com/shamatar/lib-rv32.git#869f82795f82314f17014a04f [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" @@ -4617,9 +4628,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -4727,9 +4738,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -5351,7 +5362,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit 0.23.9", ] [[package]] @@ -5883,11 +5894,10 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" dependencies = [ - "async-compression", "base64 0.22.1", "bytes", "futures-core", @@ -6244,9 +6254,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "web-time", "zeroize", @@ -6604,9 +6614,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", @@ -6623,9 +6633,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -6746,9 +6756,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_asn1" @@ -7392,9 +7402,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ "indexmap 2.12.1", "toml_datetime 0.7.3", @@ -7469,17 +7479,22 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", "bitflags 2.10.0", "bytes", + "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -7532,9 +7547,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -7543,9 +7558,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -7554,9 +7569,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", ] @@ -7661,7 +7676,7 @@ dependencies = [ "mime", "prost 0.12.6", "prost-types 0.12.6", - "reqwest 0.12.24", + "reqwest 0.12.25", "serde", "serde_json", "serde_with", @@ -7737,6 +7752,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -7837,9 +7858,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "js-sys", "wasm-bindgen", @@ -7950,9 +7971,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -7963,9 +7984,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -7976,9 +7997,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7986,9 +8007,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -7999,9 +8020,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -8035,9 +8056,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -8397,9 +8418,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -8654,6 +8675,8 @@ name = "zksync_os_api" version = "0.1.0" dependencies = [ "alloy", + "alloy-sol-types", + "basic_bootloader", "basic_system", "callable_oracles", "crypto", @@ -8670,14 +8693,12 @@ dependencies = [ [[package]] name = "zksync_os_evm_errors" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3ad55a8aa3498e304565de55b61dc930bac227c8c5e81284b623c69f2143d2" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" [[package]] name = "zksync_os_interface" version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3476c481db1ea2db28ed8cbdfef391576288cb9e13241f7944274b72cf1e251e" +source = "git+https://github.com/matter-labs/zksync-os-interface?branch=alocascio-prover-input-gen#16f2306bb4c3b6cb7465f6809637661ff4f2f86f" dependencies = [ "alloy-consensus", "alloy-primitives", diff --git a/tests/instances/eth_runner/src/live_run/mod.rs b/tests/instances/eth_runner/src/live_run/mod.rs index de485ec79..c350f581b 100644 --- a/tests/instances/eth_runner/src/live_run/mod.rs +++ b/tests/instances/eth_runner/src/live_run/mod.rs @@ -173,7 +173,7 @@ fn run_block( check_storage_diff_hashes: true, ..Default::default() }; - let (output, stats, _) = chain + let (output, stats, _, _) = chain .run_block_with_extra_stats( transactions, Some(block_context), diff --git a/tests/instances/eth_runner/src/single_run.rs b/tests/instances/eth_runner/src/single_run.rs index cc9f7313f..fe86f39eb 100644 --- a/tests/instances/eth_runner/src/single_run.rs +++ b/tests/instances/eth_runner/src/single_run.rs @@ -46,7 +46,7 @@ fn run( check_storage_diff_hashes: true, ..Default::default() }; - let (output, stats, _) = chain + let (output, stats, _, _) = chain .run_block_with_extra_stats( transactions, Some(block_context), diff --git a/tests/instances/header/src/lib.rs b/tests/instances/header/src/lib.rs index 0034bab80..aa4207480 100644 --- a/tests/instances/header/src/lib.rs +++ b/tests/instances/header/src/lib.rs @@ -25,7 +25,7 @@ fn test_block_header_invariants() { // TODO: enable when this is implemented // assert_ne!(header.transactions_root, Bytes32::ZERO); // assert_ne!(header.receipts_root, Bytes32::ZERO); - assert_eq!(header.number, 0); + assert_eq!(header.number, 1); assert_eq!(header.gas_limit, MAX_BLOCK_GAS_LIMIT); assert!( header.gas_used @@ -63,7 +63,7 @@ fn test_block_header_invariants() { // TODO: enable when this is implemented // assert_ne!(header.transactions_root, Bytes32::ZERO); // assert_ne!(header.receipts_root, Bytes32::ZERO); - assert_eq!(header.number, 1); + assert_eq!(header.number, 2); assert_eq!(header.gas_limit, gas_limit); assert!( header.gas_used diff --git a/tests/instances/interop/Cargo.toml b/tests/instances/interop/Cargo.toml new file mode 100644 index 000000000..bfdee7b22 --- /dev/null +++ b/tests/instances/interop/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "interop" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +rig = { path = "../../rig", features = ["for_tests"] } +hex = "*" +alloy-sol-types = "1.4.0" + +[features] +cycle_marker = ["rig/cycle_marker"] diff --git a/tests/instances/interop/src/lib.rs b/tests/instances/interop/src/lib.rs new file mode 100644 index 000000000..f51055984 --- /dev/null +++ b/tests/instances/interop/src/lib.rs @@ -0,0 +1,164 @@ +//! +//! These tests are focused on interop support in ZKsync OS +//! +#![cfg(test)] + +use alloy::signers::local::PrivateKeySigner; +use rig::crypto::MiniDigest; +use rig::ruint::aliases::{B160, U256}; +use rig::system_hooks::addresses_constants::L2_INTEROP_ROOT_STORAGE_ADDRESS; +use rig::utils::encode_interop_root_import_calldata; +use rig::zk_ee::common_structs::interop_root_storage::InteropRoot as StoredInteropRoot; +use rig::zk_ee::system::tracer::NopTracer; +use rig::zk_ee::utils::Bytes32; +use rig::zksync_os_interface::types::ExecutionOutput; +use rig::zksync_os_interface::types::ExecutionResult; +use rig::{alloy, zksync_web3_rs, BlockContext, Chain}; +use std::str::FromStr; +use std::vec; +use zksync_web3_rs::signers::{LocalWallet, Signer}; + +const L2_INTEROP_ROOT_STORAGE_BYTECODE : &str = "608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80633b43dbde1461004e57806377cfd1711461006a578063cca2f7bc1461009a578063fb6200c6146100b6575b5f5ffd5b610068600480360381019061006391906104c2565b6100d2565b005b610084600480360381019061007f919061053c565b610169565b6040516100919190610592565b60405180910390f35b6100b460048036038101906100af919061060c565b610188565b005b6100d060048036038101906100cb91906106ac565b6102aa565b005b60016180006100e19190610769565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610145576040517fefce78c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610166815f0135826020013583806040019061016191906107bc565b61032f565b50565b5f602052815f5260405f20602052805f5260405f205f91509150505481565b60016180006101979190610769565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101fb576040517fefce78c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8282905090505f5b818110156102a4576102998484838181106102225761022161081e565b5b9050602002810190610234919061084b565b5f013585858481811061024a5761024961081e565b5b905060200281019061025c919061084b565b602001358686858181106102735761027261081e565b5b9050602002810190610285919061084b565b806040019061029491906107bc565b61032f565b806001019050610204565b50505050565b60016180006102b99190610769565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461031d576040517fefce78c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6103298484848461032f565b50505050565b6001828290501461036c576040517f2f59bd0d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f1b82825f8181106103825761038161081e565b5b90506020020135036103c0576040517f9b5f85eb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f1b5f5f8681526020019081526020015f205f8581526020019081526020015f20541461041a576040517f2d48e8cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81815f81811061042d5761042c61081e565b5b905060200201355f5f8681526020019081526020015f205f8581526020019081526020015f208190555082847f6b451b8422636e45b93bf7f594fa2c1769d039766c4254a6e7f9c0ee1715cdb0848460405161048a9291906108ea565b60405180910390a350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f606082840312156104b9576104b86104a0565b5b81905092915050565b5f602082840312156104d7576104d6610498565b5b5f82013567ffffffffffffffff8111156104f4576104f361049c565b5b610500848285016104a4565b91505092915050565b5f819050919050565b61051b81610509565b8114610525575f5ffd5b50565b5f8135905061053681610512565b92915050565b5f5f6040838503121561055257610551610498565b5b5f61055f85828601610528565b925050602061057085828601610528565b9150509250929050565b5f819050919050565b61058c8161057a565b82525050565b5f6020820190506105a55f830184610583565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126105cc576105cb6105ab565b5b8235905067ffffffffffffffff8111156105e9576105e86105af565b5b602083019150836020820283011115610605576106046105b3565b5b9250929050565b5f5f6020838503121561062257610621610498565b5b5f83013567ffffffffffffffff81111561063f5761063e61049c565b5b61064b858286016105b7565b92509250509250929050565b5f5f83601f84011261066c5761066b6105ab565b5b8235905067ffffffffffffffff811115610689576106886105af565b5b6020830191508360208202830111156106a5576106a46105b3565b5b9250929050565b5f5f5f5f606085870312156106c4576106c3610498565b5b5f6106d187828801610528565b94505060206106e287828801610528565b935050604085013567ffffffffffffffff8111156107035761070261049c565b5b61070f87828801610657565b925092505092959194509250565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6107738261071d565b915061077e8361071d565b9250828201905073ffffffffffffffffffffffffffffffffffffffff8111156107aa576107a961073c565b5b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126107d8576107d76107b0565b5b80840192508235915067ffffffffffffffff8211156107fa576107f96107b4565b5b602083019250602082023603831315610816576108156107b8565b5b509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f82356001606003833603038112610866576108656107b0565b5b80830191505092915050565b5f82825260208201905092915050565b5f5ffd5b82818337505050565b5f61089a8385610872565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8311156108cd576108cc610882565b5b6020830292506108de838584610886565b82840190509392505050565b5f6020820190508181035f83015261090381848661088f565b9050939250505056fea26469706673582212207d5b11ffa3ae60b0ae174df6b0fe6b1c7f2bcadbc1934064a28c7723c5d4e74d64736f6c634300081c0033"; + +#[test] +fn run_processes_one_interop_root() { + // Create some dummy interop root + let interop_roots = vec![StoredInteropRoot { + root: Bytes32::from_u256_be(&U256::ONE), + block_or_batch_number: U256::from(42), + chain_id: U256::ONE, + }]; + + run_test(interop_roots); +} + +#[test] +#[should_panic(expected = "Transaction should be successful")] +fn run_fails_if_interop_root_is_incorrect() { + // Create some dummy interop root + let interop_roots = vec![StoredInteropRoot { + root: Bytes32::zero(), // Root can't be zero + block_or_batch_number: U256::from(42), + chain_id: U256::ONE, + }]; + + run_test(interop_roots); +} + +#[test] +fn run_processes_several_interop_roots() { + let mut interop_roots = Vec::new(); + // Create several interop roots to test batch processing and resource costs + for i in 1..=20 { + interop_roots.push(StoredInteropRoot { + root: Bytes32::from_u256_be(&U256::from(0x1000 + i)), + block_or_batch_number: U256::from(100 + i), + chain_id: U256::from(i), // Use different chain IDs + }); + } + + run_test(interop_roots); +} + +#[test] +fn run_processes_empty_interop_roots() { + run_test(vec![]); +} + +#[test] +fn run_processes_interop_roots_max_amount() { + let interop_roots = vec![ + // Edge case: Maximum values + StoredInteropRoot { + root: Bytes32::from_u256_be(&U256::MAX), + block_or_batch_number: U256::MAX, + chain_id: U256::MAX, + }, + // Edge case: Minimum valid values (chain_id = 1, block = 0) + StoredInteropRoot { + root: Bytes32::from_u256_be(&U256::from(1)), + block_or_batch_number: U256::ZERO, + chain_id: U256::ONE, + }, // Edge case: Large root hash with small numbers + StoredInteropRoot { + root: Bytes32::from_u256_be(&(U256::MAX - U256::from(1))), + block_or_batch_number: U256::ONE, + chain_id: U256::ONE, + }, + ]; + + run_test(interop_roots); +} + +fn run_test(interop_roots: Vec) { + run_test_inner(interop_roots) +} + +/// Executes a transaction with specified interop roots and verifies success +fn run_test_inner(interop_roots: Vec) { + let mut chain = Chain::empty(None); + // We can't set interop roots for block 0 + chain.set_last_block_number(0); + let wallet = PrivateKeySigner::from_str( + "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7", + ) + .unwrap(); + let wallet_ethers = LocalWallet::from_bytes(wallet.to_bytes().as_slice()).unwrap(); + + let from = wallet_ethers.address(); + + chain.set_balance( + B160::from_be_bytes(from.0), + U256::from(1_000_000_000_000_000_u64), + ); + + let bytecode = hex::decode(L2_INTEROP_ROOT_STORAGE_BYTECODE).unwrap(); + chain.set_evm_bytecode(L2_INTEROP_ROOT_STORAGE_ADDRESS, &bytecode); + + // Compute expected rolling hash + let expected_rolling_hash = rig::basic_system::system_implementation::system::interop_roots::calculate_interop_roots_rolling_hash( + Bytes32::ZERO, + interop_roots.iter(), + &mut rig::crypto::sha3::Keccak256::new(), + ); + + // Construct calldata + let n_interop_roots = interop_roots.len(); + let calldata = encode_interop_root_import_calldata(interop_roots); + + let tx = rig::utils::encode_service_tx( + 0, + 50_000_000, + &L2_INTEROP_ROOT_STORAGE_ADDRESS.to_be_bytes::<20>(), + &calldata, + ); + + let mut tracer = NopTracer::default(); + + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + + let (output, pi_batch_output) = chain + .run_block_pi_output(vec![tx], Some(block_context), None, None, &mut tracer) + .expect("Block should run successfully"); + + // Verify the transaction succeeded + assert_eq!(output.tx_results.len(), 1); + assert!(output.tx_results[0].is_ok(), "Transaction should succeed"); + let tx_result = output.tx_results[0].as_ref().unwrap(); + assert!(tx_result.is_success(), "Transaction should be successful"); + // check the output to ensure the contract was called + match &tx_result.execution_result { + ExecutionResult::Success(ExecutionOutput::Call(_)) => (), + _ => panic!("Execution result must be a successful call"), + } + // Check there's an event for every root + assert!(tx_result.logs.len() == n_interop_roots); + // Check the rolling hash in public input is the expected one + assert_eq!( + expected_rolling_hash, pi_batch_output.interop_roots_rolling_hash, + "Mismatch in interop_roots_rolling_hash" + ); +} diff --git a/tests/instances/multiblock_batch/src/lib.rs b/tests/instances/multiblock_batch/src/lib.rs index cc697336e..94b9a4833 100644 --- a/tests/instances/multiblock_batch/src/lib.rs +++ b/tests/instances/multiblock_batch/src/lib.rs @@ -88,10 +88,7 @@ fn run_multiblock_batch_proof_run(da_commitment_scheme: DACommitmentScheme) { let batch_input = generate_batch_proof_input( vec![block1_result.2.as_slice(), block2_result.2.as_slice()], da_commitment_scheme, - vec![ - block1_result.0.pubdata.as_slice(), - block2_result.0.pubdata.as_slice(), - ], + vec![block1_result.3.as_slice(), block2_result.3.as_slice()], ); let multinblock_program_path = PathBuf::from(std::env::var("CARGO_WORKSPACE_DIR").unwrap()) diff --git a/tests/instances/transactions/src/lib.rs b/tests/instances/transactions/src/lib.rs index a68e01de2..2f08a72aa 100644 --- a/tests/instances/transactions/src/lib.rs +++ b/tests/instances/transactions/src/lib.rs @@ -8,8 +8,9 @@ use alloy::signers::local::PrivateKeySigner; use rig::alloy::consensus::TxEip7702; use rig::alloy::primitives::{address, b256}; use rig::alloy::rpc::types::{AccessList, AccessListItem, TransactionRequest}; -use rig::basic_system::system_implementation::system::pubdata::PUBDATA_ENCODING_VERSION; +use rig::basic_system::system_implementation::system::pubdata::{self, PUBDATA_ENCODING_VERSION}; use rig::ruint::aliases::{B160, U256}; +use rig::system_hooks::addresses_constants::L2_INTEROP_ROOT_STORAGE_ADDRESS; use rig::zksync_os_interface::error::InvalidTransaction; use rig::{alloy, zksync_web3_rs, Chain}; use rig::{utils::*, BlockContext}; @@ -1319,11 +1320,12 @@ fn test_check_pubdata_encoding_version() { ..Default::default() }; // Check tx succeeds - let result = chain.run_block(vec![tx], Some(block_context), None, run_config()); + let (result, pubdata) = + chain.run_block_get_pubdata(vec![tx], Some(block_context), None, run_config()); let res0 = result.tx_results.first().expect("Must have a tx result"); assert!(res0.as_ref().is_ok(), "Tx should succeed"); - assert_eq!(result.pubdata[0], PUBDATA_ENCODING_VERSION); + assert_eq!(pubdata[0], PUBDATA_ENCODING_VERSION); } #[test] @@ -1363,12 +1365,13 @@ fn test_check_pubdata_has_timestamp() { ..Default::default() }; // Check tx succeeds - let result = chain.run_block(vec![tx], Some(block_context), None, run_config()); + let (result, pubdata) = + chain.run_block_get_pubdata(vec![tx], Some(block_context), None, run_config()); let res0 = result.tx_results.first().expect("Must have a tx result"); assert!(res0.as_ref().is_ok(), "Tx should succeed"); // Pubdata format is [VERSION(1)][BLOCK_HASH(32)][TIMESTAMP(8)][DIFFS...] - let pubdata_timestamp_bytes = &result.pubdata.as_slice()[33..41]; + let pubdata_timestamp_bytes = &pubdata.as_slice()[33..41]; let pubdata_timestamp = u64::from_be_bytes( pubdata_timestamp_bytes .try_into() @@ -1376,3 +1379,118 @@ fn test_check_pubdata_has_timestamp() { ); assert_eq!(timestamp, pubdata_timestamp, "Timestamps do not match"); } + +#[test] +fn test_simple_service_transaction() { + let mut chain = Chain::empty(None); + let wallet = chain.random_signer(); + let from = wallet.address(); + let target_address = L2_INTEROP_ROOT_STORAGE_ADDRESS.to_be_bytes::<20>(); + + // Set balance for the contract address + chain.set_balance(B160::from_be_bytes(from.into_array()), U256::from(u64::MAX)); + + let tx = encode_service_tx(0, 500_000, &target_address, &[]); + + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + // Check tx succeeds + let result = chain.run_block(vec![tx], Some(block_context), None, run_config()); + let res0 = result.tx_results.first().expect("Must have a tx result"); + assert!(res0.as_ref().is_ok(), "Tx should succeed"); +} + +#[test] +fn test_simple_service_transaction_whitelist() { + let mut chain = Chain::empty(None); + let wallet = chain.random_signer(); + let from = wallet.address(); + // Invalid target + let target_address = [0u8; 20]; + + // Set balance for the contract address + chain.set_balance(B160::from_be_bytes(from.into_array()), U256::from(u64::MAX)); + + let tx = encode_service_tx(0, 500_000, &target_address, &[]); + + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + // Check tx succeeds + let result = chain.run_block(vec![tx], Some(block_context), None, run_config()); + let res0 = result.tx_results.first().expect("Must have a tx result"); + assert!(res0.as_ref().is_err(), "Tx should fail"); +} + +#[test] +fn test_service_block_invariants() { + let mut chain = Chain::empty(None); + let wallet = chain.random_signer(); + let from = wallet.address(); + let target_address = L2_INTEROP_ROOT_STORAGE_ADDRESS.to_be_bytes::<20>(); + + // Set balance for the contract address + chain.set_balance(B160::from_be_bytes(from.into_array()), U256::from(u64::MAX)); + + // Check that a service block with several service txs works + let tx1 = encode_service_tx(0, 500_000, &target_address, &[]); + let tx2 = encode_service_tx(1, 500_000, &target_address, &[]); + let tx3 = encode_service_tx(2, 500_000, &target_address, &[]); + + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + // Check txs succeed + let result = chain.run_block(vec![tx1, tx2, tx3], Some(block_context), None, run_config()); + assert!( + result.tx_results.iter().all(|res| res.is_ok()), + "All txs should succeed" + ); + + // Check that a service block with a non-service tx fails + let tx4 = encode_service_tx(3, 500_000, &target_address, &[]); + let tx_non_service = { + let tx = TxEip1559 { + chain_id: 37u64, + nonce: 0, + max_fee_per_gas: 134217728, + max_priority_fee_per_gas: 134217728, + gas_limit: 75_000, + to: TxKind::Call(address!("4242000000000000000000000000000000000000")), + value: Default::default(), + input: Default::default(), + access_list: Default::default(), + }; + rig::utils::sign_and_encode_alloy_tx(tx, &wallet) + }; + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + chain + .run_block_no_panic( + vec![tx4.clone(), tx_non_service.clone()], + Some(block_context), + None, + run_config(), + ) + .expect_err("Service block with non service tx should fail"); + + // Check that a non-service block with a service tx fails + let block_context = BlockContext { + eip1559_basefee: U256::ZERO, + ..Default::default() + }; + chain + .run_block_no_panic( + vec![tx_non_service, tx4], + Some(block_context), + None, + run_config(), + ) + .expect_err("Service block with non service tx should fail"); +} diff --git a/tests/instances/unit/src/initial_slot_regression.rs b/tests/instances/unit/src/initial_slot_regression.rs index 642825c4a..e6cf66d14 100644 --- a/tests/instances/unit/src/initial_slot_regression.rs +++ b/tests/instances/unit/src/initial_slot_regression.rs @@ -133,6 +133,7 @@ impl TestingOracleFactory for InvalidInitialValueOracleFactory { proof_data: Option>>, da_commitment_scheme: Option, _add_uart: bool, + _use_native_callable_oracles: bool, ) -> ZkEENonDeterminismSource { // Create a malicious oracle manually instead of using the default factory let block_metadata_responder = BlockMetadataResponder { block_metadata }; diff --git a/tests/instances/unit/src/tracer/evm_opcodes_logger.rs b/tests/instances/unit/src/tracer/evm_opcodes_logger.rs index 61070a046..67e1d4338 100644 --- a/tests/instances/unit/src/tracer/evm_opcodes_logger.rs +++ b/tests/instances/unit/src/tracer/evm_opcodes_logger.rs @@ -47,7 +47,7 @@ fn run_chain_with_tracer( let result = chain.run_block_with_extra_stats(vec![encoded_tx], None, None, None, tracer); assert!(result.is_ok(), "Block execution should succeed"); - let (block_output, _, _) = result.unwrap(); + let (block_output, _, _, _) = result.unwrap(); assert!( block_output.tx_results[0].is_ok(), "Transaction should succeed. Result: {:?}", diff --git a/tests/instances/unit/src/tracer/mod.rs b/tests/instances/unit/src/tracer/mod.rs index f062ff0b5..6dff7a115 100644 --- a/tests/instances/unit/src/tracer/mod.rs +++ b/tests/instances/unit/src/tracer/mod.rs @@ -46,7 +46,7 @@ pub(crate) fn run_chain_with_tracer( let result = chain.run_block_with_extra_stats(vec![encoded_tx], None, None, None, tracer); assert!(result.is_ok(), "Block execution should succeed"); - let (block_output, _, _) = result.unwrap(); + let (block_output, _, _, _) = result.unwrap(); assert!( block_output.tx_results[0].is_ok(), "Transaction should succeed. Result: {:?}", diff --git a/tests/instances/unit/src/tracer/tracer_event_hook.rs b/tests/instances/unit/src/tracer/tracer_event_hook.rs index ff11dba25..a2de3a4f0 100644 --- a/tests/instances/unit/src/tracer/tracer_event_hook.rs +++ b/tests/instances/unit/src/tracer/tracer_event_hook.rs @@ -152,7 +152,7 @@ fn test_event_hook() { let result = chain.run_block_with_extra_stats(vec![encoded_tx], None, None, None, &mut tracer); assert!(result.is_ok(), "Block execution should succeed"); - let (block_output, _, _) = result.unwrap(); + let (block_output, _, _, _) = result.unwrap(); assert!( block_output.tx_results[0].is_ok(), "Transaction should succeed with correct tracer calls. Result: {:?}", diff --git a/tests/instances/unit/src/tracer/tracer_storage_hooks.rs b/tests/instances/unit/src/tracer/tracer_storage_hooks.rs index e66e205f2..b77d1ba08 100644 --- a/tests/instances/unit/src/tracer/tracer_storage_hooks.rs +++ b/tests/instances/unit/src/tracer/tracer_storage_hooks.rs @@ -159,7 +159,7 @@ fn test_storage_hooks() { let result = chain.run_block_with_extra_stats(vec![encoded_tx], None, None, None, &mut tracer); assert!(result.is_ok(), "Block execution should succeed"); - let (block_output, _, _) = result.unwrap(); + let (block_output, _, _, _) = result.unwrap(); assert!( block_output.tx_results[0].is_ok(), "Transaction should succeed with correct tracer calls. Result: {:?}", diff --git a/tests/rig/src/chain.rs b/tests/rig/src/chain.rs index 30e5428e9..cf9d442b7 100644 --- a/tests/rig/src/chain.rs +++ b/tests/rig/src/chain.rs @@ -9,15 +9,17 @@ use basic_system::system_implementation::flat_storage_model::{ address_into_special_storage_key, AccountProperties, ACCOUNT_PROPERTIES_STORAGE_ADDRESS, TREE_HEIGHT, }; +use basic_system::system_implementation::system::BatchOutput as PIBatchOutput; use ethers::signers::LocalWallet; use forward_system::run::result_keeper::ForwardRunningResultKeeper; +use forward_system::run::result_keeper::ProverInputResultKeeper; use forward_system::run::test_impl::{InMemoryPreimageSource, InMemoryTree, NoopTxCallback}; use forward_system::system::bootloader::run_forward_no_panic; +use forward_system::system::bootloader::run_prover_input_no_panic; use forward_system::system::system::ForwardRunningSystem; use log::{debug, info, trace}; use oracle_provider::MemorySource; use oracle_provider::{ReadWitnessSource, ZkEENonDeterminismSource}; -use risc_v_simulator::abstractions::memory::VectorMemoryImpl; use risc_v_simulator::sim::{DiagnosticsConfig, ProfilerConfig}; use ruint::aliases::{B160, B256, U256}; use std::collections::HashMap; @@ -47,6 +49,7 @@ pub trait TestingOracleFactory { proof_data: Option>>, da_commitment_scheme: Option, add_uart: bool, + use_native_callable_oracles: bool, ) -> ZkEENonDeterminismSource; } @@ -65,6 +68,7 @@ impl TestingOracleFactory proof_data: Option>>, da_commitment_scheme: Option, add_uart: bool, + use_native_callable_oracles: bool, ) -> ZkEENonDeterminismSource { forward_system::run::make_oracle_for_proofs_and_dumps( block_metadata, @@ -74,6 +78,7 @@ impl TestingOracleFactory proof_data, da_commitment_scheme, add_uart, + use_native_callable_oracles, ) } } @@ -85,7 +90,7 @@ pub struct Chain { state_tree: InMemoryTree, pub preimage_source: InMemoryPreimageSource, chain_id: u64, - previous_block_number: Option, + previous_block_number: u64, block_hashes: [U256; 256], block_timestamp: u64, } @@ -148,7 +153,7 @@ impl Chain { inner: HashMap::new(), }, chain_id: chain_id.unwrap_or(37), - previous_block_number: None, + previous_block_number: 0, block_hashes: [U256::ZERO; 256], block_timestamp: 0, } @@ -171,7 +176,7 @@ impl Chain { inner: HashMap::new(), }, chain_id: chain_id.unwrap_or(37), - previous_block_number: None, + previous_block_number: 0, block_hashes: [U256::ZERO; 256], block_timestamp: 0, } @@ -186,52 +191,17 @@ pub struct BlockExtraStats { impl Chain { pub fn set_last_block_number(&mut self, prev: u64) { - self.previous_block_number = Some(prev) + self.previous_block_number = prev } pub fn next_block_number(&self) -> u64 { - self.previous_block_number.map(|n| n + 1).unwrap_or(0) + self.previous_block_number + 1 } pub fn set_block_hashes(&mut self, block_hashes: [U256; 256]) { self.block_hashes = block_hashes } - /// TODO: duplicated from API, unify. - /// Runs a block in riscV - using zksync_os binary - and returns the - /// witness that can be passed to the prover subsystem. - pub fn run_block_generate_witness( - oracle: ZkEENonDeterminismSource, - app: &Option, - ) -> Vec { - // We'll wrap the source, to collect all the reads. - let copy_source = ReadWitnessSource::new(oracle); - let items = copy_source.get_read_items(); - // By default - enable diagnostics is false (which makes the test run faster). - let path = get_zksync_os_img_path(app); - - let diagnostics_config = if FLAMEGRAPH { - let mut profiler_config = ProfilerConfig::new("flamegraph.svg".into()); - profiler_config.frequency_recip = 10; - - Some(profiler_config).map(|cfg| { - let mut diagnostics_cfg = DiagnosticsConfig::new(get_zksync_os_sym_path(app)); - diagnostics_cfg.profiler_config = Some(cfg); - diagnostics_cfg - }) - } else { - None - }; - - let output = zksync_os_runner::run(path, diagnostics_config, 1 << 36, copy_source); - - // We return 0s in case of failure. - assert_ne!(output, [0u32; 8]); - - let result = items.borrow().clone(); - result - } - /// /// Simulate block, do not validate transactions /// @@ -311,6 +281,25 @@ impl Chain { .0 } + pub fn run_block_get_pubdata( + &mut self, + transactions: Vec, + block_context: Option, + da_commitment_scheme: Option, + run_config: Option, + ) -> (BlockOutput, Vec) { + let (r, _, _, pubdata) = self + .run_block_with_extra_stats( + transactions, + block_context, + da_commitment_scheme, + run_config, + &mut NopTracer::default(), + ) + .unwrap(); + (r, pubdata) + } + /// /// Run block with given transactions, block context, and custom oracle factory. /// If block context is `None` default testing values will be used. @@ -365,16 +354,38 @@ impl Chain { da_commitment_scheme: Option, run_config: Option, tracer: &mut impl Tracer, - ) -> Result<(BlockOutput, BlockExtraStats, Vec), BootloaderSubsystemError> { + ) -> Result<(BlockOutput, BlockExtraStats, Vec, Vec), BootloaderSubsystemError> { let factory = DefaultOracleFactory::; - self.run_inner( + let (block_output, stats, prover_input, pubdata, _) = self.run_inner( transactions, block_context, da_commitment_scheme, run_config.unwrap_or_default(), &factory, tracer, - ) + )?; + Ok((block_output, stats, prover_input, pubdata)) + } + + #[allow(clippy::result_large_err)] + pub fn run_block_pi_output( + &mut self, + transactions: Vec, + block_context: Option, + da_commitment_scheme: Option, + run_config: Option, + tracer: &mut impl Tracer, + ) -> Result<(BlockOutput, PIBatchOutput), BootloaderSubsystemError> { + let factory = DefaultOracleFactory::; + let (block_output, _, _, _, pi) = self.run_inner( + transactions, + block_context, + da_commitment_scheme, + run_config.unwrap_or_default(), + &factory, + tracer, + )?; + Ok((block_output, pi)) } #[allow(clippy::result_large_err)] @@ -388,18 +399,19 @@ impl Chain { run_config: Option, tracer: &mut impl Tracer, oracle_factory: &OF, - ) -> Result<(BlockOutput, BlockExtraStats, Vec), BootloaderSubsystemError> { - self.run_inner( + ) -> Result<(BlockOutput, BlockExtraStats, Vec, Vec), BootloaderSubsystemError> { + let (block_output, stats, prover_input, pubdata, _) = self.run_inner( transactions, block_context, da_commitment_scheme, run_config.unwrap_or_default(), oracle_factory, tracer, - ) + )?; + Ok((block_output, stats, prover_input, pubdata)) } - #[allow(clippy::result_large_err)] + #[allow(clippy::result_large_err, clippy::type_complexity)] fn run_inner>( &mut self, transactions: Vec, @@ -408,7 +420,16 @@ impl Chain { run_config: RunConfig, oracle_factory: &OF, tracer: &mut impl Tracer, - ) -> Result<(BlockOutput, BlockExtraStats, Vec), BootloaderSubsystemError> { + ) -> Result< + ( + BlockOutput, + BlockExtraStats, + Vec, + Vec, + PIBatchOutput, + ), + BootloaderSubsystemError, + > { let RunConfig { profiler_config, witness_output_file, @@ -452,6 +473,7 @@ impl Chain { Some(proof_data), Some(da_commitment_scheme), true, + false, ); let forward_oracle = oracle_factory.create_oracle( @@ -462,6 +484,18 @@ impl Chain { Some(proof_data), Some(da_commitment_scheme), true, + false, + ); + + let prover_input_oracle = oracle_factory.create_oracle( + block_metadata, + self.state_tree.clone(), + self.preimage_source.clone(), + tx_source.clone(), + Some(proof_data), + Some(da_commitment_scheme), + false, + true, ); #[cfg(feature = "simulate_witness_gen")] @@ -480,6 +514,9 @@ impl Chain { // forward run let mut result_keeper = ForwardRunningResultKeeper::new(NoopTxCallback); + // Avoid capturing markers from second round, it duplicate them + #[cfg(feature = "cycle_marker")] + let snapshot = cycle_marker::snapshot(); // we use proving config here for benchmarking, // although sequencer can have extra optimizations run_forward_no_panic::( @@ -487,8 +524,33 @@ impl Chain { &mut result_keeper, tracer, )?; + #[cfg(feature = "cycle_marker")] + cycle_marker::revert(snapshot); + + let mut result_keeper_prover_input = ProverInputResultKeeper::new(NoopTxCallback); + + let copy_source = ReadWitnessSource::new(prover_input_oracle); + let mut tracer = NopTracer::default(); + let (prover_input_forward, pi_batch_output) = + run_prover_input_no_panic::( + copy_source, + &mut result_keeper_prover_input, + &mut tracer, + )?; + + if let Some(path) = witness_output_file { + let mut file = File::create(&path).expect("should create file"); + let witness: Vec = prover_input_forward + .iter() + .flat_map(|x| x.to_be_bytes()) + .collect(); + let hex = hex::encode(witness); + file.write_all(hex.as_bytes()) + .expect("should write to file"); + } let block_output: BlockOutput = result_keeper.into(); + let pubdata = result_keeper_prover_input.pubdata; trace!( "{}Block output:{} \n{:#?}", @@ -516,7 +578,7 @@ impl Chain { } // update state - self.previous_block_number = Some(self.next_block_number()); + self.previous_block_number = self.next_block_number(); self.block_timestamp = block_context.timestamp; for i in 0..255 { self.block_hashes[i] = self.block_hashes[i + 1]; @@ -539,105 +601,95 @@ impl Chain { } let proof_input = if !only_forward { - if let Some(path) = witness_output_file { - let result = Self::run_block_generate_witness::(oracle, &app); - let mut file = File::create(&path).expect("should create file"); - let witness: Vec = result.iter().flat_map(|x| x.to_be_bytes()).collect(); - let hex = hex::encode(witness); - file.write_all(hex.as_bytes()) - .expect("should write to file"); - result - } else { - // We'll wrap the source, to collect all the reads. - let copy_source = ReadWitnessSource::new(oracle); - let items = copy_source.get_read_items(); - - let diagnostics_config = profiler_config.map(|cfg| { - let mut diagnostics_cfg = DiagnosticsConfig::new(get_zksync_os_sym_path(&app)); - diagnostics_cfg.profiler_config = Some(cfg); - diagnostics_cfg - }); - - let now = std::time::Instant::now(); - let (proof_output, block_effective) = { - zksync_os_runner::run_and_get_effective_cycles( - get_zksync_os_img_path(&app), - diagnostics_config, - 1 << 36, - copy_source, - ) - }; + // We'll wrap the source, to collect all the reads. + let copy_source = ReadWitnessSource::new(oracle); + let items = copy_source.get_read_items(); - info!( - "Simulator without witness tracing executed over {:?}", - now.elapsed() - ); - stats.effective_used = block_effective; - - #[cfg(feature = "simulate_witness_gen")] - { - zksync_os_runner::simulate_witness_tracing( - get_zksync_os_img_path(), - source_for_witness_bench, - ) - } + let diagnostics_config = profiler_config.map(|cfg| { + let mut diagnostics_cfg = DiagnosticsConfig::new(get_zksync_os_sym_path(&app)); + diagnostics_cfg.profiler_config = Some(cfg); + diagnostics_cfg + }); + + let now = std::time::Instant::now(); + let (proof_output, block_effective) = { + zksync_os_runner::run_and_get_effective_cycles( + get_zksync_os_img_path(&app), + diagnostics_config, + 1 << 36, + copy_source, + ) + }; + + info!( + "Simulator without witness tracing executed over {:?}", + now.elapsed() + ); + stats.effective_used = block_effective; + + #[cfg(feature = "simulate_witness_gen")] + { + zksync_os_runner::simulate_witness_tracing( + get_zksync_os_img_path(), + source_for_witness_bench, + ) + } - // dump csr reads if env var set - if let Ok(output_csr) = std::env::var("CSR_READS_DUMP") { - // Save the read elements into a file - that can be later read with the tools/cli from zksync-airbender. - let mut file = - File::create(&output_csr).expect("Failed to create csr reads file"); - // Write each u32 as an 8-character hexadecimal string without newlines - for num in items.borrow().iter() { - write!(file, "{num:08X}").expect("Failed to write to file"); - } - debug!( - "Successfully wrote {} u32 csr reads elements to file: {}", - items.borrow().len(), - output_csr - ); + // dump csr reads if env var set + if let Ok(output_csr) = std::env::var("CSR_READS_DUMP") { + // Save the read elements into a file - that can be later read with the tools/cli from zksync-airbender. + let mut file = File::create(&output_csr).expect("Failed to create csr reads file"); + // Write each u32 as an 8-character hexadecimal string without newlines + for num in items.borrow().iter() { + write!(file, "{num:08X}").expect("Failed to write to file"); } - - let proof_input = items.borrow().iter().copied().collect::>(); - debug!( - "{}Proof running output{} = 0x", - colors::GREEN, - colors::RESET + "Successfully wrote {} u32 csr reads elements to file: {}", + items.borrow().len(), + output_csr ); - for word in proof_output.into_iter() { - debug!("{word:08x}"); - } + } + + let proof_input = items.borrow().iter().copied().collect::>(); + + debug!( + "{}Proof running output{} = 0x", + colors::GREEN, + colors::RESET + ); + for word in proof_output.into_iter() { + debug!("{word:08x}"); + } - // Ensure that proof running didn't fail: check that output is not zero - assert!(proof_output.into_iter().any(|word| word != 0)); - let proof_output_u8: [u8; 32] = unsafe { core::mem::transmute(proof_output) }; - - if check_storage_diff_hashes { - // Also ensure that storage diff hash matches - use crypto::MiniDigest; - let mut hasher = crypto::blake2s::Blake2s256::new(); - for StorageWrite { key, value, .. } in block_output.storage_writes.iter() { - hasher.update(key.0.as_ref()); - hasher.update(value.0.as_ref()); - } - let forward_storage_diff_hash = hasher.finalize(); - info!( - "Forward storage diff hash: 0x{}", - hex::encode(forward_storage_diff_hash.as_ref()) - ); - assert_eq!(proof_output_u8, forward_storage_diff_hash); - - #[cfg(feature = "e2e_proving")] - run_prover(items.borrow().as_slice()); + // Ensure that proof running didn't fail: check that output is not zero + assert!(proof_output.into_iter().any(|word| word != 0)); + let proof_output_u8: [u8; 32] = unsafe { core::mem::transmute(proof_output) }; + + if check_storage_diff_hashes { + // Also ensure that storage diff hash matches + use crypto::MiniDigest; + let mut hasher = crypto::blake2s::Blake2s256::new(); + for StorageWrite { key, value, .. } in block_output.storage_writes.iter() { + hasher.update(key.0.as_ref()); + hasher.update(value.0.as_ref()); } + let forward_storage_diff_hash = hasher.finalize(); + info!( + "Forward storage diff hash: 0x{}", + hex::encode(forward_storage_diff_hash.as_ref()) + ); + assert_eq!(proof_output_u8, forward_storage_diff_hash); - proof_input + #[cfg(feature = "e2e_proving")] + run_prover(items.borrow().as_slice()); } + + assert_eq!(prover_input_forward, proof_input); + proof_input } else { vec![] }; - Ok((block_output, stats, proof_input)) + Ok((block_output, stats, proof_input, pubdata, pi_batch_output)) } pub fn get_account_properties(&mut self, address: &B160) -> AccountProperties { diff --git a/tests/rig/src/lib.rs b/tests/rig/src/lib.rs index ce5158c10..cffc42d25 100644 --- a/tests/rig/src/lib.rs +++ b/tests/rig/src/lib.rs @@ -19,6 +19,7 @@ pub use basic_system; pub use callable_oracles; pub use chain::BlockContext; pub use chain::Chain; +pub use crypto; pub use ethers; pub use forward_system; pub use log; @@ -26,6 +27,7 @@ pub use oracle_provider; pub use risc_v_simulator; pub use risc_v_simulator::sim::ProfilerConfig; pub use ruint; +pub use system_hooks; pub use zk_ee; pub use zksync_os_api; pub use zksync_os_interface; diff --git a/tests/rig/src/testing_utils.rs b/tests/rig/src/testing_utils.rs index 4f04ff631..84f776c03 100644 --- a/tests/rig/src/testing_utils.rs +++ b/tests/rig/src/testing_utils.rs @@ -81,7 +81,7 @@ pub fn call_address_and_measure_gas_cost( let mut tracer = CallTracer::default(); - let (output, _, _) = chain + let (output, _, _, _) = chain .run_block_with_extra_stats(transactions, None, None, None, &mut tracer) .expect("Should succeed"); diff --git a/zk_ee/src/common_structs/interop_root_storage.rs b/zk_ee/src/common_structs/interop_root_storage.rs new file mode 100644 index 000000000..6054f4fef --- /dev/null +++ b/zk_ee/src/common_structs/interop_root_storage.rs @@ -0,0 +1,61 @@ +use crate::{ + memory::stack_trait::StackFactory, system::errors::system::SystemError, utils::Bytes32, +}; +use alloc::alloc::Global; +use core::alloc::Allocator; +use ruint::aliases::U256; + +use super::history_list::HistoryList; + +/// Represents a cross-chain interoperability root that enables +/// communication and state verification between different blockchain networks. +#[cfg_attr(feature = "testing", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Default)] +pub struct InteropRoot { + /// The merkle root hash (cannot be zero for valid roots) + pub root: Bytes32, + /// Block or batch number from the source chain + pub block_or_batch_number: U256, + /// Source chain identifier (must be non-zero) + pub chain_id: U256, +} + +pub struct InteropRootStorage, const M: usize, A: Allocator + Clone = Global> { + list: HistoryList, + _marker: core::marker::PhantomData, +} + +impl, const M: usize, A: Allocator + Clone> InteropRootStorage { + pub fn new_from_parts(allocator: A) -> Self { + Self { + list: HistoryList::new(allocator), + _marker: core::marker::PhantomData, + } + } + + #[track_caller] + pub fn start_frame(&mut self) -> usize { + self.list.snapshot() + } + + pub fn push_root(&mut self, interop_root: InteropRoot) -> Result<(), SystemError> { + self.list.push(interop_root, ()); + + Ok(()) + } + + #[track_caller] + pub fn finish_frame(&mut self, rollback_handle: Option) { + if let Some(x) = rollback_handle { + self.list.rollback(x); + } + } + + pub fn iter<'a>(&'a self) -> impl Iterator { + self.list.iter() + } + + pub fn len(&self) -> usize { + self.list.len() + } +} diff --git a/zk_ee/src/common_structs/mod.rs b/zk_ee/src/common_structs/mod.rs index 8f5b999e2..adf0d6b17 100644 --- a/zk_ee/src/common_structs/mod.rs +++ b/zk_ee/src/common_structs/mod.rs @@ -5,12 +5,14 @@ pub mod events_storage; pub mod history_counter; pub mod history_list; pub mod history_map; +pub mod interop_root_storage; pub mod logs_storage; pub mod new_preimages_publication_storage; pub mod proof_data; pub mod pubdata_compression; pub mod skip_list_quasi_vec; pub mod state_root_view; +pub mod system_hooks; pub mod warm_storage_key; pub mod warm_storage_value; diff --git a/zk_ee/src/common_structs/system_hooks.rs b/zk_ee/src/common_structs/system_hooks.rs new file mode 100644 index 000000000..7412026be --- /dev/null +++ b/zk_ee/src/common_structs/system_hooks.rs @@ -0,0 +1,164 @@ +use crate::storage_types::MAX_EVENT_TOPICS; +use crate::system::{ + errors::system::SystemError, CompletedExecution, ExternalCallRequest, System, SystemTypes, +}; +use crate::types_config::SystemIOTypesConfig; +use alloc::collections::BTreeMap; +use core::{alloc::Allocator, mem::MaybeUninit}; + +/// System call hooks process the given call request. +/// +/// The inputs are: +/// - call request +/// - caller ee(logic may depend on it some cases) +/// - system +/// - output buffer +pub struct SystemCallHook( + for<'a> fn( + ExternalCallRequest, + u8, + &mut System, + &'a mut [MaybeUninit], + ) -> Result<(CompletedExecution<'a, S>, &'a mut [MaybeUninit]), SystemError>, +); + +impl SystemCallHook { + pub fn new( + f: for<'a> fn( + ExternalCallRequest, + u8, + &mut System, + &'a mut [MaybeUninit], + ) -> Result< + (CompletedExecution<'a, S>, &'a mut [MaybeUninit]), + SystemError, + >, + ) -> Self { + Self(f) + } +} + +/// System event hooks process the given event. +/// These are just used to report information from +/// system contracts to ZKsync OS. +/// +/// The inputs are: +/// - topics +/// - data +/// - caller ee(logic may depend on it some cases) +/// - system +pub struct SystemEventHook( + for<'a> fn( + &arrayvec::ArrayVec<::EventKey, MAX_EVENT_TOPICS>, + &[u8], + u8, + &mut System, + &mut S::Resources, + ) -> Result<(), SystemError>, +); + +impl SystemEventHook { + pub fn new( + f: for<'a> fn( + &arrayvec::ArrayVec<::EventKey, MAX_EVENT_TOPICS>, + &[u8], + u8, + &mut System, + &mut S::Resources, + ) -> Result<(), SystemError>, + ) -> Self { + Self(f) + } +} + +/// +/// System hooks storage. +/// Stores hooks implementations and processes calls to system addresses. +/// +pub struct HooksStorage { + call_hooks: BTreeMap, A>, + event_hooks: BTreeMap, A>, +} + +impl HooksStorage { + /// + /// Creates empty hooks storage with a given allocator. + /// + pub fn new_in(allocator: A) -> Self { + Self { + call_hooks: BTreeMap::new_in(allocator.clone()), + event_hooks: BTreeMap::new_in(allocator), + } + } + + /// + /// Adds a new call hook into a given address. + /// Fails if there was another hook registered there before. + /// + pub fn add_call_hook(&mut self, for_address_low: u16, hook: SystemCallHook) { + let existing = self.call_hooks.insert(for_address_low, hook); + // TODO: internal error? + assert!(existing.is_none()); + } + + /// + /// Adds a new event hook into a given address. + /// Fails if there was another hook registered there before. + /// + pub fn add_event_hook(&mut self, for_address_low: u32, hook: SystemEventHook) { + let existing = self.event_hooks.insert(for_address_low, hook); + // TODO: internal error? + assert!(existing.is_none()); + } + + /// + /// Intercepts calls to low addresses (< 2^16) and executes hooks + /// stored under that address. If no hook is stored there, return `Ok(None)`. + /// Always return unused return_memory. + /// + pub fn try_intercept<'a>( + &mut self, + address_low: u16, + request: ExternalCallRequest, + caller_ee: u8, + system: &mut System, + return_memory: &'a mut [MaybeUninit], + ) -> Result<(Option>, &'a mut [MaybeUninit]), SystemError> { + let Some(hook) = self.call_hooks.get(&address_low) else { + return Ok((None, return_memory)); + }; + let (res, remaining_memory) = hook.0(request, caller_ee, system, return_memory)?; + + Ok((Some(res), remaining_memory)) + } + + /// Intercepts events emitted from low addresses (< 2^16) and executes hooks + /// stored under that address. If no hook is stored there, return `Ok(None)`. + /// + pub fn try_intercept_event( + &mut self, + address_low: u32, + topics: &arrayvec::ArrayVec< + ::EventKey, + MAX_EVENT_TOPICS, + >, + data: &[u8], + caller_ee: u8, + system: &mut System, + resources: &mut S::Resources, + ) -> Result, SystemError> { + let Some(hook) = self.event_hooks.get(&address_low) else { + return Ok(None); + }; + hook.0(topics, data, caller_ee, system, resources)?; + + Ok(Some(())) + } + + /// + /// Checks if there is a call hook stored for a given low address (<16 bits). + /// + pub fn has_hook_for(&mut self, address_low: u16) -> bool { + self.call_hooks.contains_key(&address_low) + } +} diff --git a/zk_ee/src/system/execution_environment/mod.rs b/zk_ee/src/system/execution_environment/mod.rs index df945fa59..7f3bf683a 100644 --- a/zk_ee/src/system/execution_environment/mod.rs +++ b/zk_ee/src/system/execution_environment/mod.rs @@ -19,6 +19,7 @@ use super::system::System; use super::system::SystemTypes; use super::tracer::Tracer; use super::IOSubsystemExt; +use crate::common_structs::system_hooks::HooksStorage; use crate::common_structs::CalleeAccountProperties; use crate::internal_error; use crate::memory::slice_vec::SliceVec; @@ -68,6 +69,7 @@ pub trait ExecutionEnvironment<'ee, S: SystemTypes, Es: Subsystem>: Sized { fn start_executing_frame<'a, 'i: 'ee, 'h: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, frame_state: ExecutionEnvironmentLaunchParams<'i, S>, heap: SliceVec<'h, u8>, tracer: &mut impl Tracer, @@ -92,6 +94,7 @@ pub trait ExecutionEnvironment<'ee, S: SystemTypes, Es: Subsystem>: Sized { fn continue_after_preemption<'a, 'res: 'ee>( &'a mut self, system: &mut System, + hooks: &mut HooksStorage, returned_resources: S::Resources, call_request_result: CallResult<'res, S>, tracer: &mut impl Tracer, diff --git a/zk_ee/src/system/io.rs b/zk_ee/src/system/io.rs index 079b05f26..0fbb29ee0 100644 --- a/zk_ee/src/system/io.rs +++ b/zk_ee/src/system/io.rs @@ -5,6 +5,7 @@ use core::marker::PhantomData; +use super::common_structs::interop_root_storage::InteropRoot; use super::errors::internal::InternalError; use super::errors::system::SystemError; use super::logger::Logger; @@ -111,6 +112,14 @@ pub trait IOSubsystem: Sized { data: &[u8], ) -> Result; + /// Add an interop root. + fn add_interop_root( + &mut self, + ee_type: ExecutionEnvironmentType, + resources: &mut Self::Resources, + interop_root: InteropRoot, + ) -> Result<(), SystemError>; + /// Mark an account to be destructed at the end of the transaction. /// Perform token transfer to beneficiary. Returns amount of token transferred. fn mark_for_deconstruction( diff --git a/zk_ee/src/system/mod.rs b/zk_ee/src/system/mod.rs index d38c27737..f09c7c89f 100644 --- a/zk_ee/src/system/mod.rs +++ b/zk_ee/src/system/mod.rs @@ -1,3 +1,6 @@ +use arrayvec::ArrayVec; +use common_structs::system_hooks::HooksStorage; +use types_config::TryIntoLowAddress; use utils::num_usize_words_for_u8_capacity; use utils::usize_rw::AsUsizeWritable; @@ -53,6 +56,7 @@ use crate::utils::Bytes32; use crate::{ execution_environment_type::ExecutionEnvironmentType, oracle::IOOracle, + storage_types::MAX_EVENT_TOPICS, types_config::{EthereumIOTypesConfig, SystemIOTypesConfig}, }; @@ -169,6 +173,34 @@ impl System { pub fn net_pubdata_used(&self) -> Result { self.io.net_pubdata_used() } + + /// Emit an event, potentially capturing some using an event hook. + pub fn emit_event( + &mut self, + hooks: &mut HooksStorage, + ee_type: ExecutionEnvironmentType, + resources: &mut S::Resources, + address: &::Address, + topics: &ArrayVec<::EventKey, MAX_EVENT_TOPICS>, + data: &[u8], + ) -> Result<(), SystemError> { + // First, emit the event using io subsystem + self.io + .emit_event(ee_type, resources, address, topics, data)?; + + // If successful, intercept event hook, if any + if let Some(address_low) = address.try_into_low() { + let _ = hooks.try_intercept_event( + address_low, + topics, + data, + ee_type as u8, + self, + resources, + )?; + } + Ok(()) + } } impl System diff --git a/zk_ee/src/types_config/mod.rs b/zk_ee/src/types_config/mod.rs index 9f792562f..5379bf145 100644 --- a/zk_ee/src/types_config/mod.rs +++ b/zk_ee/src/types_config/mod.rs @@ -3,6 +3,13 @@ use crate::{ utils::Bytes32, }; +/// Trait to get the low u32 component +/// of an address, if this address fits into u32. +/// +pub trait TryIntoLowAddress { + fn try_into_low(&self) -> Option; +} + pub trait SystemIOTypesConfig: Sized + 'static + Send + Sync { // We want to define some associated types for addresses, storage keys, etc. // mainly for sizes. We also want to have those interpretable as byte sequences in general. @@ -11,7 +18,8 @@ pub trait SystemIOTypesConfig: Sized + 'static + Send + Sync { + Clone + Copy + core::fmt::Debug - + core::default::Default; + + core::default::Default + + TryIntoLowAddress; type StorageKey: UsizeSerializable + UsizeDeserializable + Clone @@ -54,6 +62,20 @@ pub struct EthereumIOTypesConfig; use ruint::aliases::*; +impl TryIntoLowAddress for B160 { + fn try_into_low(&self) -> Option { + let limbs = self.as_limbs(); + let lo = limbs[0]; + + // low limb must fit in u32, and all higher limbs must be zero + if lo <= u32::MAX as _ && limbs[1..].iter().all(|&w| w == 0) { + Some(lo as u32) + } else { + None + } + } +} + impl SystemIOTypesConfig for EthereumIOTypesConfig { type Address = B160; type StorageKey = Bytes32;