Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Features for an easier upgrade #3422

Merged
merged 12 commits into from
Jan 6, 2025
2 changes: 1 addition & 1 deletion core/lib/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions core/node/api_server/src/web3/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
4 changes: 4 additions & 0 deletions core/node/eth_sender/src/aggregated_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
221 changes: 179 additions & 42 deletions core/node/eth_sender/src/eth_tx_aggregator.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::Utc;
use tokio::sync::watch;
use zksync_config::configs::eth_sender::SenderConfig;
use zksync_contracts::BaseSystemContractsHashes;
Expand Down Expand Up @@ -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:
Expand All @@ -52,9 +59,15 @@ pub struct EthTxAggregator {
aggregator: Aggregator,
eth_client: Box<dyn BoundEthInterface>,
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.
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
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<u64>,
Expand Down Expand Up @@ -84,7 +97,8 @@ impl EthTxAggregator {
config: SenderConfig,
aggregator: Aggregator,
eth_client: Box<dyn BoundEthInterface>,
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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<ProtocolVersionId, EthSenderError> {
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<Address, EthSenderError> {
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,
Expand All @@ -382,6 +464,34 @@ 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.
let Some(gateway_protocol_version) = storage
.protocol_versions_dal()
.get_protocol_version_with_latest_patch(ProtocolVersionId::gateway_upgrade())
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
.await
.unwrap()
else {
return false;
};

if gateway_protocol_version.timestamp < Utc::now().timestamp() as u64 {
return false;
}

chain_protocol_version < ProtocolVersionId::gateway_upgrade()
}

async fn get_fflonk_snark_wrapper_vk_hash(
&mut self,
verifier_address: Address,
Expand Down Expand Up @@ -417,7 +527,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
Expand Down Expand Up @@ -447,7 +559,7 @@ impl EthTxAggregator {
.get_next_ready_operation(
storage,
base_system_contracts_hashes,
protocol_version_id,
chain_protocol_version_id,
l1_verifier_config,
)
.await?
Expand All @@ -471,8 +583,32 @@ impl EthTxAggregator {
);
return Ok(());
}

if Self::is_pending_gateway_upgrade(storage, chain_protocol_version_id).await
&& agg_op.is_execute()
{
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
tracing::info!(
"Skipping sending execute operation for batches {}-{} \
as there is a pending gateway upgrade",
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(
Expand Down Expand Up @@ -618,6 +754,7 @@ impl EthTxAggregator {
&self,
storage: &mut Connection<'_, Core>,
aggregated_op: &AggregatedOperation,
timelock_contract_address: Address,
is_gateway: bool,
) -> Result<EthTx, EthSenderError> {
let mut transaction = storage.start_transaction().await.unwrap();
Expand Down Expand Up @@ -653,7 +790,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,
Expand Down
3 changes: 3 additions & 0 deletions core/node/eth_sender/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -522,6 +524,7 @@ impl EthSenderTester {
.save_eth_tx(
&mut self.conn.connection().await.unwrap(),
&aggregated_operation,
Address::random(),
self.is_l2,
)
.await
Expand Down
Loading
Loading