diff --git a/.ethexe.example.local.toml b/.ethexe.example.local.toml
index b48a81f72c1..3434ae0fb1e 100644
--- a/.ethexe.example.local.toml
+++ b/.ethexe.example.local.toml
@@ -184,6 +184,26 @@ block-time = 1
# (optional, default: false).
# no-rpc = false
+# Flag to enable RocksDB snapshot download RPC API.
+# (optional, default: false).
+# snapshot = false
+
+# Bearer token used by `snapshot_download`.
+# Must be provided when `snapshot = true`.
+# snapshot-token = "replace-with-strong-random-token"
+
+# Snapshot stream chunk size in bytes.
+# (optional, default: 1048576).
+# snapshot-chunk-bytes = 1048576
+
+# Snapshot artifact retention in seconds.
+# (optional, default: 600).
+# snapshot-retention-secs = 600
+
+# Maximum concurrent snapshot downloads.
+# (optional, default: 1).
+# snapshot-max-concurrent = 1
+
##########################################################################################
### Prometheus (metrics) service parameters.
diff --git a/.ethexe.example.toml b/.ethexe.example.toml
index f39a6d6f1ba..d376b961bce 100644
--- a/.ethexe.example.toml
+++ b/.ethexe.example.toml
@@ -184,6 +184,26 @@
# (optional, default: false).
# no-rpc = false
+# Flag to enable RocksDB snapshot download RPC API.
+# (optional, default: false).
+# snapshot = false
+
+# Bearer token used by `snapshot_download`.
+# Must be provided when `snapshot = true`.
+# snapshot-token = "replace-with-strong-random-token"
+
+# Snapshot stream chunk size in bytes.
+# (optional, default: 1048576).
+# snapshot-chunk-bytes = 1048576
+
+# Snapshot artifact retention in seconds.
+# (optional, default: 600).
+# snapshot-retention-secs = 600
+
+# Maximum concurrent snapshot downloads.
+# (optional, default: 1).
+# snapshot-max-concurrent = 1
+
##########################################################################################
### Prometheus (metrics) service parameters.
diff --git a/Cargo.lock b/Cargo.lock
index 10d793331b9..c117aa19dd3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -135,9 +135,9 @@ dependencies = [
[[package]]
name = "alloy-chains"
-version = "0.2.30"
+version = "0.2.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39"
+checksum = "3842d8c52fcd3378039f4703dba392dca8b546b1c8ed6183048f8dab95b2be78"
dependencies = [
"alloy-primitives",
"num_enum 0.7.5",
@@ -421,9 +421,9 @@ dependencies = [
[[package]]
name = "alloy-primitives"
-version = "1.5.7"
+version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e"
+checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33"
dependencies = [
"alloy-rlp",
"bytes",
@@ -444,6 +444,7 @@ dependencies = [
"rustc-hash 2.1.1",
"serde",
"sha3",
+ "tiny-keccak",
]
[[package]]
@@ -5354,6 +5355,7 @@ name = "ethexe-rpc"
version = "1.10.0"
dependencies = [
"anyhow",
+ "clap 4.5.54",
"dashmap 5.5.3",
"ethexe-common",
"ethexe-db",
@@ -5363,6 +5365,7 @@ dependencies = [
"gear-core",
"gear-workspace-hack",
"gprimitives",
+ "hex",
"hyper 1.8.1",
"jsonrpsee",
"metrics",
@@ -5370,12 +5373,16 @@ dependencies = [
"ntest",
"parity-scale-codec",
"serde",
+ "sha2 0.10.9",
"sp-core",
+ "tar",
+ "tempfile",
"tokio",
"tower 0.4.13",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
+ "zstd 0.13.3",
]
[[package]]
@@ -7412,13 +7419,12 @@ dependencies = [
"indexmap 2.13.0",
"ipnet",
"itertools 0.10.5",
- "itertools 0.11.0",
+ "itertools 0.13.0",
"js-sys",
"jsonrpsee",
"jsonrpsee-client-transport",
"jsonrpsee-core",
"k256",
- "keccak",
"libc",
"libp2p 0.52.4",
"libp2p-identity",
@@ -7541,7 +7547,6 @@ dependencies = [
"signature",
"slice-group-by",
"smallvec",
- "socket2 0.4.10",
"soketto",
"sp-allocator",
"sp-api",
@@ -7629,6 +7634,7 @@ dependencies = [
"wasmtime-runtime",
"winnow",
"zeroize",
+ "zstd-sys",
]
[[package]]
@@ -9413,9 +9419,9 @@ dependencies = [
[[package]]
name = "keccak-asm"
-version = "0.1.5"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f"
+checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6"
dependencies = [
"digest 0.10.7",
"sha3-asm",
@@ -16859,9 +16865,9 @@ dependencies = [
[[package]]
name = "sha3-asm"
-version = "0.1.5"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a"
+checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46"
dependencies = [
"cc",
"cfg-if",
@@ -21579,6 +21585,15 @@ dependencies = [
"zstd-safe 6.0.6",
]
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe 7.2.4",
+]
+
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
@@ -21599,6 +21614,15 @@ dependencies = [
"zstd-sys",
]
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
diff --git a/ethexe/cli/src/params/mod.rs b/ethexe/cli/src/params/mod.rs
index 2f2e7ea444a..7682c07d26b 100644
--- a/ethexe/cli/src/params/mod.rs
+++ b/ethexe/cli/src/params/mod.rs
@@ -93,7 +93,10 @@ impl Params {
.transpose()
})
.transpose()?;
- let rpc = rpc.and_then(|p| p.into_config(&node));
+ let rpc = match rpc {
+ Some(params) => params.into_config(&node)?,
+ None => None,
+ };
let prometheus = prometheus.and_then(|p| p.into_config());
Ok(Config {
node,
diff --git a/ethexe/cli/src/params/rpc.rs b/ethexe/cli/src/params/rpc.rs
index a2c36978d4e..17b088e0336 100644
--- a/ethexe/cli/src/params/rpc.rs
+++ b/ethexe/cli/src/params/rpc.rs
@@ -17,8 +17,9 @@
// along with this program. If not, see .
use super::MergeParams;
+use anyhow::{Result, anyhow};
use clap::Parser;
-use ethexe_rpc::{DEFAULT_BLOCK_GAS_LIMIT_MULTIPLIER, RpcConfig};
+use ethexe_rpc::{DEFAULT_BLOCK_GAS_LIMIT_MULTIPLIER, RpcConfig, SnapshotRpcConfig};
use ethexe_service::config::NodeConfig;
use serde::Deserialize;
use std::{
@@ -52,6 +53,31 @@ pub struct RpcParams {
#[arg(long)]
pub gas_limit_multiplier: Option,
+
+ /// Flag to enable snapshot download RPC API.
+ #[arg(long)]
+ #[serde(default)]
+ pub snapshot: bool,
+
+ /// Bearer token for snapshot download RPC authorization.
+ #[arg(long)]
+ #[serde(rename = "snapshot-token")]
+ pub snapshot_token: Option,
+
+ /// Snapshot chunk size in bytes.
+ #[arg(long)]
+ #[serde(rename = "snapshot-chunk-bytes")]
+ pub snapshot_chunk_bytes: Option,
+
+ /// Snapshot retention period in seconds.
+ #[arg(long)]
+ #[serde(rename = "snapshot-retention-secs")]
+ pub snapshot_retention_secs: Option,
+
+ /// Max amount of concurrent snapshot downloads.
+ #[arg(long)]
+ #[serde(rename = "snapshot-max-concurrent")]
+ pub snapshot_max_concurrent: Option,
}
impl RpcParams {
@@ -59,9 +85,9 @@ impl RpcParams {
pub const DEFAULT_RPC_PORT: u16 = 9944;
/// Convert self into a proper `RpcConfig` object, if RPC service is enabled.
- pub fn into_config(self, node_config: &NodeConfig) -> Option {
+ pub fn into_config(self, node_config: &NodeConfig) -> Result