From 3037ee6aa976744a09882b5830d6242ad8336717 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Mon, 6 Jan 2025 16:58:21 +0100 Subject: [PATCH] feat: Features for an easier upgrade (#3422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Sending `execute` operations is stopped when gateway upgrade is ongoing Also, validator timelock is fetched from state transition manager Also, l1 shared bridge is updated from bridgehub ## Why ❔ ## Checklist - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. --- core/lib/contracts/src/lib.rs | 2 +- core/node/api_server/src/web3/state.rs | 4 + .../eth_sender/src/aggregated_operations.rs | 4 + core/node/eth_sender/src/aggregator.rs | 85 +++++- core/node/eth_sender/src/eth_tx_aggregator.rs | 247 +++++++++++++----- core/node/eth_sender/src/tester.rs | 3 + core/node/eth_sender/src/tests.rs | 61 ++++- core/node/eth_sender/src/zksync_functions.rs | 9 +- .../layers/eth_sender/aggregator.rs | 46 ++-- .../web3_api/server/bridge_addresses.rs | 82 +++++- .../layers/web3_api/server/mod.rs | 38 ++- 11 files changed, 456 insertions(+), 125 deletions(-) diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs index ba22ba8d1b95..9ca679fef899 100644 --- a/core/lib/contracts/src/lib.rs +++ b/core/lib/contracts/src/lib.rs @@ -37,7 +37,7 @@ const FORGE_PATH_PREFIX: &str = "contracts/l1-contracts/out"; const BRIDGEHUB_CONTRACT_FILE: (&str, &str) = ("bridgehub", "IBridgehub.sol/IBridgehub.json"); const STATE_TRANSITION_CONTRACT_FILE: (&str, &str) = ( "state-transition", - "IStateTransitionManager.sol/IStateTransitionManager.json", + "StateTransitionManager.sol/StateTransitionManager.json", ); const ZKSYNC_HYPERCHAIN_CONTRACT_FILE: (&str, &str) = ( "state-transition/chain-interfaces", diff --git a/core/node/api_server/src/web3/state.rs b/core/node/api_server/src/web3/state.rs index bdefd79b6dd6..a50b9d062321 100644 --- a/core/node/api_server/src/web3/state.rs +++ b/core/node/api_server/src/web3/state.rs @@ -232,6 +232,10 @@ impl BridgeAddressesHandle { *self.0.write().await = bridge_addresses; } + pub async fn update_l1_shared_bridge(&self, l1_shared_bridge: Address) { + self.0.write().await.l1_shared_default_bridge = Some(l1_shared_bridge); + } + pub async fn read(&self) -> api::BridgeAddresses { self.0.read().await.clone() } diff --git a/core/node/eth_sender/src/aggregated_operations.rs b/core/node/eth_sender/src/aggregated_operations.rs index 5271d42d3b75..35cb1648116d 100644 --- a/core/node/eth_sender/src/aggregated_operations.rs +++ b/core/node/eth_sender/src/aggregated_operations.rs @@ -62,4 +62,8 @@ impl AggregatedOperation { self.get_action_type() == AggregatedActionType::PublishProofOnchain || self.get_action_type() == AggregatedActionType::Execute } + + pub fn is_execute(&self) -> bool { + self.get_action_type() == AggregatedActionType::Execute + } } diff --git a/core/node/eth_sender/src/aggregator.rs b/core/node/eth_sender/src/aggregator.rs index 3a318f44bcea..33b9500b2d18 100644 --- a/core/node/eth_sender/src/aggregator.rs +++ b/core/node/eth_sender/src/aggregator.rs @@ -51,6 +51,61 @@ pub struct Aggregator { priority_tree_start_index: Option, } +/// Denotes whether there are any restrictions on sending either +/// commit, prove or execute operations. If there is one, the reason for it +/// is stored to be logged. +#[derive(Debug, Default)] +pub(crate) struct OperationSkippingRestrictions { + pub(crate) commit_restriction: Option<&'static str>, + pub(crate) prove_restriction: Option<&'static str>, + pub(crate) execute_restriction: Option<&'static str>, +} + +impl OperationSkippingRestrictions { + fn check_for_continuation( + &self, + agg_op: &AggregatedOperation, + reason: Option<&'static str>, + ) -> bool { + if let Some(reason) = reason { + tracing::info!( + "Skipping sending commit operation of type {} for batches {}-{} \ + since {}", + agg_op.get_action_type(), + agg_op.l1_batch_range().start(), + agg_op.l1_batch_range().end(), + reason + ); + false + } else { + true + } + } + + // Unlike other funcitons `filter_commit_op` accepts an already prepared `AggregatedOperation` for + // easier compatibility with other interfaces in the file. + fn filter_commit_op( + &self, + commit_op: Option, + ) -> Option { + let commit_op = commit_op?; + self.check_for_continuation(&commit_op, self.commit_restriction) + .then_some(commit_op) + } + + fn filter_prove_op(&self, prove_op: Option) -> Option { + let op = AggregatedOperation::PublishProofOnchain(prove_op?); + self.check_for_continuation(&op, self.commit_restriction) + .then_some(op) + } + + fn filter_execute_op(&self, execute_op: Option) -> Option { + let op = AggregatedOperation::Execute(execute_op?); + self.check_for_continuation(&op, self.commit_restriction) + .then_some(op) + } +} + impl Aggregator { pub async fn new( config: SenderConfig, @@ -153,12 +208,13 @@ impl Aggregator { }) } - pub async fn get_next_ready_operation( + pub(crate) async fn get_next_ready_operation( &mut self, storage: &mut Connection<'_, Core>, base_system_contracts_hashes: BaseSystemContractsHashes, protocol_version_id: ProtocolVersionId, l1_verifier_config: L1VerifierConfig, + restrictions: OperationSkippingRestrictions, ) -> Result, EthSenderError> { let Some(last_sealed_l1_batch_number) = storage .blocks_dal() @@ -169,30 +225,31 @@ impl Aggregator { return Ok(None); // No L1 batches in Postgres; no operations are ready yet }; - if let Some(op) = self - .get_execute_operations( + if let Some(op) = restrictions.filter_execute_op( + self.get_execute_operations( storage, self.config.max_aggregated_blocks_to_execute as usize, last_sealed_l1_batch_number, ) - .await? - { - Ok(Some(AggregatedOperation::Execute(op))) - } else if let Some(op) = self - .get_proof_operation(storage, last_sealed_l1_batch_number, l1_verifier_config) - .await - { - Ok(Some(AggregatedOperation::PublishProofOnchain(op))) + .await?, + ) { + Ok(Some(op)) + } else if let Some(op) = restrictions.filter_prove_op( + self.get_proof_operation(storage, last_sealed_l1_batch_number, l1_verifier_config) + .await, + ) { + Ok(Some(op)) } else { - Ok(self - .get_commit_operation( + Ok(restrictions.filter_commit_op( + self.get_commit_operation( storage, self.config.max_aggregated_blocks_to_commit as usize, last_sealed_l1_batch_number, base_system_contracts_hashes, protocol_version_id, ) - .await) + .await, + )) } } diff --git a/core/node/eth_sender/src/eth_tx_aggregator.rs b/core/node/eth_sender/src/eth_tx_aggregator.rs index 0b176d6cc7f3..8a829ed00faa 100644 --- a/core/node/eth_sender/src/eth_tx_aggregator.rs +++ b/core/node/eth_sender/src/eth_tx_aggregator.rs @@ -28,6 +28,7 @@ use zksync_types::{ use super::aggregated_operations::AggregatedOperation; use crate::{ + aggregator::OperationSkippingRestrictions, health::{EthTxAggregatorHealthDetails, EthTxDetails}, metrics::{PubdataKind, METRICS}, publish_criterion::L1GasCriterion, @@ -41,7 +42,13 @@ use crate::{ pub struct MulticallData { pub base_system_contracts_hashes: BaseSystemContractsHashes, pub verifier_address: Address, - pub protocol_version_id: ProtocolVersionId, + pub chain_protocol_version_id: ProtocolVersionId, + /// The latest validator timelock that is stored on the StateTransitionManager (ChainTypeManager). + /// For a smoother upgrade process, if the `stm_protocol_version_id` is the same as `chain_protocol_version_id`, + /// we will use the validator timelock from the CTM. This removes the need to immediately set the correct + /// validator timelock in the config. However, it is expected that it will be done eventually. + pub stm_validator_timelock_address: Address, + pub stm_protocol_version_id: ProtocolVersionId, } /// The component is responsible for aggregating l1 batches into eth_txs: @@ -52,9 +59,15 @@ pub struct EthTxAggregator { aggregator: Aggregator, eth_client: Box, config: SenderConfig, - timelock_contract_address: Address, + // The validator timelock address provided in the config. + // If the contracts have the same protocol version as the state transition manager, the validator timelock + // from the state transition manager will be used. + // The address provided from the config is only used when there is a discrepancy between the two. + // TODO(EVM-932): always fetch the validator timelock from L1, but it requires a protocol change. + config_timelock_contract_address: Address, l1_multicall3_address: Address, pub(super) state_transition_chain_contract: Address, + state_transition_manager_address: Address, functions: ZkSyncFunctions, base_nonce: u64, base_nonce_custom_commit_sender: Option, @@ -84,7 +97,8 @@ impl EthTxAggregator { config: SenderConfig, aggregator: Aggregator, eth_client: Box, - timelock_contract_address: Address, + config_timelock_contract_address: Address, + state_transition_manager_address: Address, l1_multicall3_address: Address, state_transition_chain_contract: Address, rollup_chain_id: L2ChainId, @@ -113,7 +127,8 @@ impl EthTxAggregator { config, aggregator, eth_client, - timelock_contract_address, + config_timelock_contract_address, + state_transition_manager_address, l1_multicall3_address, state_transition_chain_contract, functions, @@ -229,12 +244,40 @@ impl EthTxAggregator { calldata: get_protocol_version_input, }; + let get_stm_protocol_version_input = self + .functions + .state_transition_manager_contract + .function("protocolVersion") + .unwrap() + .encode_input(&[]) + .unwrap(); + let get_stm_protocol_version_call = Multicall3Call { + target: self.state_transition_manager_address, + allow_failure: ALLOW_FAILURE, + calldata: get_stm_protocol_version_input, + }; + + let get_stm_validator_timelock_input = self + .functions + .state_transition_manager_contract + .function("validatorTimelock") + .unwrap() + .encode_input(&[]) + .unwrap(); + let get_stm_validator_timelock_call = Multicall3Call { + target: self.state_transition_manager_address, + allow_failure: ALLOW_FAILURE, + calldata: get_stm_validator_timelock_input, + }; + let mut token_vec = vec![ get_bootloader_hash_call.into_token(), get_default_aa_hash_call.into_token(), get_verifier_params_call.into_token(), get_verifier_call.into_token(), get_protocol_version_call.into_token(), + get_stm_protocol_version_call.into_token(), + get_stm_validator_timelock_call.into_token(), ]; let mut evm_emulator_hash_requested = false; @@ -270,8 +313,8 @@ impl EthTxAggregator { }; if let Token::Array(call_results) = token { - let number_of_calls = if evm_emulator_hash_requested { 6 } else { 5 }; - // 5 or 6 calls are aggregated in multicall + let number_of_calls = if evm_emulator_hash_requested { 8 } else { 7 }; + // 7 or 8 calls are aggregated in multicall if call_results.len() != number_of_calls { return parse_error(&call_results); } @@ -327,47 +370,86 @@ impl EthTxAggregator { call_results_iterator.next().unwrap(); // FIXME: why is this value requested? - let multicall3_verifier_address = - Multicall3Result::from_token(call_results_iterator.next().unwrap())?.return_data; - if multicall3_verifier_address.len() != 32 { - return Err(EthSenderError::Parse(Web3ContractError::InvalidOutputType( - format!( - "multicall3 verifier address data is not of the len of 32: {:?}", - multicall3_verifier_address - ), - ))); - } - let verifier_address = Address::from_slice(&multicall3_verifier_address[12..]); - - let multicall3_protocol_version = - Multicall3Result::from_token(call_results_iterator.next().unwrap())?.return_data; - if multicall3_protocol_version.len() != 32 { - return Err(EthSenderError::Parse(Web3ContractError::InvalidOutputType( - format!( - "multicall3 protocol version data is not of the len of 32: {:?}", - multicall3_protocol_version - ), - ))); - } - - let protocol_version = U256::from_big_endian(&multicall3_protocol_version); - // In case the protocol version is smaller than `PACKED_SEMVER_MINOR_MASK`, it will mean that it is - // equal to the `protocol_version_id` value, since it the interface from before the semver was supported. - let protocol_version_id = if protocol_version < U256::from(PACKED_SEMVER_MINOR_MASK) { - ProtocolVersionId::try_from(protocol_version.as_u32() as u16).unwrap() - } else { - ProtocolVersionId::try_from_packed_semver(protocol_version).unwrap() - }; + let verifier_address = + Self::parse_address(call_results_iterator.next().unwrap(), "verifier address")?; + + let chain_protocol_version_id = Self::parse_protocol_version( + call_results_iterator.next().unwrap(), + "contract protocol version", + )?; + let stm_protocol_version_id = Self::parse_protocol_version( + call_results_iterator.next().unwrap(), + "STM protocol version", + )?; + let stm_validator_timelock_address = Self::parse_address( + call_results_iterator.next().unwrap(), + "STM validator timelock address", + )?; return Ok(MulticallData { base_system_contracts_hashes, verifier_address, - protocol_version_id, + chain_protocol_version_id, + stm_protocol_version_id, + stm_validator_timelock_address, }); } parse_error(&[token]) } + fn parse_protocol_version( + data: Token, + name: &'static str, + ) -> Result { + let multicall_data = Multicall3Result::from_token(data)?.return_data; + if multicall_data.len() != 32 { + return Err(EthSenderError::Parse(Web3ContractError::InvalidOutputType( + format!( + "multicall3 {name} data is not of the len of 32: {:?}", + multicall_data + ), + ))); + } + + let protocol_version = U256::from_big_endian(&multicall_data); + // In case the protocol version is smaller than `PACKED_SEMVER_MINOR_MASK`, it will mean that it is + // equal to the `protocol_version_id` value, since it the interface from before the semver was supported. + let protocol_version_id = if protocol_version < U256::from(PACKED_SEMVER_MINOR_MASK) { + ProtocolVersionId::try_from(protocol_version.as_u32() as u16).unwrap() + } else { + ProtocolVersionId::try_from_packed_semver(protocol_version).unwrap() + }; + + Ok(protocol_version_id) + } + + fn parse_address(data: Token, name: &'static str) -> Result { + let multicall_data = Multicall3Result::from_token(data)?.return_data; + if multicall_data.len() != 32 { + return Err(EthSenderError::Parse(Web3ContractError::InvalidOutputType( + format!( + "multicall3 {name} data is not of the len of 32: {:?}", + multicall_data + ), + ))); + } + + Ok(Address::from_slice(&multicall_data[12..])) + } + + fn timelock_contract_address( + &self, + chain_protocol_version_id: ProtocolVersionId, + stm_protocol_version_id: ProtocolVersionId, + stm_validator_timelock_address: Address, + ) -> Address { + if chain_protocol_version_id == stm_protocol_version_id { + stm_validator_timelock_address + } else { + self.config_timelock_contract_address + } + } + /// Loads current verifier config on L1 async fn get_snark_wrapper_vk_hash( &mut self, @@ -382,6 +464,32 @@ impl EthTxAggregator { Ok(vk_hash) } + /// Returns whether there is a pending gateway upgrade. + /// During gateway upgrade, the signature of the `executeBatches` function on `ValidatorTimelock` will change. + /// This means that transactions that were created before the upgrade but were sent right after it + /// will fail, which we want to avoid. + async fn is_pending_gateway_upgrade( + storage: &mut Connection<'_, Core>, + chain_protocol_version: ProtocolVersionId, + ) -> bool { + // If the gateway protocol version is present in the DB, and its timestamp is larger than `now`, it means that + // the upgrade process on the server has begun. + // However, if the protocol version on the contract is lower than the `gateway_upgrade`, it means that the upgrade has + // not yet completed. + + if storage + .blocks_dal() + .pending_protocol_version() + .await + .unwrap() + < ProtocolVersionId::gateway_upgrade() + { + return false; + } + + chain_protocol_version < ProtocolVersionId::gateway_upgrade() + } + async fn get_fflonk_snark_wrapper_vk_hash( &mut self, verifier_address: Address, @@ -417,7 +525,9 @@ impl EthTxAggregator { let MulticallData { base_system_contracts_hashes, verifier_address, - protocol_version_id, + chain_protocol_version_id, + stm_protocol_version_id, + stm_validator_timelock_address, } = self.get_multicall_data().await.map_err(|err| { tracing::error!("Failed to get multicall data {err:?}"); err @@ -442,37 +552,51 @@ impl EthTxAggregator { snark_wrapper_vk_hash, fflonk_snark_wrapper_vk_hash, }; + + let mut op_restrictions = OperationSkippingRestrictions { + commit_restriction: self + .config + .tx_aggregation_only_prove_and_execute + .then_some("tx_aggregation_only_prove_and_execute=true"), + prove_restriction: None, + execute_restriction: Self::is_pending_gateway_upgrade( + storage, + chain_protocol_version_id, + ) + .await + .then_some("there is a pending gateway upgrade"), + }; + if self.config.tx_aggregation_paused { + let reason = Some("tx aggregation is paused"); + op_restrictions.commit_restriction = reason; + op_restrictions.prove_restriction = reason; + op_restrictions.execute_restriction = reason; + } + if let Some(agg_op) = self .aggregator .get_next_ready_operation( storage, base_system_contracts_hashes, - protocol_version_id, + chain_protocol_version_id, l1_verifier_config, + op_restrictions, ) .await? { - if self.config.tx_aggregation_paused { - tracing::info!( - "Skipping sending operation of type {} for batches {}-{} \ - as tx_aggregation_paused=true", - agg_op.get_action_type(), - agg_op.l1_batch_range().start(), - agg_op.l1_batch_range().end() - ); - return Ok(()); - } - if self.config.tx_aggregation_only_prove_and_execute && !agg_op.is_prove_or_execute() { - tracing::info!( - "Skipping sending commit operation for batches {}-{} \ - as tx_aggregation_only_prove_and_execute=true", - agg_op.l1_batch_range().start(), - agg_op.l1_batch_range().end() - ); - return Ok(()); - } let is_gateway = self.settlement_mode.is_gateway(); - let tx = self.save_eth_tx(storage, &agg_op, is_gateway).await?; + let tx = self + .save_eth_tx( + storage, + &agg_op, + self.timelock_contract_address( + chain_protocol_version_id, + stm_protocol_version_id, + stm_validator_timelock_address, + ), + is_gateway, + ) + .await?; Self::report_eth_tx_saving(storage, &agg_op, &tx).await; self.health_updater.update( @@ -618,6 +742,7 @@ impl EthTxAggregator { &self, storage: &mut Connection<'_, Core>, aggregated_op: &AggregatedOperation, + timelock_contract_address: Address, is_gateway: bool, ) -> Result { let mut transaction = storage.start_transaction().await.unwrap(); @@ -653,7 +778,7 @@ impl EthTxAggregator { nonce, encoded_aggregated_op.calldata, op_type, - self.timelock_contract_address, + timelock_contract_address, eth_tx_predicted_gas, sender_addr, encoded_aggregated_op.sidecar, diff --git a/core/node/eth_sender/src/tester.rs b/core/node/eth_sender/src/tester.rs index e7d9f2ac87e7..943e808cfa6b 100644 --- a/core/node/eth_sender/src/tester.rs +++ b/core/node/eth_sender/src/tester.rs @@ -24,6 +24,7 @@ use crate::{ }; pub(super) const STATE_TRANSITION_CONTRACT_ADDRESS: Address = Address::repeat_byte(0xa0); +pub(super) const STATE_TRANSITION_MANAGER_CONTRACT_ADDRESS: Address = Address::repeat_byte(0xb0); // Alias to conveniently call static methods of `ETHSender`. type MockEthTxManager = EthTxManager; @@ -268,6 +269,7 @@ impl EthSenderTester { gateway.clone(), // ZKsync contract address Address::random(), + STATE_TRANSITION_MANAGER_CONTRACT_ADDRESS, contracts_config.l1_multicall3_addr, STATE_TRANSITION_CONTRACT_ADDRESS, Default::default(), @@ -522,6 +524,7 @@ impl EthSenderTester { .save_eth_tx( &mut self.conn.connection().await.unwrap(), &aggregated_operation, + Address::random(), self.is_l2, ) .await diff --git a/core/node/eth_sender/src/tests.rs b/core/node/eth_sender/src/tests.rs index aab6d2e43d76..f104d222982a 100644 --- a/core/node/eth_sender/src/tests.rs +++ b/core/node/eth_sender/src/tests.rs @@ -11,18 +11,19 @@ use zksync_types::{ commitment::{ L1BatchCommitmentMode, L1BatchMetaParameters, L1BatchMetadata, L1BatchWithMetadata, }, - ethabi, - ethabi::Token, + ethabi::{self, Token}, helpers::unix_timestamp_ms, - web3, - web3::contract::Error, + web3::{self, contract::Error}, Address, ProtocolVersionId, H256, }; use crate::{ abstract_l1_interface::OperatorType, aggregated_operations::AggregatedOperation, - tester::{EthSenderTester, TestL1Batch, STATE_TRANSITION_CONTRACT_ADDRESS}, + tester::{ + EthSenderTester, TestL1Batch, STATE_TRANSITION_CONTRACT_ADDRESS, + STATE_TRANSITION_MANAGER_CONTRACT_ADDRESS, + }, zksync_functions::ZkSyncFunctions, EthSenderError, }; @@ -66,27 +67,53 @@ pub(crate) fn mock_multicall_response(call: &web3::CallRequest) -> Token { panic!("Unexpected input: {tokens:?}"); }; + let validator_timelock_short_selector = functions + .state_transition_manager_contract + .function("validatorTimelock") + .unwrap() + .short_signature(); + let prototol_version_short_selector = functions + .state_transition_manager_contract + .function("protocolVersion") + .unwrap() + .short_signature(); + let calls = tokens.into_iter().map(Multicall3Call::from_token); let response = calls.map(|call| { let call = call.unwrap(); - assert_eq!(call.target, STATE_TRANSITION_CONTRACT_ADDRESS); let output = match &call.calldata[..4] { selector if selector == bootloader_signature => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); vec![1u8; 32] } selector if selector == default_aa_signature => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); vec![2u8; 32] } selector if Some(selector) == evm_emulator_getter_signature => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); vec![3u8; 32] } selector if selector == functions.get_verifier_params.short_signature() => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); vec![4u8; 96] } selector if selector == functions.get_verifier.short_signature() => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); vec![5u8; 32] } selector if selector == functions.get_protocol_version.short_signature() => { + assert!(call.target == STATE_TRANSITION_CONTRACT_ADDRESS); + H256::from_low_u64_be(ProtocolVersionId::default() as u64) + .0 + .to_vec() + } + selector if selector == validator_timelock_short_selector => { + assert!(call.target == STATE_TRANSITION_MANAGER_CONTRACT_ADDRESS); + vec![6u8; 32] + } + selector if selector == prototol_version_short_selector => { + assert!(call.target == STATE_TRANSITION_MANAGER_CONTRACT_ADDRESS); H256::from_low_u64_be(ProtocolVersionId::default() as u64) .0 .to_vec() @@ -208,6 +235,7 @@ async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Re .save_eth_tx( &mut tester.conn.connection().await.unwrap(), &get_dummy_operation(0), + Address::random(), false, ) .await?; @@ -729,6 +757,15 @@ async fn parsing_multicall_data(with_evm_emulator: bool) { .to_vec(), ), ]), + Token::Tuple(vec![ + Token::Bool(true), + Token::Bytes( + H256::from_low_u64_be(ProtocolVersionId::latest() as u64) + .0 + .to_vec(), + ), + ]), + Token::Tuple(vec![Token::Bool(true), Token::Bytes(vec![6u8; 32])]), ]; if with_evm_emulator { mock_response.insert( @@ -756,7 +793,15 @@ async fn parsing_multicall_data(with_evm_emulator: bool) { expected_evm_emulator_hash ); assert_eq!(parsed.verifier_address, Address::repeat_byte(5)); - assert_eq!(parsed.protocol_version_id, ProtocolVersionId::latest()); + assert_eq!( + parsed.chain_protocol_version_id, + ProtocolVersionId::latest() + ); + assert_eq!( + parsed.stm_validator_timelock_address, + Address::repeat_byte(6) + ); + assert_eq!(parsed.stm_protocol_version_id, ProtocolVersionId::latest()); } #[test_log::test(tokio::test)] @@ -848,5 +893,5 @@ async fn get_multicall_data(commitment_mode: L1BatchCommitmentMode) { ); assert_eq!(data.base_system_contracts_hashes.evm_emulator, None); assert_eq!(data.verifier_address, Address::repeat_byte(5)); - assert_eq!(data.protocol_version_id, ProtocolVersionId::latest()); + assert_eq!(data.chain_protocol_version_id, ProtocolVersionId::latest()); } diff --git a/core/node/eth_sender/src/zksync_functions.rs b/core/node/eth_sender/src/zksync_functions.rs index f3e4998ef37c..5c2088f7cec7 100644 --- a/core/node/eth_sender/src/zksync_functions.rs +++ b/core/node/eth_sender/src/zksync_functions.rs @@ -1,6 +1,7 @@ use zksync_contracts::{ - hyperchain_contract, multicall_contract, verifier_contract, POST_SHARED_BRIDGE_COMMIT_FUNCTION, - POST_SHARED_BRIDGE_EXECUTE_FUNCTION, POST_SHARED_BRIDGE_PROVE_FUNCTION, + hyperchain_contract, multicall_contract, state_transition_manager_contract, verifier_contract, + POST_SHARED_BRIDGE_COMMIT_FUNCTION, POST_SHARED_BRIDGE_EXECUTE_FUNCTION, + POST_SHARED_BRIDGE_PROVE_FUNCTION, }; use zksync_types::ethabi::{Contract, Function}; @@ -26,6 +27,8 @@ pub(super) struct ZkSyncFunctions { pub(super) multicall_contract: Contract, pub(super) aggregate3: Function, + + pub(super) state_transition_manager_contract: Contract, } fn get_function(contract: &Contract, name: &str) -> Function { @@ -51,6 +54,7 @@ impl Default for ZkSyncFunctions { let zksync_contract = hyperchain_contract(); let verifier_contract = verifier_contract(); let multicall_contract = multicall_contract(); + let state_transition_manager_contract = state_transition_manager_contract(); let post_shared_bridge_commit = POST_SHARED_BRIDGE_COMMIT_FUNCTION.clone(); let post_shared_bridge_prove = POST_SHARED_BRIDGE_PROVE_FUNCTION.clone(); @@ -89,6 +93,7 @@ impl Default for ZkSyncFunctions { verification_key_hash, multicall_contract, aggregate3, + state_transition_manager_contract, } } } diff --git a/core/node/node_framework/src/implementations/layers/eth_sender/aggregator.rs b/core/node/node_framework/src/implementations/layers/eth_sender/aggregator.rs index b412c376f68d..1642db9be367 100644 --- a/core/node/node_framework/src/implementations/layers/eth_sender/aggregator.rs +++ b/core/node/node_framework/src/implementations/layers/eth_sender/aggregator.rs @@ -111,24 +111,33 @@ impl WiringLayer for EthTxAggregatorLayer { tracing::info!("Gateway contracts: {:?}", self.gateway_chain_config); // Get resources. - let (validator_timelock_addr, multicall3_addr, diamond_proxy_addr) = - if self.settlement_mode.is_gateway() { - let gateway_chain_config = self - .gateway_chain_config - .as_ref() - .context("gateway_chain_config")?; - ( - gateway_chain_config.validator_timelock_addr, - gateway_chain_config.multicall3_addr, - gateway_chain_config.diamond_proxy_addr, - ) - } else { - ( - self.contracts_config.validator_timelock_addr, - self.contracts_config.l1_multicall3_addr, - self.contracts_config.diamond_proxy_addr, - ) - }; + let ( + validator_timelock_addr, + multicall3_addr, + diamond_proxy_addr, + state_transition_manager_address, + ) = if self.settlement_mode.is_gateway() { + let gateway_chain_config = self + .gateway_chain_config + .as_ref() + .context("gateway_chain_config")?; + ( + gateway_chain_config.validator_timelock_addr, + gateway_chain_config.multicall3_addr, + gateway_chain_config.diamond_proxy_addr, + gateway_chain_config.state_transition_proxy_addr, + ) + } else { + ( + self.contracts_config.validator_timelock_addr, + self.contracts_config.l1_multicall3_addr, + self.contracts_config.diamond_proxy_addr, + self.contracts_config + .ecosystem_contracts + .context("Missing ecosystem contracts")? + .state_transition_proxy_addr, + ) + }; let eth_client = if self.settlement_mode.is_gateway() { input @@ -167,6 +176,7 @@ impl WiringLayer for EthTxAggregatorLayer { aggregator, eth_client, validator_timelock_addr, + state_transition_manager_address, multicall3_addr, diamond_proxy_addr, self.zksync_network_id, diff --git a/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs b/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs index 4ba8098c8399..785c19846a60 100644 --- a/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs +++ b/core/node/node_framework/src/implementations/layers/web3_api/server/bridge_addresses.rs @@ -1,20 +1,87 @@ use std::time::Duration; +use zksync_eth_client::CallFunctionArgs; use zksync_node_api_server::web3::state::BridgeAddressesHandle; +use zksync_types::{ethabi::Contract, Address}; use zksync_web3_decl::{ - client::{DynClient, L2}, + client::{DynClient, L1, L2}, namespaces::ZksNamespaceClient, }; use crate::{StopReceiver, Task, TaskId}; #[derive(Debug)] -pub struct BridgeAddressesUpdaterTask { +pub struct MainNodeUpdaterInner { pub bridge_address_updater: BridgeAddressesHandle, pub main_node_client: Box>, pub update_interval: Option, } +impl MainNodeUpdaterInner { + async fn loop_iteration(&self) { + match self.main_node_client.get_bridge_contracts().await { + Ok(bridge_addresses) => { + self.bridge_address_updater.update(bridge_addresses).await; + } + Err(err) => { + tracing::error!("Failed to query `get_bridge_contracts`, error: {:?}", err); + } + } + } +} + +#[derive(Debug)] +pub struct L1UpdaterInner { + pub bridge_address_updater: BridgeAddressesHandle, + pub l1_eth_client: Box>, + pub bridgehub_addr: Address, + pub update_interval: Option, + pub bridgehub_abi: Contract, +} + +impl L1UpdaterInner { + async fn loop_iteration(&self) { + let call_result = CallFunctionArgs::new("sharedBridge", ()) + .for_contract(self.bridgehub_addr, &self.bridgehub_abi) + .call(&self.l1_eth_client) + .await; + + match call_result { + Ok(shared_bridge_address) => { + self.bridge_address_updater + .update_l1_shared_bridge(shared_bridge_address) + .await; + } + Err(err) => { + tracing::error!("Failed to query shared bridge address, error: {err:?}"); + } + } + } +} + +// Define the enum to hold either updater +#[derive(Debug)] +pub enum BridgeAddressesUpdaterTask { + L1Updater(L1UpdaterInner), + MainNodeUpdater(MainNodeUpdaterInner), +} + +impl BridgeAddressesUpdaterTask { + async fn loop_iteration(&self) { + match self { + BridgeAddressesUpdaterTask::L1Updater(updater) => updater.loop_iteration().await, + BridgeAddressesUpdaterTask::MainNodeUpdater(updater) => updater.loop_iteration().await, + } + } + + fn update_interval(&self) -> Option { + match self { + BridgeAddressesUpdaterTask::L1Updater(updater) => updater.update_interval, + BridgeAddressesUpdaterTask::MainNodeUpdater(updater) => updater.update_interval, + } + } +} + #[async_trait::async_trait] impl Task for BridgeAddressesUpdaterTask { fn id(&self) -> TaskId { @@ -24,16 +91,9 @@ impl Task for BridgeAddressesUpdaterTask { async fn run(self: Box, mut stop_receiver: StopReceiver) -> anyhow::Result<()> { const DEFAULT_INTERVAL: Duration = Duration::from_secs(30); - let update_interval = self.update_interval.unwrap_or(DEFAULT_INTERVAL); + let update_interval = self.update_interval().unwrap_or(DEFAULT_INTERVAL); while !*stop_receiver.0.borrow_and_update() { - match self.main_node_client.get_bridge_contracts().await { - Ok(bridge_addresses) => { - self.bridge_address_updater.update(bridge_addresses).await; - } - Err(err) => { - tracing::error!("Failed to query `get_bridge_contracts`, error: {err:?}"); - } - } + self.loop_iteration().await; if tokio::time::timeout(update_interval, stop_receiver.0.changed()) .await diff --git a/core/node/node_framework/src/implementations/layers/web3_api/server/mod.rs b/core/node/node_framework/src/implementations/layers/web3_api/server/mod.rs index 390d321647cf..c4c18b6ecb3f 100644 --- a/core/node/node_framework/src/implementations/layers/web3_api/server/mod.rs +++ b/core/node/node_framework/src/implementations/layers/web3_api/server/mod.rs @@ -1,8 +1,11 @@ use std::{num::NonZeroU32, time::Duration}; +use anyhow::Context; +use bridge_addresses::{L1UpdaterInner, MainNodeUpdaterInner}; use tokio::{sync::oneshot, task::JoinHandle}; use zksync_circuit_breaker::replication_lag::ReplicationLagChecker; use zksync_config::configs::api::MaxResponseSize; +use zksync_contracts::bridgehub_contract; use zksync_node_api_server::web3::{ state::{BridgeAddressesHandle, InternalApiConfig, SealedL2BlockNumber}, ApiBuilder, ApiServer, Namespace, @@ -15,6 +18,7 @@ use crate::{ }, resources::{ circuit_breakers::CircuitBreakersResource, + eth_interface::EthInterfaceResource, healthcheck::AppHealthCheckResource, main_node_client::MainNodeClientResource, pools::{PoolResource, ReplicaPool}, @@ -128,6 +132,7 @@ pub struct Input { #[context(default)] pub app_health: AppHealthCheckResource, pub main_node_client: Option, + pub l1_eth_client: EthInterfaceResource, } #[derive(Debug, IntoContext)] @@ -140,7 +145,7 @@ pub struct Output { #[context(task)] pub sealed_l2_block_updater_task: SealedL2BlockUpdaterTask, #[context(task)] - pub bridge_addresses_updater_task: Option, + pub bridge_addresses_updater_task: BridgeAddressesUpdaterTask, } impl Web3ServerLayer { @@ -201,15 +206,28 @@ impl WiringLayer for Web3ServerLayer { number_updater: sealed_l2_block_handle.clone(), pool: updaters_pool, }; - // Bridge addresses updater task must be started for ENs and only for ENs. - let bridge_addresses_updater_task = - input - .main_node_client - .map(|main_node_client| BridgeAddressesUpdaterTask { - bridge_address_updater: bridge_addresses_handle.clone(), - main_node_client: main_node_client.0, - update_interval: self.optional_config.bridge_addresses_refresh_interval, - }); + + // In case it is an EN, the bridge addresses should be updated by fetching values from the main node. + // It is the main node, the bridge addresses need to be updated by querying the L1. + + let bridge_addresses_updater_task = if let Some(main_node_client) = input.main_node_client { + BridgeAddressesUpdaterTask::MainNodeUpdater(MainNodeUpdaterInner { + bridge_address_updater: bridge_addresses_handle.clone(), + main_node_client: main_node_client.0, + update_interval: self.optional_config.bridge_addresses_refresh_interval, + }) + } else { + BridgeAddressesUpdaterTask::L1Updater(L1UpdaterInner { + bridge_address_updater: bridge_addresses_handle.clone(), + l1_eth_client: input.l1_eth_client.0, + bridgehub_addr: self + .internal_api_config + .l1_bridgehub_proxy_addr + .context("Lacking l1 bridgehub proxy address")?, + update_interval: self.optional_config.bridge_addresses_refresh_interval, + bridgehub_abi: bridgehub_contract(), + }) + }; // Build server. let mut api_builder =