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: add cli download to download public node snapshots #13598

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions bin/reth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ futures.workspace = true

# misc
aquamarine.workspace = true
eyre.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
backon.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
similar-asserts.workspace = true

[dev-dependencies]
Expand Down
8 changes: 7 additions & 1 deletion bin/reth/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use clap::{value_parser, Parser, Subcommand};
use reth_chainspec::ChainSpec;
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_commands::{
config_cmd, db, dump_genesis, import, init_cmd, init_state,
config_cmd, db, download, dump_genesis, import, init_cmd, init_state,
node::{self, NoArgs},
p2p, prune, recover, stage,
};
Expand Down Expand Up @@ -169,6 +169,9 @@ impl<C: ChainSpecParser<ChainSpec = ChainSpec>, Ext: clap::Args + fmt::Debug> Cl
Commands::Db(command) => {
runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
}
Commands::Download(command) => {
runner.run_blocking_until_ctrl_c(command.execute::<EthereumNode>())
}
Commands::Stage(command) => runner.run_command_until_exit(|ctx| {
command.execute::<EthereumNode, _, _, EthNetworkPrimitives>(
ctx,
Expand Down Expand Up @@ -221,6 +224,9 @@ pub enum Commands<C: ChainSpecParser, Ext: clap::Args + fmt::Debug> {
/// Database debugging utilities
#[command(name = "db")]
Db(db::Command<C>),
/// Downloads public node snapshots
#[command(name = "download")]
Download(download::Command<C>),
/// Manipulate individual stages.
#[command(name = "stage")]
Stage(stage::Command<C>),
Expand Down
9 changes: 5 additions & 4 deletions crates/cli/commands/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,19 @@ tokio.workspace = true

# misc
ahash = "0.8"
human_bytes = "0.4.1"
eyre.workspace = true
backon.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
human_bytes = "0.4.1"
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
tracing.workspace = true
backon.workspace = true
secp256k1 = { workspace = true, features = [
"global-context",
"rand-std",
"recovery",
] }
tracing.workspace = true

# io
fdlimit.workspace = true
Expand Down
83 changes: 83 additions & 0 deletions crates/cli/commands/src/download/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::{io::Write, path::Path, sync::Arc};
use tokio::{fs, io::AsyncWriteExt};

use clap::Parser;
use eyre::Result;
use reqwest::Client;
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_node_core::args::DatadirArgs;

const SNAPSHOT_FILE: &str = "snapshot.tar.lz4";

/// `reth download` command
#[derive(Debug, Parser, Clone)]
pub struct Command<C: ChainSpecParser> {
/// The chain this node is running.
///
/// Possible values are either a built-in chain or the path to a chain specification file.
#[arg(
long,
value_name = "CHAIN_OR_PATH",
long_help = C::help_message(),
default_value = C::SUPPORTED_CHAINS[0],
value_parser = C::parser()
)]
chain: Arc<C::ChainSpec>,

/// Path where will be store the snapshot
#[command(flatten)]
datadir: DatadirArgs,

/// Custom URL to download the snapshot from
/// TODO: check if we can add public snapshots urls by default
#[arg(long, short, required = true)]
url: String,
}

impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
/// Execute the download command
pub async fn execute<N>(self) -> Result<()> {
let data_dir = self.datadir.resolve_datadir(self.chain.chain());
let snapshot_path = data_dir.data_dir().join(SNAPSHOT_FILE);
fs::create_dir_all(&data_dir).await?;

println!("Starting snapshot download for chain: {:?}", self.chain);
println!("Target directory: {:?}", data_dir);
println!("Source URL: {}", self.url);

download_snapshot(&self.url, &snapshot_path).await?;

println!("Snapshot downloaded successfully to {:?}", snapshot_path);
//TODO: add decompression step
println!(
"Please extract the snapshot using: tar --use-compress-program=lz4 -xf {:?}",
snapshot_path
);

Ok(())
}
}

async fn download_snapshot(url: &str, target_path: &Path) -> Result<()> {
let client = Client::new();
let mut response = client.get(url).send().await?.error_for_status()?;

let total_size = response.content_length().unwrap_or(0);
let mut file = fs::File::create(&target_path).await?;
let mut downloaded = 0u64;

while let Some(chunk) = response.chunk().await? {
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;

if total_size > 0 {
let progress = (downloaded as f64 / total_size as f64) * 100.0;
print!("\rDownloading... {:.1}%", progress);
std::io::stdout().flush()?;
}
}
println!("\nDownload complete!");

Ok(())
}
1 change: 1 addition & 0 deletions crates/cli/commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
pub mod common;
pub mod config_cmd;
pub mod db;
pub mod download;
pub mod dump_genesis;
pub mod import;
pub mod init_cmd;
Expand Down
Loading