diff --git a/crates/ethereum/cli/src/chainspec.rs b/crates/ethereum/cli/src/chainspec.rs index 419ff71cb..24151fe2f 100644 --- a/crates/ethereum/cli/src/chainspec.rs +++ b/crates/ethereum/cli/src/chainspec.rs @@ -8,7 +8,7 @@ use reth_primitives::{Header, SealedHeader}; use std::sync::Arc; /// Chains supported by reth. First value should be used as the default. -pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "sepolia", "holesky", "dev"]; +pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "testnet", "sepolia", "holesky", "dev"]; static GENESIS_HASH: B256 = b256!("d8fcc13b6a195b88b7b2da3722ff6cad767b13a8c1e9ffb1c73aa9d216d895f0"); @@ -92,6 +92,7 @@ pub static HL_MAINNET: Lazy> = Lazy::new(|| { pub fn chain_value_parser(s: &str) -> eyre::Result, eyre::Error> { Ok(match s { "mainnet" => HL_MAINNET.clone(), + "testnet" => Arc::new(super::hl_testnet::load_hl_testnet()), "sepolia" => SEPOLIA.clone(), "holesky" => HOLESKY.clone(), "dev" => DEV.clone(), diff --git a/crates/ethereum/cli/src/hl_testnet.rs b/crates/ethereum/cli/src/hl_testnet.rs new file mode 100644 index 000000000..4f52ad5b1 --- /dev/null +++ b/crates/ethereum/cli/src/hl_testnet.rs @@ -0,0 +1,113 @@ +use alloy_consensus::Header; +use alloy_genesis::{ChainConfig, Genesis}; +use alloy_primitives::U256; +use alloy_rlp::Decodable; +use reqwest::blocking::get; +use reth_chainspec::{ChainSpec, DEV_HARDFORKS}; +use reth_primitives::SealedHeader; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{Read, Write}; + +pub(crate) fn load_hl_testnet() -> ChainSpec { + const TESTNET_GENESIS_URL: &str = "https://raw.githubusercontent.com/sprites0/hl-testnet-genesis/main/19386700.rlp"; + + fn download_testnet_genesis() -> Result<&'static str, Box> { + let path = "/tmp/hl_testnet.rmp.lz4"; + println!("Downloading testnet genesis"); + let mut response = get(TESTNET_GENESIS_URL)?; + if let Some(length) = response.content_length() { + // Check if the file exists + if let Ok(metadata) = std::fs::metadata(path) { + if metadata.len() == length { + println!("Already downloaded"); + return Ok(path); + } + } + } + let mut file = File::create(path)?; + let mut downloaded = 0; + let total_size = response.content_length().unwrap_or(0); + let mut buffer = vec![0; 0x100000]; + + loop { + let size = response.read(buffer.as_mut_slice())?; + if size == 0 { + break; + } + file.write_all(&buffer[..size])?; + downloaded += size as u64; + println!( + "Downloaded {} of {} bytes ({}%)", + downloaded, + total_size, + (downloaded as f64 / total_size as f64 * 100.0).round() + ); + } + Ok(path) + } + + let path = download_testnet_genesis().expect("Failed to download testnet genesis"); + let mut file = File::open(path).expect("Failed to open testnet genesis"); + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).expect("Failed to read testnet genesis"); + let mut header = Header::decode(&mut &buffer[..]).expect("Failed to decode testnet genesis"); + + let config = ChainConfig { + chain_id: 998, + homestead_block: Some(0), + dao_fork_block: Some(0), + dao_fork_support: false, + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: Some(0), + osaka_time: Some(0), + terminal_total_difficulty: Some(U256::ZERO), + terminal_total_difficulty_passed: true, + ethash: None, + clique: None, + parlia: None, + extra_fields: Default::default(), + deposit_contract_address: None, + blob_schedule: Default::default(), + }; + header.number = 0; + let genesis_header = SealedHeader::new(header.clone(), header.hash_slow()); + let genesis = Genesis { + config, + nonce: header.nonce.into(), + timestamp: header.timestamp, + extra_data: header.extra_data, + gas_limit: header.gas_limit, + difficulty: header.difficulty, + mix_hash: header.mix_hash, + coinbase: header.beneficiary, + alloc: BTreeMap::default(), + base_fee_per_gas: header.base_fee_per_gas.map(|x| x.into()), + excess_blob_gas: header.excess_blob_gas, + blob_gas_used: header.blob_gas_used, + number: None, + }; + + ChainSpec { + chain: alloy_chains::Chain::from_id(998), + genesis: genesis.into(), + genesis_header, + hardforks: DEV_HARDFORKS.clone(), + prune_delete_limit: 10000, + ..Default::default() + } +} diff --git a/crates/ethereum/cli/src/lib.rs b/crates/ethereum/cli/src/lib.rs index 7296e7c2d..dcdfcdb03 100644 --- a/crates/ethereum/cli/src/lib.rs +++ b/crates/ethereum/cli/src/lib.rs @@ -11,6 +11,8 @@ /// Chain specification parser. pub mod chainspec; +mod hl_testnet; + #[cfg(test)] mod test { use clap::Parser;