Skip to content

Commit 6cb3334

Browse files
committed
feat(sync): implement execution layer snap sync
1 parent 963830e commit 6cb3334

37 files changed

Lines changed: 7592 additions & 9 deletions

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ members = [
1010
"crates/mempool",
1111
"crates/rpc",
1212
"crates/metrics",
13+
"crates/sync",
1314
]
1415
resolver = "2"
1516

crates/execution/src/engine.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,14 @@ impl<P: Provider + Clone> ExecutionEngine<P> {
295295
self.database.provider()
296296
}
297297

298+
/// Get the current block number.
299+
///
300+
/// Returns the block number of the most recently executed block, or 0 if
301+
/// no blocks have been executed yet.
302+
pub fn current_block_number(&self) -> u64 {
303+
self.current_block
304+
}
305+
298306
/// Process all transactions in a block.
299307
///
300308
/// Returns a tuple containing receipts, cumulative gas used, logs, and total fees.

crates/node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cipherbft-consensus = { path = "../consensus", features = ["malachite"] }
2121
cipherbft-rpc = { path = "../rpc" }
2222
cipherbft-mempool = { path = "../mempool" }
2323
cipherbft-metrics = { path = "../metrics" }
24+
cipherbft-sync = { path = "../sync" }
2425

2526
# Ethereum primitives (for genesis types in tests and transaction parsing)
2627
alloy-primitives = { version = "1", features = ["serde"] }

crates/node/src/config.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,38 @@ pub const DEFAULT_RPC_WS_PORT: u16 = 8546;
7777
/// Default metrics port (Prometheus standard)
7878
pub const DEFAULT_METRICS_PORT: u16 = 9100;
7979

80+
/// Default snap sync enabled setting
81+
pub const DEFAULT_SNAP_SYNC_ENABLED: bool = true;
82+
83+
/// Default minimum peers required to start sync
84+
pub const DEFAULT_MIN_SYNC_PEERS: usize = 3;
85+
86+
/// Default sync request timeout in seconds
87+
pub const DEFAULT_SYNC_TIMEOUT_SECS: u64 = 30;
88+
89+
/// Default block gap threshold to trigger snap sync (vs block-by-block)
90+
pub const DEFAULT_SNAP_SYNC_THRESHOLD: u64 = 1024;
91+
92+
/// Serde default function for snap_sync_enabled
93+
fn default_snap_sync_enabled() -> bool {
94+
DEFAULT_SNAP_SYNC_ENABLED
95+
}
96+
97+
/// Serde default function for min_sync_peers
98+
fn default_min_sync_peers() -> usize {
99+
DEFAULT_MIN_SYNC_PEERS
100+
}
101+
102+
/// Serde default function for sync_timeout
103+
fn default_sync_timeout() -> u64 {
104+
DEFAULT_SYNC_TIMEOUT_SECS
105+
}
106+
107+
/// Serde default function for snap_sync_threshold
108+
fn default_snap_sync_threshold() -> u64 {
109+
DEFAULT_SNAP_SYNC_THRESHOLD
110+
}
111+
80112
/// Serde default function for rpc_http_port
81113
fn default_rpc_http_port() -> u16 {
82114
DEFAULT_RPC_HTTP_PORT
@@ -109,6 +141,52 @@ pub struct PeerConfig {
109141
pub worker_addrs: Vec<SocketAddr>,
110142
}
111143

144+
/// Sync configuration for state synchronization
145+
///
146+
/// Controls how the node performs snap sync when joining the network
147+
/// or recovering from being behind.
148+
#[derive(Clone, Debug, Serialize, Deserialize)]
149+
pub struct SyncConfig {
150+
/// Enable snap sync for fast bootstrap.
151+
///
152+
/// When enabled, nodes that are significantly behind will use snap sync
153+
/// to download state directly rather than replaying all blocks.
154+
#[serde(default = "default_snap_sync_enabled")]
155+
pub snap_sync_enabled: bool,
156+
157+
/// Minimum peers required to start sync.
158+
///
159+
/// The node will wait until it has at least this many peers with
160+
/// consistent state before beginning snap sync.
161+
#[serde(default = "default_min_sync_peers")]
162+
pub min_sync_peers: usize,
163+
164+
/// Sync request timeout in seconds.
165+
///
166+
/// Maximum time to wait for a response to a sync request before
167+
/// retrying with a different peer.
168+
#[serde(default = "default_sync_timeout")]
169+
pub sync_timeout_secs: u64,
170+
171+
/// Block gap threshold to trigger snap sync (vs block-by-block).
172+
///
173+
/// If the node is behind by more than this many blocks, it will
174+
/// use snap sync. Otherwise, it will sync block-by-block.
175+
#[serde(default = "default_snap_sync_threshold")]
176+
pub snap_sync_threshold: u64,
177+
}
178+
179+
impl Default for SyncConfig {
180+
fn default() -> Self {
181+
Self {
182+
snap_sync_enabled: DEFAULT_SNAP_SYNC_ENABLED,
183+
min_sync_peers: DEFAULT_MIN_SYNC_PEERS,
184+
sync_timeout_secs: DEFAULT_SYNC_TIMEOUT_SECS,
185+
snap_sync_threshold: DEFAULT_SNAP_SYNC_THRESHOLD,
186+
}
187+
}
188+
}
189+
112190
/// Node configuration
113191
///
114192
/// Keys are loaded from the keyring backend specified by `keyring_backend`.
@@ -199,6 +277,13 @@ pub struct NodeConfig {
199277
/// Port for Prometheus metrics endpoint (default: 9100)
200278
#[serde(default = "default_metrics_port")]
201279
pub metrics_port: u16,
280+
281+
/// Sync configuration for state synchronization.
282+
///
283+
/// Controls snap sync behavior for fast bootstrap when joining
284+
/// the network or recovering from being behind.
285+
#[serde(default)]
286+
pub sync: SyncConfig,
202287
}
203288

204289
/// Test configuration with keypairs for local testing
@@ -253,6 +338,7 @@ impl NodeConfig {
253338
rpc_http_port: DEFAULT_RPC_HTTP_PORT + (index as u16),
254339
rpc_ws_port: DEFAULT_RPC_WS_PORT + (index as u16),
255340
metrics_port: DEFAULT_METRICS_PORT + (index as u16),
341+
sync: SyncConfig::default(),
256342
};
257343

258344
LocalTestConfig {

crates/node/src/execution_bridge.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,86 @@ impl ExecutionBridge {
506506
let execution = self.execution.read().await;
507507
Arc::clone(execution.staking_precompile())
508508
}
509+
510+
/// Get the last executed block hash.
511+
///
512+
/// Returns the hash of the most recently executed block, or B256::ZERO
513+
/// if no blocks have been executed yet.
514+
pub fn last_block_hash(&self) -> B256 {
515+
self.last_block_hash
516+
.read()
517+
.map(|guard| *guard)
518+
.unwrap_or(B256::ZERO)
519+
}
520+
521+
/// Get the current block number.
522+
///
523+
/// Returns the block number of the most recently executed block.
524+
/// This requires acquiring the execution lock.
525+
pub async fn current_block_number(&self) -> u64 {
526+
let execution = self.execution.read().await;
527+
execution.current_block_number()
528+
}
529+
530+
/// Execute a block directly from BlockInput (for sync replay).
531+
///
532+
/// This method is used during sync to replay blocks that have been
533+
/// downloaded from peers. It bypasses the Cut conversion since the
534+
/// transactions are already flattened.
535+
///
536+
/// # Arguments
537+
///
538+
/// * `input` - Block input containing ordered transactions
539+
///
540+
/// # Returns
541+
///
542+
/// Returns `BlockExecutionResult` containing execution result with properly
543+
/// computed block hash and parent hash.
544+
pub async fn execute_block_input(
545+
&self,
546+
input: BlockInput,
547+
) -> anyhow::Result<BlockExecutionResult> {
548+
let block_number = input.block_number;
549+
let timestamp = input.timestamp;
550+
let parent_hash = input.parent_hash;
551+
let transactions = input.transactions.clone();
552+
553+
let mut execution = self.execution.write().await;
554+
555+
let result = execution
556+
.execute_block(input)
557+
.map_err(|e| anyhow::anyhow!("Block execution failed: {}", e))?;
558+
559+
// Compute and store the new block hash for the next block's parent_hash
560+
let new_block_hash = compute_block_hash(
561+
block_number,
562+
timestamp,
563+
parent_hash,
564+
result.state_root,
565+
result.transactions_root,
566+
result.receipts_root,
567+
);
568+
569+
// Update the last block hash for the next execution
570+
if let Ok(mut guard) = self.last_block_hash.write() {
571+
*guard = new_block_hash;
572+
}
573+
574+
debug!(
575+
height = block_number,
576+
block_hash = %new_block_hash,
577+
parent_hash = %parent_hash,
578+
"Sync block hash updated"
579+
);
580+
581+
Ok(BlockExecutionResult {
582+
execution_result: result,
583+
block_hash: new_block_hash,
584+
parent_hash,
585+
timestamp,
586+
executed_transactions: transactions,
587+
})
588+
}
509589
}
510590

511591
/// Compute a deterministic block hash from block components.

crates/node/src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@ pub mod network;
1313
pub mod network_api;
1414
pub mod node;
1515
pub mod supervisor;
16+
pub mod sync_executor;
17+
pub mod sync_network;
18+
pub mod sync_runner;
19+
pub mod sync_server;
1620
pub mod util;
1721

1822
pub use client_config::ClientConfig;
1923
pub use config::{
20-
generate_keypair, generate_local_configs, LocalTestConfig, NodeConfig, PeerConfig,
24+
generate_keypair, generate_local_configs, LocalTestConfig, NodeConfig, PeerConfig, SyncConfig,
2125
CIPHERD_GENESIS_PATH_ENV, CIPHERD_HOME_ENV, DEFAULT_GENESIS_FILENAME, DEFAULT_HOME_DIR,
2226
DEFAULT_KEYRING_BACKEND, DEFAULT_KEYS_DIR, DEFAULT_KEY_NAME, DEFAULT_METRICS_PORT,
23-
DEFAULT_RPC_HTTP_PORT, DEFAULT_RPC_WS_PORT,
27+
DEFAULT_MIN_SYNC_PEERS, DEFAULT_RPC_HTTP_PORT, DEFAULT_RPC_WS_PORT, DEFAULT_SNAP_SYNC_ENABLED,
28+
DEFAULT_SNAP_SYNC_THRESHOLD, DEFAULT_SYNC_TIMEOUT_SECS,
2429
};
2530
pub use execution_bridge::{create_default_bridge, ExecutionBridge};
2631
pub use genesis_bootstrap::{
@@ -31,3 +36,7 @@ pub use key_cli::{execute_keys_command, KeysCommand};
3136
pub use network_api::{NodeNetworkApi, TcpNetworkApi};
3237
pub use node::Node;
3338
pub use supervisor::{NodeSupervisor, ShutdownError};
39+
pub use sync_executor::ExecutionBridgeSyncExecutor;
40+
pub use sync_network::{create_sync_adapter, wire_sync_to_network, SyncNetworkAdapter};
41+
pub use sync_runner::{create_sync_manager, run_snap_sync, should_snap_sync, SyncResult};
42+
pub use sync_server::SnapSyncServer;

crates/node/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ fn cmd_init(
560560
rpc_http_port: cipherd::DEFAULT_RPC_HTTP_PORT,
561561
rpc_ws_port: cipherd::DEFAULT_RPC_WS_PORT,
562562
metrics_port: cipherd::DEFAULT_METRICS_PORT,
563+
sync: cipherd::SyncConfig::default(),
563564
};
564565

565566
let config_path = config_dir.join("node.json");
@@ -975,6 +976,7 @@ fn cmd_testnet_init_files(
975976
rpc_http_port: cipherd::DEFAULT_RPC_HTTP_PORT + (i as u16 * 10),
976977
rpc_ws_port: cipherd::DEFAULT_RPC_WS_PORT + (i as u16 * 10),
977978
metrics_port: cipherd::DEFAULT_METRICS_PORT + (i as u16 * 10),
979+
sync: cipherd::SyncConfig::default(),
978980
};
979981

980982
let config_path = config_dir.join("node.json");

0 commit comments

Comments
 (0)