use clap::ArgMatches; use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{ BeaconNodeHttpClient, Error, SensitiveUrl, Timeouts, types::{BlockId, ChainSpec, ForkName, PublishBlockRequest, SignedBlockContents}, }; use eth2_network_config::Eth2NetworkConfig; use ssz::Encode; use std::fs; use std::fs::File; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; use types::EthSpec; const HTTP_TIMEOUT: Duration = Duration::from_secs(3600); const DEFAULT_CACHE_DIR: &str = "./cache"; pub fn run( env: Environment, network_config: Eth2NetworkConfig, matches: &ArgMatches, ) -> Result<(), String> { let executor = env.core_context().executor; executor .handle() .ok_or("shutdown in progress")? .block_on(async move { run_async::(network_config, matches).await }) } pub async fn run_async( network_config: Eth2NetworkConfig, matches: &ArgMatches, ) -> Result<(), String> { let spec = &network_config.chain_spec::()?; let source_url: SensitiveUrl = parse_required(matches, "source-url")?; let target_url: SensitiveUrl = parse_required(matches, "target-url")?; let start_block: BlockId = parse_required(matches, "start-block")?; let maybe_common_ancestor_block: Option = parse_optional(matches, "known–common-ancestor")?; let cache_dir_path: PathBuf = parse_optional(matches, "block-cache-dir")?.unwrap_or(DEFAULT_CACHE_DIR.into()); let source = BeaconNodeHttpClient::new(source_url, Timeouts::set_all(HTTP_TIMEOUT)); let target = BeaconNodeHttpClient::new(target_url, Timeouts::set_all(HTTP_TIMEOUT)); if !cache_dir_path.exists() { fs::create_dir_all(&cache_dir_path) .map_err(|e| format!("Unable to create block cache dir: {:?}", e))?; } // 1. Download blocks back from head, looking for common ancestor. let mut blocks = vec![]; let mut next_block_id = start_block; loop { println!("downloading {next_block_id:?}"); let publish_block_req = get_block_from_source::(&source, next_block_id, spec, &cache_dir_path).await; let block = publish_block_req.signed_block(); next_block_id = BlockId::Root(block.parent_root()); blocks.push((block.slot(), publish_block_req)); if let Some(ref common_ancestor_block) = maybe_common_ancestor_block && common_ancestor_block == &next_block_id { println!("reached known common ancestor: {next_block_id:?}"); break; } let block_exists_in_target = target .get_beacon_blocks_ssz::(next_block_id, spec) .await .unwrap() .is_some(); if block_exists_in_target { println!("common ancestor found: {next_block_id:?}"); break; } } // 2. Apply blocks to target. for (slot, block) in blocks.iter().rev() { println!("posting block at slot {slot}"); if let Err(e) = target.post_beacon_blocks(block).await { if let Error::ServerMessage(ref e) = e && e.code == 202 { println!("duplicate block detected while posting block at slot {slot}"); continue; } return Err(format!("error posting {slot}: {e:?}")); } else { println!("success"); } } println!("SYNCED!!!!"); Ok(()) } async fn get_block_from_source( source: &BeaconNodeHttpClient, block_id: BlockId, spec: &ChainSpec, cache_dir_path: &Path, ) -> PublishBlockRequest { let mut cache_path = cache_dir_path.join(format!("block_{block_id}")); if cache_path.exists() { let mut f = File::open(&cache_path).unwrap(); let mut bytes = vec![]; f.read_to_end(&mut bytes).unwrap(); PublishBlockRequest::from_ssz_bytes(&bytes, ForkName::Deneb).unwrap() } else { let block_from_source = source .get_beacon_blocks_ssz::(block_id, spec) .await .unwrap() .unwrap(); let blobs_from_source = source .get_blob_sidecars::(block_id, None, spec) .await .unwrap() .unwrap() .into_data(); let (kzg_proofs, blobs): (Vec<_>, Vec<_>) = blobs_from_source .iter() .map(|sidecar| (sidecar.kzg_proof, sidecar.blob.clone())) .unzip(); let block_root = block_from_source.canonical_root(); let block_contents = SignedBlockContents { signed_block: Arc::new(block_from_source), kzg_proofs: kzg_proofs.try_into().unwrap(), blobs: blobs.try_into().unwrap(), }; let publish_block_req = PublishBlockRequest::BlockContents(block_contents); cache_path = cache_dir_path.join(format!("block_{block_root:?}")); let mut f = File::create(&cache_path).unwrap(); f.write_all(&publish_block_req.as_ssz_bytes()).unwrap(); publish_block_req } }