Skip to content

Commit 3cffdb2

Browse files
encodyischasny
andauthored
feat: add support for custom genesis state (#3259)
This PR introduces the `custom_genesis_export` tool, designed to export zkSync PostgreSQL database state in a format suitable for initializing a custom genesis state for a new chain. - Exports `initial_writes`, `storage_logs`, and `factory_deps` (excluding system context entries) into a binary file. - Updates the `genesis.yaml` file with: - `genesis_root_hash`, `rollup_last_leaf_index`, and `genesis_commitment` matching the exported data. - Adds `custom_genesis_state_path` pointing to the export file, which is recognised by the server during genesis. - Adds support for initialising from a custom genesis state to the server. --------- Co-authored-by: Ivan Schasny <[email protected]> Co-authored-by: Ivan Schasny <[email protected]>
1 parent ee117a8 commit 3cffdb2

File tree

22 files changed

+664
-77
lines changed

22 files changed

+664
-77
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ members = [
33
# Binaries
44
"core/bin/block_reverter",
55
"core/bin/contract-verifier",
6+
"core/bin/custom_genesis_export",
67
"core/bin/external_node",
78
"core/bin/merkle_tree_consistency_checker",
89
"core/bin/snapshots_creator",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.bin
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "custom_genesis_export"
3+
version.workspace = true
4+
edition.workspace = true
5+
authors.workspace = true
6+
homepage.workspace = true
7+
repository.workspace = true
8+
license.workspace = true
9+
keywords.workspace = true
10+
categories.workspace = true
11+
12+
[dependencies]
13+
clap = { workspace = true, features = ["derive"] }
14+
futures.workspace = true
15+
sqlx = { workspace = true, features = [
16+
"runtime-tokio",
17+
"tls-native-tls",
18+
"macros",
19+
"postgres",
20+
] }
21+
tokio = { workspace = true, features = ["full"] }
22+
23+
zksync_types.workspace = true
24+
zksync_node_genesis.workspace = true
25+
zksync_contracts.workspace = true
26+
zksync_core_leftovers.workspace = true
27+
zksync_protobuf_config.workspace = true
28+
zksync_dal.workspace = true
29+
anyhow.workspace = true
30+
bincode.workspace = true
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Custom Genesis Export
2+
3+
The `custom_genesis_export` tool allows exporting a state from a zkSync PostgreSQL database in a format that can be
4+
included as a custom genesis state for a new chain.
5+
6+
This is particularly useful in data migration scenarios where a large existing state needs to be applied to a newly
7+
created chain.
8+
9+
A typical workflow could be:
10+
11+
- Run a chain locally, not connected to the real L1, and add all required data to it.
12+
- Export the data using the `custom_genesis_export` tool.
13+
- Create a new chain connected to the real ecosystem using the exported data.
14+
15+
## How it works
16+
17+
The tool exports all entries from `storage_logs`, and `factory_deps`, except those related to the system context. The
18+
data is then written to a binary file using the Rust standard library following a simple serialisation format.
19+
20+
`custom_genesis_export` can be built using the following command:
21+
22+
```shell
23+
cargo build --release -p custom_genesis_export
24+
```
25+
26+
And then executed using the following command, where:
27+
28+
- `database-url` is the URL of the PostgreSQL database.
29+
- `genesis-config-path` is the path to the `genesis.yaml` configuration file, used to set up a new chain (located in the
30+
`file_based` directory).
31+
- `output-path` is the path to the generated binary output file.
32+
33+
```shell
34+
custom_genesis_export --database-url=postgres://postgres:notsecurepassword@localhost:5432/zksync_server_localhost_validium --genesis-config-path=/Users/ischasny/Dev/zksync-era/etc/env/file_based/genesis.yaml --output-path=export.bin
35+
```
36+
37+
> Please make sure that the database is not written into before running data export.
38+
39+
After the export is completed, the tool will make the following updates to the `genesis.yaml` file in-place:
40+
41+
- Update `genesis_root_hash`, `rollup_last_leaf_index`, and `genesis_commitment` to match the contents of the export
42+
file.
43+
- Add a `custom_genesis_state_path` property pointing to the data export.
44+
45+
The modified genesis file can be used to bootstrap an ecosystem or initialize new chains. The data export will be
46+
automatically recognized by the server during the execution of `zkstack ecosystem init ...` and
47+
`zkstack chain create ...` commands.
48+
49+
### Running considerations
50+
51+
- All chains within the same ecosystem must be bootstrapped from the same genesis state. This is enforced at the
52+
protocol level. If two chains require different states, this can only be achieved by bringing the chain into the
53+
ecosystem through governance voting.
54+
- If a chain is added to the ecosystem via a vote, ensure no assets are minted on the old bridge, as this would create
55+
discrepancies with the new one. One should set gas prices to zero when generating a state to account for that.
56+
- To calculate genesis parameters, the tool must load all VM logs into RAM. This is due to implementation specifics. For
57+
larger states, ensure the VM has sufficient RAM capacity.
58+
- After the import, block numbers for all VM logs will be reset to zero - if the imported data has been indexed based on
59+
block number, such indexes will break.
60+
- External Nodes will have to be bootstrapped from data snapshot (i.e. genesis can't be generated locally).
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
extern crate core;
2+
3+
use std::{fs, fs::File, io::BufWriter, path::PathBuf, str::FromStr};
4+
5+
use clap::Parser;
6+
use zksync_contracts::BaseSystemContractsHashes;
7+
use zksync_core_leftovers::temp_config_store::read_yaml_repr;
8+
use zksync_dal::{custom_genesis_export_dal::GenesisState, ConnectionPool, Core, CoreDal};
9+
use zksync_node_genesis::{make_genesis_batch_params, utils::get_deduped_log_queries};
10+
use zksync_protobuf_config::encode_yaml_repr;
11+
use zksync_types::{url::SensitiveUrl, StorageLog};
12+
13+
#[derive(Debug, Parser)]
14+
#[command(name = "Custom genesis export tool", author = "Matter Labs")]
15+
struct Args {
16+
/// PostgreSQL connection string for the database to export.
17+
#[arg(short, long)]
18+
database_url: Option<String>,
19+
20+
/// Output file path.
21+
#[arg(short, long, default_value = "genesis_export.bin")]
22+
output_path: PathBuf,
23+
24+
/// Path to the genesis.yaml
25+
#[arg(short, long)]
26+
genesis_config_path: PathBuf,
27+
}
28+
29+
/// The `custom_genesis_export` tool allows exporting storage logs and factory dependencies
30+
/// from the ZKSync PostgreSQL database in a way that they can be used as a custom genesis state for a new chain.
31+
///
32+
/// Inputs:
33+
/// * `database_url` - URL to the PostgreSQL database.
34+
/// * `output` - Path to the output file.
35+
/// * `genesis_config_path` - Path to the `genesis.yaml` configuration file, which will be used to set up a new chain (located in the `file_based` directory).
36+
///
37+
/// Given the inputs above, `custom_genesis_export` will perform the following:
38+
/// * Read storage logs, and factory dependencies; filter out those related to the system context,
39+
/// and save the remaining data to the output file.
40+
/// * Calculate the new `genesis_root_hash`, `rollup_last_leaf_index`, and `genesis_commitment`, then update these
41+
/// in-place in the provided `genesis.yaml`. Additionally, the tool will add a `custom_genesis_state_path` property
42+
/// pointing to the genesis export.
43+
///
44+
/// Note: To calculate the new genesis parameters, the current implementation requires loading all storage logs
45+
/// into RAM. This is necessary due to the specific sorting and filtering that need to be applied.
46+
/// For larger states, keep this in mind and ensure you have a machine with sufficient RAM.
47+
#[tokio::main]
48+
async fn main() -> anyhow::Result<()> {
49+
let args = Args::parse();
50+
51+
let mut out = BufWriter::new(File::create(&args.output_path)?);
52+
53+
println!(
54+
"Export file: {}",
55+
args.output_path.canonicalize()?.display(),
56+
);
57+
58+
println!("Connecting to source database...");
59+
60+
let db_url = args.database_url.or_else(|| std::env::var("DATABASE_URL").ok()).expect("Specify the database connection string in either a CLI argument or in the DATABASE_URL environment variable.");
61+
// we need only 1 DB connection at most for data export
62+
let connection_pool_builder =
63+
ConnectionPool::<Core>::builder(SensitiveUrl::from_str(db_url.as_str())?, 1);
64+
let connection_pool = connection_pool_builder.build().await?;
65+
66+
let mut storage = connection_pool.connection().await?;
67+
let mut transaction = storage.start_transaction().await?;
68+
69+
println!("Connected to source database.");
70+
71+
let storage_logs = transaction
72+
.custom_genesis_export_dal()
73+
.get_storage_logs()
74+
.await?;
75+
let factory_deps = transaction
76+
.custom_genesis_export_dal()
77+
.get_factory_deps()
78+
.await?;
79+
80+
transaction.commit().await?;
81+
82+
println!(
83+
"Loaded {} storage logs {} factory deps from source database.",
84+
storage_logs.len(),
85+
factory_deps.len()
86+
);
87+
88+
let storage_logs_for_genesis: Vec<StorageLog> =
89+
storage_logs.iter().map(StorageLog::from).collect();
90+
91+
bincode::serialize_into(
92+
&mut out,
93+
&GenesisState {
94+
storage_logs,
95+
factory_deps,
96+
},
97+
)?;
98+
99+
println!(
100+
"Saved genesis state into the file {}.",
101+
args.output_path.display()
102+
);
103+
println!("Calculating new genesis parameters");
104+
105+
let mut genesis_config = read_yaml_repr::<zksync_protobuf_config::proto::genesis::Genesis>(
106+
&args.genesis_config_path,
107+
)?;
108+
109+
let base_system_contract_hashes = BaseSystemContractsHashes {
110+
bootloader: genesis_config
111+
.bootloader_hash
112+
.ok_or(anyhow::anyhow!("No bootloader_hash specified"))?,
113+
default_aa: genesis_config
114+
.default_aa_hash
115+
.ok_or(anyhow::anyhow!("No default_aa_hash specified"))?,
116+
evm_emulator: genesis_config.evm_emulator_hash,
117+
};
118+
119+
let (genesis_batch_params, _) = make_genesis_batch_params(
120+
get_deduped_log_queries(&storage_logs_for_genesis),
121+
base_system_contract_hashes,
122+
genesis_config
123+
.protocol_version
124+
.ok_or(anyhow::anyhow!("No bootloader_hash specified"))?
125+
.minor,
126+
);
127+
128+
genesis_config.genesis_root_hash = Some(genesis_batch_params.root_hash);
129+
genesis_config.rollup_last_leaf_index = Some(genesis_batch_params.rollup_last_leaf_index);
130+
genesis_config.genesis_commitment = Some(genesis_batch_params.commitment);
131+
genesis_config.custom_genesis_state_path =
132+
args.output_path.canonicalize()?.to_str().map(String::from);
133+
134+
let bytes =
135+
encode_yaml_repr::<zksync_protobuf_config::proto::genesis::Genesis>(&genesis_config)?;
136+
fs::write(&args.genesis_config_path, &bytes)?;
137+
138+
println!("Done.");
139+
140+
Ok(())
141+
}

core/lib/config/src/configs/genesis.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub struct GenesisConfig {
3232
pub fee_account: Address,
3333
pub dummy_verifier: bool,
3434
pub l1_batch_commit_data_generator_mode: L1BatchCommitmentMode,
35+
pub custom_genesis_state_path: Option<String>,
3536
}
3637

3738
impl GenesisConfig {
@@ -60,6 +61,7 @@ impl GenesisConfig {
6061
l2_chain_id: L2ChainId::default(),
6162
dummy_verifier: false,
6263
l1_batch_commit_data_generator_mode: L1BatchCommitmentMode::Rollup,
64+
custom_genesis_state_path: None,
6365
}
6466
}
6567
}

core/lib/config/src/testonly.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ impl Distribution<configs::GenesisConfig> for EncodeDist {
746746
0 => L1BatchCommitmentMode::Rollup,
747747
_ => L1BatchCommitmentMode::Validium,
748748
},
749+
custom_genesis_state_path: None,
749750
}
750751
}
751752
}

core/lib/dal/.sqlx/query-42f15afb71632bdfab7befb651eaa3061382dfe4142c7fc46df1dfebec34ec92.json

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/lib/dal/.sqlx/query-bd37db29c86a84ed09ed0633e8511c5e74988422abd052a33ff1ec5db41f7d52.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)