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: allow syncing op-mainnet with only state and without importing blocks/receipts #10850

Merged
merged 26 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5361270
add init_state --without-ovm feature
joshieDo Sep 11, 2024
3ed8c4b
fmt
joshieDo Sep 11, 2024
1e81c64
use LAST_OVM_HEADER_HASH
joshieDo Sep 11, 2024
c5d811b
add minimal bootstrap to docs
joshieDo Sep 12, 2024
4a6c0e7
init_from_state_dump takes a provider_rw instead
joshieDo Sep 13, 2024
a83f0fe
set up chain up to bedrock block
joshieDo Sep 13, 2024
8cf72d1
fmt
joshieDo Sep 13, 2024
884b875
fix docs
joshieDo Sep 13, 2024
9538ab2
docs
joshieDo Sep 13, 2024
206390c
Merge remote-tracking branch 'origin/main' into joshie/dummy-ovm
joshieDo Sep 13, 2024
658dd1c
clippy
joshieDo Sep 13, 2024
2f168b4
use insert_block for bedrock block
joshieDo Sep 13, 2024
c520e17
split storage commits
joshieDo Sep 13, 2024
474c29c
dont delete header
joshieDo Sep 13, 2024
b4e91be
fmt
joshieDo Sep 13, 2024
874e466
clippy
joshieDo Sep 13, 2024
8eaa62f
Update crates/optimism/cli/src/commands/init_state/mod.rs
joshieDo Sep 16, 2024
94c44e9
Update crates/optimism/cli/src/commands/init_state/bedrock.rs
joshieDo Sep 16, 2024
0632da6
Merge remote-tracking branch 'origin/main' into joshie/dummy-ovm
joshieDo Sep 17, 2024
a24b95d
Merge branch 'joshie/dummy-ovm' of github.com:paradigmxyz/reth into j…
joshieDo Sep 17, 2024
41c9329
reuse reth_cli_commands::init_state::InitStateCommand
joshieDo Sep 17, 2024
9b57751
rephrase docs
joshieDo Sep 18, 2024
c0fc70b
use threads instead of rayon
joshieDo Sep 18, 2024
663c74c
add target_height to append_dummy_chain
joshieDo Sep 18, 2024
483380a
Merge remote-tracking branch 'origin/main' into joshie/dummy-ovm
joshieDo Sep 18, 2024
a52c8d4
fmt
joshieDo Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 26 additions & 6 deletions book/run/sync-op-mainnet.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Sync OP Mainnet

To sync OP mainnet, bedrock state needs to be imported as a starting point. There are currently two ways:

* Minimal bootstrap: only state is imported without any OVM historical data.
* Full bootstrap: state, blocks and receipts are imported.

## Minimal bootstrap

**Bedrock state is required.** It can be exported from [op-geth](https://github.com/testinprod-io/op-erigon/blob/pcw109550/bedrock-db-migration/bedrock-migration.md#export-state) (**.jsonl**) or downloaded directly from [here](https://mega.nz/file/GdZ1xbAT#a9cBv3AqzsTGXYgX7nZc_3fl--tcBmOAIwIA5ND6kwc).
joshieDo marked this conversation as resolved.
Show resolved Hide resolved

```sh
$ op-reth init-state --without-ovm --chain optimism --datadir op-mainnet world_trie_state.jsonl

$ op-reth node --chain optimism --datadir op-mainnet --debug.tip 0x098f87b75c8b861c775984f9d5dbe7b70cbbbc30fc15adb03a5044de0144f2d0 # block #125200000
```


## Full bootstrap

### Import state

To sync OP mainnet, the Bedrock datadir needs to be imported to use as starting point.
Blocks lower than the OP mainnet Bedrock fork, are built on the OVM and cannot be executed on the EVM.
For this reason, the chain segment from genesis until Bedrock, must be manually imported to circumvent
Expand All @@ -10,17 +30,17 @@ Importing OP mainnet Bedrock datadir requires exported data:
- Blocks [and receipts] below Bedrock
- State snapshot at first Bedrock block

## Manual Export Steps
### Manual Export Steps

The `op-geth` Bedrock datadir can be downloaded from <https://datadirs.optimism.io/mainnet-bedrock.tar.zst>.

To export the OVM chain from `op-geth`, clone the `testinprod-io/op-geth` repo and checkout
<https://github.com/testinprod-io/op-geth/pull/1>. Commands to export blocks, receipts and state dump can be
found in `op-geth/migrate.sh`.

## Manual Import Steps
### Manual Import Steps

### 1. Import Blocks
#### 1. Import Blocks

Imports a `.rlp` file of blocks.

Expand All @@ -30,7 +50,7 @@ Import of >100 million OVM blocks, from genesis to Bedrock, completes in 45 minu
$ op-reth import-op <exported-blocks>
```

### 2. Import Receipts
#### 2. Import Receipts

This step is optional. To run a full node, skip this step. If however receipts are to be imported, the
corresponding transactions must already be imported (see [step 1](#1-import-blocks)).
Expand All @@ -44,7 +64,7 @@ Import of >100 million OVM receipts, from genesis to Bedrock, completes in 30 mi
$ op-reth import-receipts-op <exported-receipts>
```

### 3. Import State
#### 3. Import State

Imports a `.jsonl` state dump. The block at which the state dump is made, must be the latest block in
reth's database. This should be block 105 235 063, the first Bedrock block (see [step 1](#1-import-blocks)).
Expand All @@ -61,4 +81,4 @@ Running the node with `--debug.tip <block-hash>`syncs the node without help from
block hash can be taken from the latest block on <https://optimistic.etherscan.io>.

Use `op-node` to track the tip. Start `op-node` with `--syncmode=execution-layer` and `--l2.enginekind=reth`. If `op-node`'s RPC
connection to L1 is over localhost, `--l1.trustrpc` can be set to improve performance.
connection to L1 is over localhost, `--l1.trustrpc` can be set to improve performance.
10 changes: 7 additions & 3 deletions crates/cli/commands/src/init_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tracing::info;
#[derive(Debug, Parser)]
pub struct InitStateCommand<C: ChainSpecParser> {
#[command(flatten)]
env: EnvironmentArgs<C>,
pub env: EnvironmentArgs<C>,

/// JSONL file with state dump.
///
Expand All @@ -37,7 +37,7 @@ pub struct InitStateCommand<C: ChainSpecParser> {
/// Allows init at a non-genesis block. Caution! Blocks must be manually imported up until
/// and including the non-genesis block to init chain at. See 'import' command.
#[arg(value_name = "STATE_DUMP_FILE", verbatim_doc_comment)]
state: PathBuf,
pub state: PathBuf,
}

impl<C: ChainSpecParser<ChainSpec = ChainSpec>> InitStateCommand<C> {
Expand Down Expand Up @@ -71,5 +71,9 @@ pub fn init_at_state<N: NodeTypesWithDB<ChainSpec = ChainSpec>>(
let file = File::open(state_dump_path)?;
let reader = BufReader::new(file);

init_from_state_dump(reader, factory, etl_config)
let provider_rw = factory.provider_rw()?;
let hash = init_from_state_dump(reader, &provider_rw.0, etl_config)?;
provider_rw.commit()?;

Ok(hash)
}
2 changes: 2 additions & 0 deletions crates/optimism/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ reth-cli-commands.workspace = true
reth-consensus.workspace = true
reth-db = { workspace = true, features = ["mdbx"] }
reth-db-api.workspace = true
reth-db-common.workspace = true
reth-downloaders.workspace = true
reth-provider.workspace = true
reth-prune.workspace = true
Expand Down Expand Up @@ -60,6 +61,7 @@ tokio = { workspace = true, features = [
tokio-util = { workspace = true, features = ["codec"] }
tracing.workspace = true
eyre.workspace = true
rayon.workspace = true

[dev-dependencies]
tempfile.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/optimism/cli/src/commands/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use reth_downloaders::file_client::{
};
use reth_node_builder::NodeTypesWithEngine;
use reth_node_core::version::SHORT_VERSION;
use reth_optimism_primitives::bedrock_import::is_dup_tx;
use reth_optimism_primitives::bedrock::is_dup_tx;
use reth_provider::StageCheckpointReader;
use reth_prune::PruneModes;
use reth_stages::StageId;
Expand Down
2 changes: 1 addition & 1 deletion crates/optimism/cli/src/commands/import_receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use reth_downloaders::{
use reth_execution_types::ExecutionOutcome;
use reth_node_builder::{NodeTypesWithDB, NodeTypesWithEngine};
use reth_node_core::version::SHORT_VERSION;
use reth_optimism_primitives::bedrock_import::is_dup_tx;
use reth_optimism_primitives::bedrock::is_dup_tx;
use reth_primitives::Receipts;
use reth_provider::{
writer::UnifiedStorageWriter, DatabaseProviderFactory, OriginalValuesKnown, ProviderFactory,
Expand Down
132 changes: 132 additions & 0 deletions crates/optimism/cli/src/commands/init_state/bedrock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use alloy_primitives::B256;
use reth_db::Database;
use reth_optimism_primitives::bedrock::{BEDROCK_HEADER, BEDROCK_HEADER_HASH, BEDROCK_HEADER_TTD};
use reth_primitives::{
BlockBody, Header, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, U256,
};
use reth_provider::{
providers::StaticFileProvider, BlockWriter, DatabaseProviderRW, StageCheckpointWriter,
StaticFileWriter,
};
use reth_stages::{StageCheckpoint, StageId};
use tracing::info;

/// Creates a dummy chain (with no transactions) up to the last OVM block and appends the
/// first valid Bedrock block.
pub(crate) fn setup_op_mainnet_without_ovm<DB: Database>(
provider_rw: &DatabaseProviderRW<DB>,
static_file_provider: &StaticFileProvider,
) -> Result<(), eyre::Error> {
info!(target: "reth::cli", "Setting up dummy OVM chain before importing state.");

// Write OVM dummy data up to `BEDROCK_HEADER - 1` block
append_dummy_chain(static_file_provider)?;

info!(target: "reth::cli", "Appending Bedrock block.");

append_bedrock_block(provider_rw, static_file_provider)?;

for stage in StageId::ALL {
provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(BEDROCK_HEADER.number))?;
}

info!(target: "reth::cli", "Set up finished.");

Ok(())
}

/// Appends the first bedrock block.
///
/// By appending it, static file writer also verifies that all segments are at the same
/// height.
fn append_bedrock_block<DB: Database>(
provider_rw: &DatabaseProviderRW<DB>,
sf_provider: &StaticFileProvider,
) -> Result<(), eyre::Error> {
provider_rw.insert_block(
SealedBlockWithSenders::new(
SealedBlock::new(
SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH),
BlockBody::default(),
),
vec![],
)
.expect("no senders or txes"),
)?;

sf_provider.latest_writer(StaticFileSegment::Headers)?.append_header(
&BEDROCK_HEADER,
BEDROCK_HEADER_TTD,
&BEDROCK_HEADER_HASH,
)?;

sf_provider
.latest_writer(StaticFileSegment::Receipts)?
.increment_block(BEDROCK_HEADER.number)?;

sf_provider
.latest_writer(StaticFileSegment::Transactions)?
.increment_block(BEDROCK_HEADER.number)?;

Ok(())
}

/// Creates a dummy chain with no transactions/receipts up to `BEDROCK_HEADER - 1` block.
///
/// * Headers: It will push an empty block.
/// * Transactions: It will not push any tx, only increments the end block range.
/// * Receipts: It will not push any receipt, only increments the end block range.
fn append_dummy_chain(sf_provider: &StaticFileProvider) -> Result<(), eyre::Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this accept the target as argument, could be useful in the future

let mut headers_writer = sf_provider.latest_writer(StaticFileSegment::Headers)?;
let txs_writer = sf_provider.latest_writer(StaticFileSegment::Transactions)?;
let receipts_writer = sf_provider.latest_writer(StaticFileSegment::Receipts)?;
let (tx, rx) = std::sync::mpsc::channel();

rayon::scope(|s| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be fine because this is the only thing we're running, even if this does a lot of IO.

still would prefer regular thread for this.

// Spawn jobs for incrementing the block end range of transactions and receipts
for mut writer in [txs_writer, receipts_writer] {
let tx_clone = tx.clone();
s.spawn(move |_| {
for block_num in 1..BEDROCK_HEADER.number {
if let Err(e) = writer.increment_block(block_num) {
tx_clone.send(Err(e)).unwrap();
return;
}
}
tx_clone.send(Ok(())).unwrap();
});
}

// Spawn job for appending empty headers
s.spawn(move |_| {
let mut empty_header = Header::default();
// TODO: should we fill with real parent_hash?
for block_num in 1..BEDROCK_HEADER.number {
empty_header.number = block_num;
if let Err(e) = headers_writer.append_header(&empty_header, U256::ZERO, &B256::ZERO)
{
tx.send(Err(e)).unwrap();
return;
}
}
tx.send(Ok(())).unwrap();
});
});

// Catches any StaticFileWriter error.
while let Ok(r) = rx.recv() {
r?;
}

// If, for any reason, rayon crashes this verifies if all segments are at the same height.
for segment in
[StaticFileSegment::Headers, StaticFileSegment::Receipts, StaticFileSegment::Transactions]
{
assert_eq!(
sf_provider.latest_writer(segment)?.user_header().block_end(),
Some(BEDROCK_HEADER.number - 1)
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should include a message because will panic

}

Ok(())
}
80 changes: 80 additions & 0 deletions crates/optimism/cli/src/commands/init_state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! Command that initializes the node from a genesis file.

use clap::Parser;
use reth_chainspec::ChainSpec;
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_commands::common::{AccessRights, Environment};
use reth_db_common::init::init_from_state_dump;
use reth_node_builder::NodeTypesWithEngine;
use reth_optimism_primitives::bedrock::BEDROCK_HEADER;
use reth_provider::{
BlockNumReader, ChainSpecProvider, StaticFileProviderFactory, StaticFileWriter,
};
use std::{fs::File, io::BufReader};
use tracing::info;

mod bedrock;

/// Initializes the database with the genesis block.
#[derive(Debug, Parser)]
pub struct InitStateCommandOp<C: ChainSpecParser> {
#[command(flatten)]
init_state: reth_cli_commands::init_state::InitStateCommand<C>,

/// **Optimism Mainnet Only**
///
/// Specifies whether to initialize the state without relying on OVM historical data.
///
/// When enabled, and before inserting the state, it creates a dummy chain up to the last OVM
/// block (#105235062) (14GB / 90 seconds). It then, appends the Bedrock block.
///
/// - **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be
/// ignored.
#[arg(long, default_value = "false")]
without_ovm: bool,
}

impl<C: ChainSpecParser<ChainSpec = ChainSpec>> InitStateCommandOp<C> {
/// Execute the `init` command
pub async fn execute<N: NodeTypesWithEngine<ChainSpec = C::ChainSpec>>(
self,
) -> eyre::Result<()> {
info!(target: "reth::cli", "Reth init-state starting");

let Environment { config, provider_factory, .. } =
self.init_state.env.init::<N>(AccessRights::RW)?;

let static_file_provider = provider_factory.static_file_provider();
let provider_rw = provider_factory.provider_rw()?;

// OP-Mainnet may want to bootstrap a chain without OVM historical data
if provider_factory.chain_spec().is_optimism_mainnet() && self.without_ovm {
let last_block_number = provider_rw.last_block_number()?;

if last_block_number == 0 {
bedrock::setup_op_mainnet_without_ovm(&provider_rw, &static_file_provider)?;

// SAFETY: it's safe to commit static files, since in the event of a crash, they
// will be unwinded according to database checkpoints.
//
// Necessary to commit, so the BEDROCK_HEADER is accessible to provider_rw and
// init_state_dump
static_file_provider.commit()?;
} else if last_block_number > 0 && last_block_number < BEDROCK_HEADER.number {
return Err(eyre::eyre!(
"Data directory should be empty when calling init-state with --without-ovm."
))
}
}

info!(target: "reth::cli", "Initiating state dump");

let reader = BufReader::new(File::open(self.init_state.state)?);
let hash = init_from_state_dump(reader, &provider_rw.0, config.stages.etl)?;

provider_rw.commit()?;

info!(target: "reth::cli", hash = ?hash, "Genesis block written");
Ok(())
}
}
5 changes: 3 additions & 2 deletions crates/optimism/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use import_receipts::ImportReceiptsOpCommand;
use reth_chainspec::ChainSpec;
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_commands::{
config_cmd, db, dump_genesis, init_cmd, init_state,
config_cmd, db, dump_genesis, init_cmd,
node::{self, NoArgs},
p2p, prune, recover, stage,
};
Expand All @@ -15,6 +15,7 @@ use std::fmt;
mod build_pipeline;
pub mod import;
pub mod import_receipts;
pub mod init_state;

/// Commands to be executed
#[derive(Debug, Subcommand)]
Expand All @@ -30,7 +31,7 @@ pub enum Commands<
Init(init_cmd::InitCommand<Spec>),
/// Initialize the database from a state dump file.
#[command(name = "init-state")]
InitState(init_state::InitStateCommand<Spec>),
InitState(init_state::InitStateCommandOp<Spec>),
/// This syncs RLP encoded OP blocks below Bedrock from a file, without executing.
#[command(name = "import-op")]
ImportOp(ImportOpCommand<Spec>),
Expand Down
Loading
Loading