feat(op): import below bedrock blocks (#7555)

Co-authored-by: Atris <vacekj@outlook.com>
This commit is contained in:
Emilia Hane
2024-04-12 13:57:06 +02:00
committed by GitHub
parent 47d533217c
commit 9c5aea8c81
11 changed files with 152 additions and 17 deletions

View File

@ -85,7 +85,7 @@ futures.workspace = true
# misc # misc
aquamarine.workspace = true aquamarine.workspace = true
eyre.workspace = true eyre.workspace = true
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive", "env"] }
tempfile.workspace = true tempfile.workspace = true
backon = "0.4" backon = "0.4"
similar-asserts.workspace = true similar-asserts.workspace = true

View File

@ -21,7 +21,7 @@ use reth_downloaders::{
use reth_interfaces::consensus::Consensus; use reth_interfaces::consensus::Consensus;
use reth_node_core::{events::node::NodeEvent, init::init_genesis}; use reth_node_core::{events::node::NodeEvent, init::init_genesis};
use reth_node_ethereum::EthEvmConfig; use reth_node_ethereum::EthEvmConfig;
use reth_primitives::{stage::StageId, ChainSpec, PruneModes, B256}; use reth_primitives::{stage::StageId, ChainSpec, PruneModes, B256, OP_RETH_MAINNET_BELOW_BEDROCK};
use reth_provider::{HeaderSyncMode, ProviderFactory, StageCheckpointReader}; use reth_provider::{HeaderSyncMode, ProviderFactory, StageCheckpointReader};
use reth_stages::{ use reth_stages::{
prelude::*, prelude::*,
@ -61,6 +61,15 @@ pub struct ImportCommand {
)] )]
chain: Arc<ChainSpec>, chain: Arc<ChainSpec>,
/// Disables execution stage.
#[arg(long, verbatim_doc_comment)]
disable_execution: bool,
/// Import OP Mainnet chain below Bedrock. Caution! Flag must be set as env var, since the env
/// var is read by another process too, in order to make below Bedrock import work.
#[arg(long, verbatim_doc_comment, env = OP_RETH_MAINNET_BELOW_BEDROCK)]
op_mainnet_below_bedrock: bool,
#[command(flatten)] #[command(flatten)]
db: DatabaseArgs, db: DatabaseArgs,
@ -74,9 +83,18 @@ pub struct ImportCommand {
impl ImportCommand { impl ImportCommand {
/// Execute `import` command /// Execute `import` command
pub async fn execute(self) -> eyre::Result<()> { pub async fn execute(mut self) -> eyre::Result<()> {
info!(target: "reth::cli", "reth {} starting", SHORT_VERSION); info!(target: "reth::cli", "reth {} starting", SHORT_VERSION);
if self.op_mainnet_below_bedrock {
self.disable_execution = true;
debug!(target: "reth::cli", "Importing OP mainnet below bedrock");
}
if self.disable_execution {
debug!(target: "reth::cli", "Execution stage disabled");
}
// add network name to data dir // add network name to data dir
let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain); let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain);
let config_path = self.config.clone().unwrap_or_else(|| data_dir.config_path()); let config_path = self.config.clone().unwrap_or_else(|| data_dir.config_path());
@ -118,6 +136,7 @@ impl ImportCommand {
provider_factory.static_file_provider(), provider_factory.static_file_provider(),
PruneModes::default(), PruneModes::default(),
), ),
self.disable_execution,
) )
.await?; .await?;
@ -154,6 +173,7 @@ impl ImportCommand {
consensus: &Arc<C>, consensus: &Arc<C>,
file_client: Arc<FileClient>, file_client: Arc<FileClient>,
static_file_producer: StaticFileProducer<DB>, static_file_producer: StaticFileProducer<DB>,
disable_execution: bool,
) -> eyre::Result<(Pipeline<DB>, impl Stream<Item = NodeEvent>)> ) -> eyre::Result<(Pipeline<DB>, impl Stream<Item = NodeEvent>)>
where where
DB: Database + Clone + Unpin + 'static, DB: Database + Clone + Unpin + 'static,
@ -209,7 +229,8 @@ impl ImportCommand {
.max(config.stages.account_hashing.clean_threshold) .max(config.stages.account_hashing.clean_threshold)
.max(config.stages.storage_hashing.clean_threshold), .max(config.stages.storage_hashing.clean_threshold),
config.prune.map(|prune| prune.segments).unwrap_or_default(), config.prune.map(|prune| prune.segments).unwrap_or_default(),
)), ))
.disable_if(StageId::Execution, || disable_execution),
) )
.build(provider_factory, static_file_producer); .build(provider_factory, static_file_producer);

View File

@ -5,6 +5,7 @@ pub mod db;
pub mod debug_cmd; pub mod debug_cmd;
pub mod dump_genesis; pub mod dump_genesis;
pub mod import; pub mod import;
pub mod init_cmd; pub mod init_cmd;
pub mod node; pub mod node;

View File

@ -65,7 +65,8 @@ impl FileClient {
let metadata = file.metadata().await?; let metadata = file.metadata().await?;
let file_len = metadata.len(); let file_len = metadata.len();
// read the entire file into memory // todo: read chunks into memory. for op mainnet 1/8 th of blocks below bedrock can be
// decoded at once
let mut reader = vec![]; let mut reader = vec![];
file.read_to_end(&mut reader).await.unwrap(); file.read_to_end(&mut reader).await.unwrap();
@ -76,8 +77,12 @@ impl FileClient {
// use with_capacity to make sure the internal buffer contains the entire file // use with_capacity to make sure the internal buffer contains the entire file
let mut stream = FramedRead::with_capacity(&reader[..], BlockFileCodec, file_len as usize); let mut stream = FramedRead::with_capacity(&reader[..], BlockFileCodec, file_len as usize);
let mut log_interval = 0;
let mut log_interval_start_block = 0;
while let Some(block_res) = stream.next().await { while let Some(block_res) = stream.next().await {
let block = block_res?; let block = block_res?;
let block_number = block.header.number;
let block_hash = block.header.hash_slow(); let block_hash = block.header.hash_slow();
// add to the internal maps // add to the internal maps
@ -91,6 +96,17 @@ impl FileClient {
withdrawals: block.withdrawals, withdrawals: block.withdrawals,
}, },
); );
if log_interval == 0 {
log_interval_start_block = block_number;
} else if log_interval % 100000 == 0 {
trace!(target: "downloaders::file",
blocks=?log_interval_start_block..=block_number,
"inserted blocks into db"
);
log_interval_start_block = block_number + 1;
}
log_interval += 1;
} }
trace!(blocks = headers.len(), "Initialized file client"); trace!(blocks = headers.len(), "Initialized file client");

View File

@ -10,14 +10,14 @@ use std::{
}; };
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
use reth_primitives::{BASE_MAINNET, BASE_SEPOLIA, OP_SEPOLIA}; use reth_primitives::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA};
#[cfg(not(feature = "optimism"))] #[cfg(not(feature = "optimism"))]
use reth_primitives::{DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA}; use reth_primitives::{DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA};
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
/// Chains supported by op-reth. First value should be used as the default. /// Chains supported by op-reth. First value should be used as the default.
pub const SUPPORTED_CHAINS: &[&str] = &["base", "base-sepolia", "optimism-sepolia"]; pub const SUPPORTED_CHAINS: &[&str] = &["base", "base-sepolia", "optimism", "optimism-sepolia"];
#[cfg(not(feature = "optimism"))] #[cfg(not(feature = "optimism"))]
/// Chains supported by reth. First value should be used as the default. /// Chains supported by reth. First value should be used as the default.
pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "goerli", "holesky", "dev"]; pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "goerli", "holesky", "dev"];
@ -43,11 +43,13 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Er
#[cfg(not(feature = "optimism"))] #[cfg(not(feature = "optimism"))]
"dev" => DEV.clone(), "dev" => DEV.clone(),
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
"optimism" => OP_MAINNET.clone(),
#[cfg(feature = "optimism")]
"optimism_sepolia" | "optimism-sepolia" => OP_SEPOLIA.clone(), "optimism_sepolia" | "optimism-sepolia" => OP_SEPOLIA.clone(),
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
"base_sepolia" | "base-sepolia" => BASE_SEPOLIA.clone(),
#[cfg(feature = "optimism")]
"base" => BASE_MAINNET.clone(), "base" => BASE_MAINNET.clone(),
#[cfg(feature = "optimism")]
"base_sepolia" | "base-sepolia" => BASE_SEPOLIA.clone(),
_ => { _ => {
let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
serde_json::from_str(&raw)? serde_json::from_str(&raw)?
@ -78,11 +80,13 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error
#[cfg(not(feature = "optimism"))] #[cfg(not(feature = "optimism"))]
"dev" => DEV.clone(), "dev" => DEV.clone(),
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
"optimism" => OP_MAINNET.clone(),
#[cfg(feature = "optimism")]
"optimism_sepolia" | "optimism-sepolia" => OP_SEPOLIA.clone(), "optimism_sepolia" | "optimism-sepolia" => OP_SEPOLIA.clone(),
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
"base_sepolia" | "base-sepolia" => BASE_SEPOLIA.clone(),
#[cfg(feature = "optimism")]
"base" => BASE_MAINNET.clone(), "base" => BASE_MAINNET.clone(),
#[cfg(feature = "optimism")]
"base_sepolia" | "base-sepolia" => BASE_SEPOLIA.clone(),
_ => { _ => {
// try to read json from path first // try to read json from path first
let raw = match fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned())) { let raw = match fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned())) {

View File

@ -0,0 +1,32 @@
{
"config": {
"ChainName": "optimism-mainnet",
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 3950000,
"londonBlock": 3950000,
"arrowGlacierBlock": 3950000,
"grayGlacierBlock": 3950000,
"mergeNetsplitBlock": 3950000,
"bedrockBlock": 105235063,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"optimism": {
"eip1559Elasticity": 6,
"eip1559Denominator": 50
},
"regolithTime": 0
},
"difficulty": "1",
"gasLimit": "15000000",
"extradata": "0x000000000000000000000000000000000000000000000000000000000000000000000398232e2064f896018496b4b44b3d62751f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"alloc": {}
}

View File

@ -6,7 +6,7 @@ pub use spec::{
MAINNET, SEPOLIA, MAINNET, SEPOLIA,
}; };
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_SEPOLIA}; pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA};
// The chain spec module. // The chain spec module.
mod spec; mod spec;

View File

@ -243,6 +243,58 @@ pub static DEV: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
.into() .into()
}); });
/// The Optimism Mainnet spec
#[cfg(feature = "optimism")]
pub static OP_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::optimism_mainnet(),
// genesis contains empty alloc field because state at first bedrock block is imported
// manually from trusted source
genesis: serde_json::from_str(include_str!("../../res/genesis/optimism.json"))
.expect("Can't deserialize Optimism Mainnet genesis json"),
genesis_hash: Some(b256!(
"7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b"
)),
fork_timestamps: ForkTimestamps::default()
.shanghai(1699981200)
.canyon(1699981200)
.cancun(1707238800)
.ecotone(1707238800),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(3950000)),
(Hardfork::London, ForkCondition::Block(3950000)),
(Hardfork::ArrowGlacier, ForkCondition::Block(3950000)),
(Hardfork::GrayGlacier, ForkCondition::Block(3950000)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(3950000), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(105235063)),
(Hardfork::Regolith, ForkCondition::Timestamp(0)),
]),
base_fee_params: BaseFeeParamsKind::Variable(
vec![
(Hardfork::London, BaseFeeParams::optimism()),
(Hardfork::Canyon, BaseFeeParams::optimism_canyon()),
]
.into(),
),
prune_delete_limit: 1700,
..Default::default()
}
.into()
});
/// The OP Sepolia spec /// The OP Sepolia spec
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
pub static OP_SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| { pub static OP_SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
@ -469,7 +521,7 @@ impl BaseFeeParams {
} }
} }
/// Get the base fee parameters for optimism goerli (post Canyon) /// Get the base fee parameters for optimism sepolia (post Canyon)
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
pub const fn optimism_sepolia_canyon() -> BaseFeeParams { pub const fn optimism_sepolia_canyon() -> BaseFeeParams {
BaseFeeParams { BaseFeeParams {
@ -3176,7 +3228,7 @@ Post-merge hard forks (timestamp based):
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
#[test] #[test]
fn latest_op_mainnet_fork_id() { fn latest_base_mainnet_fork_id() {
assert_eq!( assert_eq!(
ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 0 }, ForkId { hash: ForkHash([0x51, 0xcc, 0x98, 0xb3]), next: 0 },
BASE_MAINNET.latest_fork_id() BASE_MAINNET.latest_fork_id()

View File

@ -98,8 +98,9 @@ pub use transaction::{
InvalidTransactionError, Signature, Transaction, TransactionKind, TransactionMeta, InvalidTransactionError, Signature, Transaction, TransactionKind, TransactionMeta,
TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930, TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxEip1559, TxEip2930,
TxEip4844, TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, TxEip4844, TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, OP_RETH_MAINNET_BELOW_BEDROCK,
}; };
pub use withdrawal::{Withdrawal, Withdrawals}; pub use withdrawal::{Withdrawal, Withdrawals};
// Re-exports // Re-exports
@ -141,7 +142,7 @@ pub use c_kzg as kzg;
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
mod optimism { mod optimism {
pub use crate::{ pub use crate::{
chain::{BASE_MAINNET, BASE_SEPOLIA, OP_SEPOLIA}, chain::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA},
transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID}, transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID},
}; };
} }

View File

@ -28,7 +28,7 @@ pub use sidecar::generate_blob_sidecar;
#[cfg(feature = "c-kzg")] #[cfg(feature = "c-kzg")]
pub use sidecar::{BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError}; pub use sidecar::{BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError};
pub use signature::Signature; pub use signature::{Signature, OP_RETH_MAINNET_BELOW_BEDROCK};
pub use tx_type::{ pub use tx_type::{
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
}; };

View File

@ -14,6 +14,9 @@ const SECP256K1N_HALF: U256 = U256::from_be_bytes([
0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0, 0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
]); ]);
/// Running OP Mainnet migration for chain below bedrock.]
pub const OP_RETH_MAINNET_BELOW_BEDROCK: &str = "OP_RETH_MAINNET_BELOW_BEDROCK";
/// r, s: Values corresponding to the signature of the /// r, s: Values corresponding to the signature of the
/// transaction and used to determine the sender of /// transaction and used to determine the sender of
/// the transaction; formally Tr and Ts. This is expanded in Appendix F of yellow paper. /// the transaction; formally Tr and Ts. This is expanded in Appendix F of yellow paper.
@ -94,9 +97,14 @@ impl Signature {
let v = u64::decode(buf)?; let v = u64::decode(buf)?;
let r = Decodable::decode(buf)?; let r = Decodable::decode(buf)?;
let s = Decodable::decode(buf)?; let s = Decodable::decode(buf)?;
if v < 35 { if v < 35 {
// non-EIP-155 legacy scheme, v = 27 for even y-parity, v = 28 for odd y-parity // non-EIP-155 legacy scheme, v = 27 for even y-parity, v = 28 for odd y-parity
if v != 27 && v != 28 { if v != 27 && v != 28 {
#[cfg(feature = "optimism")]
if std::env::var(OP_RETH_MAINNET_BELOW_BEDROCK) == Ok(true.to_string()) && v == 0 {
return Ok((Signature { r, s, odd_y_parity: false }, None))
}
return Err(RlpError::Custom("invalid Ethereum signature (V is not 27 or 28)")) return Err(RlpError::Custom("invalid Ethereum signature (V is not 27 or 28)"))
} }
let odd_y_parity = v == 28; let odd_y_parity = v == 28;