Skip to content

Commit caee55f

Browse files
authored
fix(consensus): preventing config update reverts (#3148)
Random failures of consensus_global_config RPC may cause config reverts, until the consensus_genesis() RPC is fully deprecated. Although the problems of this sort are transient, this pr adds extra protection from this kind of situations to prevent unnecessary binary restarts.
1 parent b29be7d commit caee55f

File tree

2 files changed

+46
-25
lines changed
  • core

2 files changed

+46
-25
lines changed

core/lib/dal/src/consensus_dal/mod.rs

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,48 @@ use crate::{Core, CoreDal};
1616
#[cfg(test)]
1717
mod tests;
1818

19+
/// Hash of the batch.
1920
pub fn batch_hash(info: &StoredBatchInfo) -> attester::BatchHash {
2021
attester::BatchHash(Keccak256::from_bytes(info.hash().0))
2122
}
2223

24+
/// Verifies that the transition from `old` to `new` is admissible.
25+
pub fn verify_config_transition(old: &GlobalConfig, new: &GlobalConfig) -> anyhow::Result<()> {
26+
anyhow::ensure!(
27+
old.genesis.chain_id == new.genesis.chain_id,
28+
"changing chain_id is not allowed: old = {:?}, new = {:?}",
29+
old.genesis.chain_id,
30+
new.genesis.chain_id,
31+
);
32+
// Note that it may happen that the fork number didn't change,
33+
// in case the binary was updated to support more fields in genesis struct.
34+
// In such a case, the old binary was not able to connect to the consensus network,
35+
// because of the genesis hash mismatch.
36+
// TODO: Perhaps it would be better to deny unknown fields in the genesis instead.
37+
// It would require embedding the genesis either as a json string or protobuf bytes within
38+
// the global config, so that the global config can be parsed with
39+
// `deny_unknown_fields:false` while genesis would be parsed with
40+
// `deny_unknown_fields:true`.
41+
anyhow::ensure!(
42+
old.genesis.fork_number <= new.genesis.fork_number,
43+
"transition to a past fork is not allowed: old = {:?}, new = {:?}",
44+
old.genesis.fork_number,
45+
new.genesis.fork_number,
46+
);
47+
new.genesis.verify().context("genesis.verify()")?;
48+
// This is a temporary hack until the `consensus_genesis()` RPC is disabled.
49+
if new
50+
== (&GlobalConfig {
51+
genesis: old.genesis.clone(),
52+
registry_address: None,
53+
seed_peers: [].into(),
54+
})
55+
{
56+
anyhow::bail!("new config is equal to truncated old config, which means that it was sourced from the wrong endpoint");
57+
}
58+
Ok(())
59+
}
60+
2361
/// Storage access methods for `zksync_core::consensus` module.
2462
#[derive(Debug)]
2563
pub struct ConsensusDal<'a, 'c> {
@@ -94,6 +132,8 @@ impl ConsensusDal<'_, '_> {
94132
if got == want {
95133
return Ok(());
96134
}
135+
verify_config_transition(got, want)?;
136+
97137
// If genesis didn't change, just update the config.
98138
if got.genesis == want.genesis {
99139
let s = zksync_protobuf::serde::Serialize;
@@ -112,30 +152,6 @@ impl ConsensusDal<'_, '_> {
112152
txn.commit().await?;
113153
return Ok(());
114154
}
115-
116-
// Verify the genesis change.
117-
anyhow::ensure!(
118-
got.genesis.chain_id == want.genesis.chain_id,
119-
"changing chain_id is not allowed: old = {:?}, new = {:?}",
120-
got.genesis.chain_id,
121-
want.genesis.chain_id,
122-
);
123-
// Note that it may happen that the fork number didn't change,
124-
// in case the binary was updated to support more fields in genesis struct.
125-
// In such a case, the old binary was not able to connect to the consensus network,
126-
// because of the genesis hash mismatch.
127-
// TODO: Perhaps it would be better to deny unknown fields in the genesis instead.
128-
// It would require embedding the genesis either as a json string or protobuf bytes within
129-
// the global config, so that the global config can be parsed with
130-
// `deny_unknown_fields:false` while genesis would be parsed with
131-
// `deny_unknown_fields:true`.
132-
anyhow::ensure!(
133-
got.genesis.fork_number <= want.genesis.fork_number,
134-
"transition to a past fork is not allowed: old = {:?}, new = {:?}",
135-
got.genesis.fork_number,
136-
want.genesis.fork_number,
137-
);
138-
want.genesis.verify().context("genesis.verify()")?;
139155
}
140156

141157
// Reset the consensus state.

core/node/consensus/src/en.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ impl EN {
100100
let old = old;
101101
loop {
102102
if let Ok(new) = self.fetch_global_config(ctx).await {
103-
if new != old {
103+
// We verify the transition here to work around the situation
104+
// where `consenus_global_config()` RPC fails randomly and fallback
105+
// to `consensus_genesis()` RPC activates.
106+
if new != old
107+
&& consensus_dal::verify_config_transition(&old, &new).is_ok()
108+
{
104109
return Err(anyhow::format_err!(
105110
"global config changed: old {old:?}, new {new:?}"
106111
)

0 commit comments

Comments
 (0)