diff --git a/core/lib/config/src/configs/contracts.rs b/core/lib/config/src/configs/contracts.rs
index ddc26f7bf35c..4c3230f7c265 100644
--- a/core/lib/config/src/configs/contracts.rs
+++ b/core/lib/config/src/configs/contracts.rs
@@ -34,6 +34,7 @@ pub struct ContractsConfig {
pub fri_recursion_scheduler_level_vk_hash: H256,
pub fri_recursion_node_level_vk_hash: H256,
pub fri_recursion_leaf_level_vk_hash: H256,
+ pub governance_addr: Option
,
}
impl ContractsConfig {
@@ -93,6 +94,7 @@ mod tests {
fri_recursion_leaf_level_vk_hash: hash(
"0x72167c43a46cf38875b267d67716edc4563861364a3c03ab7aee73498421e828",
),
+ governance_addr: None,
}
}
diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs
index 1fb12da55312..cdc5b8b0e6aa 100644
--- a/core/lib/contracts/src/lib.rs
+++ b/core/lib/contracts/src/lib.rs
@@ -24,6 +24,8 @@ pub enum ContractLanguage {
Yul,
}
+const GOVERNANCE_CONTRACT_FILE: &str =
+ "contracts/ethereum/artifacts/cache/solpp-generated-contracts/governance/IGovernance.sol/IGovernance.json";
const ZKSYNC_CONTRACT_FILE: &str =
"contracts/ethereum/artifacts/cache/solpp-generated-contracts/zksync/interfaces/IZkSync.sol/IZkSync.json";
const MULTICALL3_CONTRACT_FILE: &str =
@@ -50,9 +52,19 @@ fn read_file_to_json_value(path: impl AsRef) -> serde_json::Value {
.unwrap_or_else(|e| panic!("Failed to parse file {:?}: {}", path, e))
}
+pub fn load_contract_if_present + std::fmt::Debug>(path: P) -> Option {
+ let zksync_home = std::env::var("ZKSYNC_HOME").unwrap_or_else(|_| ".".into());
+ let path = Path::new(&zksync_home).join(path);
+ path.exists().then(|| {
+ serde_json::from_value(read_file_to_json_value(&path)["abi"].take())
+ .unwrap_or_else(|e| panic!("Failed to parse contract abi from file {:?}: {}", path, e))
+ })
+}
+
pub fn load_contract + std::fmt::Debug>(path: P) -> Contract {
- serde_json::from_value(read_file_to_json_value(&path)["abi"].take())
- .unwrap_or_else(|e| panic!("Failed to parse contract abi from file {:?}: {}", path, e))
+ load_contract_if_present(&path).unwrap_or_else(|| {
+ panic!("Failed to load contract from {:?}", path);
+ })
}
pub fn load_sys_contract(contract_name: &str) -> Contract {
@@ -69,6 +81,10 @@ pub fn read_contract_abi(path: impl AsRef) -> String {
.to_string()
}
+pub fn governance_contract() -> Option {
+ load_contract_if_present(GOVERNANCE_CONTRACT_FILE)
+}
+
pub fn zksync_contract() -> Contract {
load_contract(ZKSYNC_CONTRACT_FILE)
}
diff --git a/core/lib/types/src/protocol_version.rs b/core/lib/types/src/protocol_version.rs
index afc4868785a9..8faa998636c2 100644
--- a/core/lib/types/src/protocol_version.rs
+++ b/core/lib/types/src/protocol_version.rs
@@ -156,6 +156,32 @@ pub struct L1VerifierConfig {
pub recursion_scheduler_level_vk_hash: H256,
}
+/// Represents a call that was made during governance operation.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Call {
+ /// The address to which the call will be made.
+ pub target: Address,
+ /// The amount of Ether (in wei) to be sent along with the call.
+ pub value: U256,
+ /// The calldata to be executed on the `target` address.
+ pub data: Vec,
+ /// Hash of the corresponding Ethereum transaction.
+ pub eth_hash: H256,
+ /// Block in which Ethereum transaction was included.
+ pub eth_block: u64,
+}
+
+/// Defines the structure of an operation that Governance contract executed.
+#[derive(Debug, Clone, Default)]
+pub struct GovernanceOperation {
+ /// An array of `Call` structs, each representing a call to be made during the operation.
+ pub calls: Vec,
+ /// The hash of the predecessor operation, that should be executed before this operation.
+ pub predecessor: H256,
+ /// The value used for creating unique operation hashes.
+ pub salt: H256,
+}
+
/// Protocol upgrade proposal from L1.
/// Most of the fields are optional meaning if value is none
/// then this field is not changed within an upgrade.
@@ -408,6 +434,106 @@ impl TryFrom for ProtocolUpgrade {
}
}
+impl TryFrom for GovernanceOperation {
+ type Error = crate::ethabi::Error;
+
+ fn try_from(event: Log) -> Result {
+ let call_param_type = ParamType::Tuple(vec![
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Bytes,
+ ]);
+
+ let operation_param_type = ParamType::Tuple(vec![
+ ParamType::Array(Box::new(call_param_type)),
+ ParamType::FixedBytes(32),
+ ParamType::FixedBytes(32),
+ ]);
+ // Decode data.
+ let mut decoded = decode(&[ParamType::Uint(256), operation_param_type], &event.data.0)?;
+ // Extract `GovernanceOperation` data.
+ let mut decoded_governance_operation = decoded.remove(1).into_tuple().unwrap();
+
+ let eth_hash = event
+ .transaction_hash
+ .expect("Event transaction hash is missing");
+ let eth_block = event
+ .block_number
+ .expect("Event block number is missing")
+ .as_u64();
+
+ let calls = decoded_governance_operation.remove(0).into_array().unwrap();
+ let predecessor = H256::from_slice(
+ &decoded_governance_operation
+ .remove(0)
+ .into_fixed_bytes()
+ .unwrap(),
+ );
+ let salt = H256::from_slice(
+ &decoded_governance_operation
+ .remove(0)
+ .into_fixed_bytes()
+ .unwrap(),
+ );
+
+ let calls = calls
+ .into_iter()
+ .map(|call| {
+ let mut decoded_governance_operation = call.into_tuple().unwrap();
+
+ Call {
+ target: decoded_governance_operation
+ .remove(0)
+ .into_address()
+ .unwrap(),
+ value: decoded_governance_operation.remove(0).into_uint().unwrap(),
+ data: decoded_governance_operation.remove(0).into_bytes().unwrap(),
+ eth_hash,
+ eth_block,
+ }
+ })
+ .collect();
+
+ Ok(Self {
+ calls,
+ predecessor,
+ salt,
+ })
+ }
+}
+
+impl TryFrom for ProtocolUpgrade {
+ type Error = crate::ethabi::Error;
+
+ fn try_from(call: Call) -> Result {
+ // Reuses `ProtocolUpgrade::try_from`.
+ // `ProtocolUpgrade::try_from` only uses 3 log fields: `data`, `block_number`, `transaction_hash`. Others can be filled with dummy values.
+ // We build data as `call.data` without first 4 bytes which are for selector
+ // and append it with `bytes32(0)` for compatibility with old event data.
+ let data = call
+ .data
+ .into_iter()
+ .skip(4)
+ .chain(encode(&[Token::FixedBytes(H256::zero().0.to_vec())]))
+ .collect::>()
+ .into();
+ let log = Log {
+ address: Default::default(),
+ topics: Default::default(),
+ data,
+ block_hash: Default::default(),
+ block_number: Some(call.eth_block.into()),
+ transaction_hash: Some(call.eth_hash),
+ transaction_index: Default::default(),
+ log_index: Default::default(),
+ transaction_log_index: Default::default(),
+ log_type: Default::default(),
+ removed: Default::default(),
+ };
+ ProtocolUpgrade::try_from(log)
+ }
+}
+
#[derive(Debug, Clone, Default)]
pub struct ProtocolVersion {
/// Protocol version ID
@@ -559,3 +685,46 @@ impl From for VmVersion {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn governance_operation_from_log() {
+ let call_token = Token::Tuple(vec![
+ Token::Address(Address::random()),
+ Token::Uint(U256::zero()),
+ Token::Bytes(vec![1, 2, 3]),
+ ]);
+ let operation_token = Token::Tuple(vec![
+ Token::Array(vec![call_token]),
+ Token::FixedBytes(H256::random().0.to_vec()),
+ Token::FixedBytes(H256::random().0.to_vec()),
+ ]);
+ let event_data = encode(&[Token::Uint(U256::zero()), operation_token]);
+
+ let correct_log = Log {
+ address: Default::default(),
+ topics: Default::default(),
+ data: event_data.into(),
+ block_hash: Default::default(),
+ block_number: Some(1u64.into()),
+ transaction_hash: Some(H256::random()),
+ transaction_index: Default::default(),
+ log_index: Default::default(),
+ transaction_log_index: Default::default(),
+ log_type: Default::default(),
+ removed: Default::default(),
+ };
+ let decoded_op: GovernanceOperation = correct_log.clone().try_into().unwrap();
+ assert_eq!(decoded_op.calls.len(), 1);
+
+ let mut incorrect_log = correct_log;
+ incorrect_log
+ .data
+ .0
+ .truncate(incorrect_log.data.0.len() - 32);
+ assert!(TryInto::::try_into(incorrect_log).is_err());
+ }
+}
diff --git a/core/lib/zksync_core/src/eth_watch/client.rs b/core/lib/zksync_core/src/eth_watch/client.rs
index f8154f14dbc7..af38ac79ae7d 100644
--- a/core/lib/zksync_core/src/eth_watch/client.rs
+++ b/core/lib/zksync_core/src/eth_watch/client.rs
@@ -48,6 +48,9 @@ pub struct EthHttpQueryClient {
client: E,
topics: Vec,
zksync_contract_addr: Address,
+ /// Address of the `Governance` contract. It's optional because it is present only for post-boojum chains.
+ /// If address is some then client will listen to events coming from it.
+ governance_address: Option,
verifier_contract_abi: Contract,
confirmations_for_eth_event: Option,
}
@@ -56,13 +59,19 @@ impl EthHttpQueryClient {
pub fn new(
client: E,
zksync_contract_addr: Address,
+ governance_address: Option,
confirmations_for_eth_event: Option,
) -> Self {
- tracing::debug!("New eth client, contract addr: {:x}", zksync_contract_addr);
+ tracing::debug!(
+ "New eth client, zkSync addr: {:x}, governance addr: {:?}",
+ zksync_contract_addr,
+ governance_address
+ );
Self {
client,
topics: Vec::new(),
zksync_contract_addr,
+ governance_address,
verifier_contract_abi: verifier_contract(),
confirmations_for_eth_event,
}
@@ -75,7 +84,13 @@ impl EthHttpQueryClient {
topics: Vec,
) -> Result, Error> {
let filter = FilterBuilder::default()
- .address(vec![self.zksync_contract_addr])
+ .address(
+ [Some(self.zksync_contract_addr), self.governance_address]
+ .iter()
+ .flatten()
+ .copied()
+ .collect(),
+ )
.from_block(from)
.to_block(to)
.topics(Some(topics), None, None, None)
@@ -88,10 +103,14 @@ impl EthHttpQueryClient {
#[async_trait::async_trait]
impl EthClient for EthHttpQueryClient {
async fn scheduler_vk_hash(&self, verifier_address: Address) -> Result {
- let vk_token: Token = self
+ // This is here for backward compatibility with the old verifier:
+ // Legacy verifier returns the full verification key;
+ // New verifier returns the hash of the verification key.
+
+ let vk_hash = self
.client
.call_contract_function(
- "get_verification_key",
+ "verificationKeyHash",
(),
None,
Default::default(),
@@ -99,8 +118,25 @@ impl EthClient for EthHttpQueryClient Self {
+ Self {
+ diamond_proxy_address,
+ last_seen_version_id,
+ upgrade_proposal_signature: governance_contract
+ .event("TransparentOperationScheduled")
+ .expect("TransparentOperationScheduled event is missing in abi")
+ .signature(),
+ }
+ }
+}
+
+#[async_trait::async_trait]
+impl EventProcessor for GovernanceUpgradesEventProcessor {
+ async fn process_events(
+ &mut self,
+ storage: &mut StorageProcessor<'_>,
+ client: &W,
+ events: Vec,
+ ) -> Result<(), Error> {
+ let mut upgrades = Vec::new();
+ for event in events
+ .into_iter()
+ .filter(|event| event.topics[0] == self.upgrade_proposal_signature)
+ {
+ let governance_operation = GovernanceOperation::try_from(event)
+ .map_err(|err| Error::LogParse(format!("{:?}", err)))?;
+ // Some calls can target other contracts than Diamond proxy, skip them.
+ for call in governance_operation
+ .calls
+ .into_iter()
+ .filter(|call| call.target == self.diamond_proxy_address)
+ {
+ let upgrade = ProtocolUpgrade::try_from(call)
+ .map_err(|err| Error::LogParse(format!("{:?}", err)))?;
+ // Scheduler VK is not present in proposal event. It is hardcoded in verifier contract.
+ let scheduler_vk_hash = if let Some(address) = upgrade.verifier_address {
+ Some(client.scheduler_vk_hash(address).await?)
+ } else {
+ None
+ };
+ upgrades.push((upgrade, scheduler_vk_hash));
+ }
+ }
+
+ if upgrades.is_empty() {
+ return Ok(());
+ }
+
+ let ids_str: Vec<_> = upgrades
+ .iter()
+ .map(|(u, _)| format!("{}", u.id as u16))
+ .collect();
+ tracing::debug!("Received upgrades with ids: {}", ids_str.join(", "));
+
+ let new_upgrades: Vec<_> = upgrades
+ .into_iter()
+ .skip_while(|(v, _)| v.id as u16 <= self.last_seen_version_id as u16)
+ .collect();
+ if new_upgrades.is_empty() {
+ return Ok(());
+ }
+
+ let last_id = new_upgrades.last().unwrap().0.id;
+ let stage_start = Instant::now();
+ for (upgrade, scheduler_vk_hash) in new_upgrades {
+ let previous_version = storage
+ .protocol_versions_dal()
+ .load_previous_version(upgrade.id)
+ .await
+ .unwrap_or_else(|| {
+ panic!(
+ "Expected some version preceding {:?} be present in DB",
+ upgrade.id
+ )
+ });
+ let new_version = previous_version.apply_upgrade(upgrade, scheduler_vk_hash);
+ storage
+ .protocol_versions_dal()
+ .save_protocol_version_with_tx(new_version)
+ .await;
+ }
+ metrics::histogram!("eth_watcher.poll_eth_node", stage_start.elapsed(), "stage" => "persist_upgrades");
+
+ self.last_seen_version_id = last_id;
+
+ Ok(())
+ }
+
+ fn relevant_topic(&self) -> H256 {
+ self.upgrade_proposal_signature
+ }
+}
diff --git a/core/lib/zksync_core/src/eth_watch/event_processors/mod.rs b/core/lib/zksync_core/src/eth_watch/event_processors/mod.rs
index 70e1db9a3f14..84ea1eeb04cf 100644
--- a/core/lib/zksync_core/src/eth_watch/event_processors/mod.rs
+++ b/core/lib/zksync_core/src/eth_watch/event_processors/mod.rs
@@ -2,6 +2,7 @@ use crate::eth_watch::client::{Error, EthClient};
use zksync_dal::StorageProcessor;
use zksync_types::{web3::types::Log, H256};
+pub mod governance_upgrades;
pub mod priority_ops;
pub mod upgrades;
diff --git a/core/lib/zksync_core/src/eth_watch/mod.rs b/core/lib/zksync_core/src/eth_watch/mod.rs
index 1cd2eb95947d..150b6556ab48 100644
--- a/core/lib/zksync_core/src/eth_watch/mod.rs
+++ b/core/lib/zksync_core/src/eth_watch/mod.rs
@@ -14,7 +14,8 @@ use zksync_config::ETHWatchConfig;
use zksync_dal::{ConnectionPool, StorageProcessor};
use zksync_eth_client::EthInterface;
use zksync_types::{
- web3::types::BlockNumber as Web3BlockNumber, Address, PriorityOpId, ProtocolVersionId,
+ ethabi::Contract, web3::types::BlockNumber as Web3BlockNumber, Address, PriorityOpId,
+ ProtocolVersionId,
};
mod client;
@@ -26,6 +27,7 @@ mod tests;
use self::{
client::{Error, EthClient, EthHttpQueryClient, RETRY_LIMIT},
event_processors::{
+ governance_upgrades::GovernanceUpgradesEventProcessor,
priority_ops::PriorityOpsEventProcessor, upgrades::UpgradesEventProcessor, EventProcessor,
},
metrics::{PollStage, METRICS},
@@ -48,7 +50,13 @@ pub struct EthWatch {
}
impl EthWatch {
- pub async fn new(mut client: W, pool: &ConnectionPool, poll_interval: Duration) -> Self {
+ pub async fn new(
+ diamond_proxy_address: Address,
+ governance_contract: Option,
+ mut client: W,
+ pool: &ConnectionPool,
+ poll_interval: Duration,
+ ) -> Self {
let mut storage = pool.access_storage_tagged("eth_watch").await.unwrap();
let state = Self::initialize_state(&client, &mut storage).await;
@@ -58,11 +66,20 @@ impl EthWatch {
let priority_ops_processor =
PriorityOpsEventProcessor::new(state.next_expected_priority_id);
let upgrades_processor = UpgradesEventProcessor::new(state.last_seen_version_id);
- let event_processors: Vec>> = vec![
+ let mut event_processors: Vec>> = vec![
Box::new(priority_ops_processor),
Box::new(upgrades_processor),
];
+ if let Some(governance_contract) = governance_contract {
+ let governance_upgrades_processor = GovernanceUpgradesEventProcessor::new(
+ diamond_proxy_address,
+ state.last_seen_version_id,
+ &governance_contract,
+ );
+ event_processors.push(Box::new(governance_upgrades_processor))
+ }
+
let topics = event_processors
.iter()
.map(|p| p.relevant_topic())
@@ -174,16 +191,25 @@ pub async fn start_eth_watch(
pool: ConnectionPool,
eth_gateway: E,
diamond_proxy_addr: Address,
+ governance: Option<(Contract, Address)>,
stop_receiver: watch::Receiver,
) -> anyhow::Result>> {
let eth_watch = ETHWatchConfig::from_env().context("ETHWatchConfig::from_env()")?;
let eth_client = EthHttpQueryClient::new(
eth_gateway,
diamond_proxy_addr,
+ governance.as_ref().map(|(_, address)| *address),
eth_watch.confirmations_for_eth_event,
);
- let mut eth_watch = EthWatch::new(eth_client, &pool, eth_watch.poll_interval()).await;
+ let mut eth_watch = EthWatch::new(
+ diamond_proxy_addr,
+ governance.map(|(contract, _)| contract),
+ eth_client,
+ &pool,
+ eth_watch.poll_interval(),
+ )
+ .await;
Ok(tokio::spawn(async move {
eth_watch.run(pool, stop_receiver).await
diff --git a/core/lib/zksync_core/src/eth_watch/tests.rs b/core/lib/zksync_core/src/eth_watch/tests.rs
index d9448efbfabe..01fb83b98c01 100644
--- a/core/lib/zksync_core/src/eth_watch/tests.rs
+++ b/core/lib/zksync_core/src/eth_watch/tests.rs
@@ -10,7 +10,7 @@ use zksync_dal::{ConnectionPool, StorageProcessor};
use zksync_types::protocol_version::{ProtocolUpgradeTx, ProtocolUpgradeTxCommonData};
use zksync_types::web3::types::{Address, BlockNumber};
use zksync_types::{
- ethabi::{encode, Hash, Token},
+ ethabi::{encode, Contract, Hash, Token},
l1::{L1Tx, OpProcessingType, PriorityQueueType},
web3::types::Log,
Execute, L1TxCommonData, PriorityOpId, ProtocolUpgrade, ProtocolVersion, ProtocolVersionId,
@@ -22,7 +22,8 @@ use crate::eth_watch::{client::EthClient, EthWatch};
struct FakeEthClientData {
transactions: HashMap>,
- upgrades: HashMap>,
+ diamond_upgrades: HashMap>,
+ governance_upgrades: HashMap>,
last_finalized_block_number: u64,
}
@@ -30,7 +31,8 @@ impl FakeEthClientData {
fn new() -> Self {
Self {
transactions: Default::default(),
- upgrades: Default::default(),
+ diamond_upgrades: Default::default(),
+ governance_upgrades: Default::default(),
last_finalized_block_number: 0,
}
}
@@ -45,12 +47,21 @@ impl FakeEthClientData {
}
}
- fn add_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
+ fn add_diamond_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
for (upgrade, eth_block) in upgrades {
- self.upgrades
+ self.diamond_upgrades
.entry(*eth_block)
.or_default()
- .push(upgrade_into_log(upgrade.clone(), *eth_block));
+ .push(upgrade_into_diamond_proxy_log(upgrade.clone(), *eth_block));
+ }
+ }
+
+ fn add_governance_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
+ for (upgrade, eth_block) in upgrades {
+ self.governance_upgrades
+ .entry(*eth_block)
+ .or_default()
+ .push(upgrade_into_governor_log(upgrade.clone(), *eth_block));
}
}
@@ -75,8 +86,12 @@ impl FakeEthClient {
self.inner.write().await.add_transactions(transactions);
}
- async fn add_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
- self.inner.write().await.add_upgrades(upgrades);
+ async fn add_diamond_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
+ self.inner.write().await.add_diamond_upgrades(upgrades);
+ }
+
+ async fn add_governance_upgrades(&mut self, upgrades: &[(ProtocolUpgrade, u64)]) {
+ self.inner.write().await.add_governance_upgrades(upgrades);
}
async fn set_last_finalized_block_number(&mut self, number: u64) {
@@ -113,7 +128,10 @@ impl EthClient for FakeEthClient {
if let Some(ops) = self.inner.read().await.transactions.get(&number) {
logs.extend_from_slice(ops);
}
- if let Some(ops) = self.inner.read().await.upgrades.get(&number) {
+ if let Some(ops) = self.inner.read().await.diamond_upgrades.get(&number) {
+ logs.extend_from_slice(ops);
+ }
+ if let Some(ops) = self.inner.read().await.governance_upgrades.get(&number) {
logs.extend_from_slice(ops);
}
}
@@ -190,6 +208,8 @@ async fn test_normal_operation_l1_txs(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -235,6 +255,8 @@ async fn test_normal_operation_upgrades(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -243,7 +265,7 @@ async fn test_normal_operation_upgrades(connection_pool: ConnectionPool) {
let mut storage = connection_pool.access_test_storage().await;
client
- .add_upgrades(&[
+ .add_diamond_upgrades(&[
(
ProtocolUpgrade {
id: ProtocolVersionId::latest(),
@@ -293,6 +315,8 @@ async fn test_gap_in_upgrades(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -301,7 +325,7 @@ async fn test_gap_in_upgrades(connection_pool: ConnectionPool) {
let mut storage = connection_pool.access_test_storage().await;
client
- .add_upgrades(&[(
+ .add_diamond_upgrades(&[(
ProtocolUpgrade {
id: ProtocolVersionId::next(),
tx: None,
@@ -323,6 +347,66 @@ async fn test_gap_in_upgrades(connection_pool: ConnectionPool) {
assert_eq!(db_ids[1], next_version);
}
+#[db_test]
+async fn test_normal_operation_governance_upgrades(connection_pool: ConnectionPool) {
+ setup_db(&connection_pool).await;
+
+ let mut client = FakeEthClient::new();
+ let mut watcher = EthWatch::new(
+ Address::default(),
+ Some(governance_contract()),
+ client.clone(),
+ &connection_pool,
+ std::time::Duration::from_nanos(1),
+ )
+ .await;
+
+ let mut storage = connection_pool.access_test_storage().await;
+ client
+ .add_governance_upgrades(&[
+ (
+ ProtocolUpgrade {
+ id: ProtocolVersionId::latest(),
+ tx: None,
+ ..Default::default()
+ },
+ 10,
+ ),
+ (
+ ProtocolUpgrade {
+ id: ProtocolVersionId::next(),
+ tx: Some(build_upgrade_tx(ProtocolVersionId::next(), 18)),
+ ..Default::default()
+ },
+ 18,
+ ),
+ ])
+ .await;
+ client.set_last_finalized_block_number(15).await;
+ // second upgrade will not be processed, as it has less than 5 confirmations
+ watcher.loop_iteration(&mut storage).await.unwrap();
+
+ let db_ids = storage.protocol_versions_dal().all_version_ids().await;
+ // there should be genesis version and just added version
+ assert_eq!(db_ids.len(), 2);
+ assert_eq!(db_ids[1], ProtocolVersionId::latest());
+
+ client.set_last_finalized_block_number(20).await;
+ // now the second upgrade will be processed
+ watcher.loop_iteration(&mut storage).await.unwrap();
+ let db_ids = storage.protocol_versions_dal().all_version_ids().await;
+ assert_eq!(db_ids.len(), 3);
+ assert_eq!(db_ids[2], ProtocolVersionId::next());
+
+ // check that tx was saved with the last upgrade
+ let tx = storage
+ .protocol_versions_dal()
+ .get_protocol_upgrade_tx(ProtocolVersionId::next())
+ .await
+ .unwrap();
+ assert_eq!(tx.common_data.upgrade_id, ProtocolVersionId::next());
+}
+
#[db_test]
#[should_panic]
async fn test_gap_in_single_batch(connection_pool: ConnectionPool) {
@@ -330,6 +414,8 @@ async fn test_gap_in_single_batch(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -357,6 +443,8 @@ async fn test_gap_between_batches(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -389,6 +477,8 @@ async fn test_overlapping_batches(connection_pool: ConnectionPool) {
let mut client = FakeEthClient::new();
let mut watcher = EthWatch::new(
+ Address::default(),
+ None,
client.clone(),
&connection_pool,
std::time::Duration::from_nanos(1),
@@ -490,7 +580,72 @@ fn tx_into_log(tx: L1Tx) -> Log {
}
}
-fn upgrade_into_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log {
+fn upgrade_into_diamond_proxy_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log {
+ let diamond_cut = upgrade_into_diamond_cut(upgrade);
+ let data = encode(&[diamond_cut, Token::FixedBytes(vec![0u8; 32])]);
+ Log {
+ address: Address::repeat_byte(0x1),
+ topics: vec![zksync_contract()
+ .event("ProposeTransparentUpgrade")
+ .expect("ProposeTransparentUpgrade event is missing in abi")
+ .signature()],
+ data: data.into(),
+ block_hash: Some(H256::repeat_byte(0x11)),
+ block_number: Some(eth_block.into()),
+ transaction_hash: Some(H256::random()),
+ transaction_index: Some(0u64.into()),
+ log_index: Some(0u64.into()),
+ transaction_log_index: Some(0u64.into()),
+ log_type: None,
+ removed: None,
+ }
+}
+
+fn upgrade_into_governor_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log {
+ let diamond_cut = upgrade_into_diamond_cut(upgrade);
+ let execute_upgrade_selector = zksync_contract()
+ .function("executeUpgrade")
+ .unwrap()
+ .short_signature();
+ let diamond_upgrade_calldata = execute_upgrade_selector
+ .iter()
+ .copied()
+ .chain(encode(&[diamond_cut]))
+ .collect();
+ let governance_call = Token::Tuple(vec![
+ Token::Address(Default::default()),
+ Token::Uint(U256::default()),
+ Token::Bytes(diamond_upgrade_calldata),
+ ]);
+ let governance_operation = Token::Tuple(vec![
+ Token::Array(vec![governance_call]),
+ Token::FixedBytes(vec![0u8; 32]),
+ Token::FixedBytes(vec![0u8; 32]),
+ ]);
+ let final_data = encode(&[Token::FixedBytes(vec![0u8; 32]), governance_operation]);
+
+ Log {
+ address: Address::repeat_byte(0x1),
+ topics: vec![
+ governance_contract()
+ .event("TransparentOperationScheduled")
+ .expect("TransparentOperationScheduled event is missing in abi")
+ .signature(),
+ Default::default(),
+ ],
+ data: final_data.into(),
+ block_hash: Some(H256::repeat_byte(0x11)),
+ block_number: Some(eth_block.into()),
+ transaction_hash: Some(H256::random()),
+ transaction_index: Some(0u64.into()),
+ log_index: Some(0u64.into()),
+ transaction_log_index: Some(0u64.into()),
+ log_type: None,
+ removed: None,
+ }
+}
+
+fn upgrade_into_diamond_cut(upgrade: ProtocolUpgrade) -> Token {
let tx_data_token = if let Some(tx) = upgrade.tx {
Token::Tuple(vec![
Token::Uint(0xfe.into()),
@@ -592,7 +747,7 @@ fn upgrade_into_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log {
Token::Address(Default::default()),
]);
- let final_token = Token::Tuple(vec![
+ Token::Tuple(vec![
Token::Array(vec![]),
Token::Address(Default::default()),
Token::Bytes(
@@ -601,25 +756,7 @@ fn upgrade_into_log(upgrade: ProtocolUpgrade, eth_block: u64) -> Log {
.chain(encode(&[upgrade_token]))
.collect(),
),
- ]);
-
- let data = encode(&[final_token, Token::FixedBytes(vec![0u8; 32])]);
- Log {
- address: Address::repeat_byte(0x1),
- topics: vec![zksync_contract()
- .event("ProposeTransparentUpgrade")
- .expect("ProposeTransparentUpgrade event is missing in abi")
- .signature()],
- data: data.into(),
- block_hash: Some(H256::repeat_byte(0x11)),
- block_number: Some(eth_block.into()),
- transaction_hash: Some(H256::random()),
- transaction_index: Some(0u64.into()),
- log_index: Some(0u64.into()),
- transaction_log_index: Some(0u64.into()),
- log_type: None,
- removed: None,
- }
+ ])
}
async fn setup_db(connection_pool: &ConnectionPool) {
@@ -633,3 +770,68 @@ async fn setup_db(connection_pool: &ConnectionPool) {
})
.await;
}
+
+fn governance_contract() -> Contract {
+ let json = r#"[
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "bytes32",
+ "name": "_id",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "delay",
+ "type": "uint256"
+ },
+ {
+ "components": [
+ {
+ "components": [
+ {
+ "internalType": "address",
+ "name": "target",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "internalType": "struct IGovernance.Call[]",
+ "name": "calls",
+ "type": "tuple[]"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "predecessor",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "indexed": false,
+ "internalType": "struct IGovernance.Operation",
+ "name": "_operation",
+ "type": "tuple"
+ }
+ ],
+ "name": "TransparentOperationScheduled",
+ "type": "event"
+ }
+ ]"#;
+ serde_json::from_str(json).unwrap()
+}
diff --git a/core/lib/zksync_core/src/lib.rs b/core/lib/zksync_core/src/lib.rs
index f64eaffefc24..7fd7db9443a7 100644
--- a/core/lib/zksync_core/src/lib.rs
+++ b/core/lib/zksync_core/src/lib.rs
@@ -27,7 +27,7 @@ use zksync_config::{
ApiConfig, ContractsConfig, DBConfig, ETHClientConfig, ETHSenderConfig, FetcherConfig,
ProverConfigs,
};
-use zksync_contracts::BaseSystemContracts;
+use zksync_contracts::{governance_contract, BaseSystemContracts};
use zksync_dal::{
connection::DbVariant, healthcheck::ConnectionPoolHealthCheck, ConnectionPool, StorageProcessor,
};
@@ -496,11 +496,17 @@ pub async fn initialize_components(
.build()
.await
.context("failed to build eth_watch_pool")?;
+ let governance = contracts_config.governance_addr.map(|addr| {
+ let contract = governance_contract()
+ .expect("Governance contract must be present if governance_addr is set in config");
+ (contract, addr)
+ });
task_futures.push(
start_eth_watch(
eth_watch_pool,
query_client.clone(),
main_zksync_contract_address,
+ governance,
stop_receiver.clone(),
)
.await