MENU
IDENTITY
Identity ▼
ACCOUNT
Account ▼
STATE & PROOFS
State & Proofs ▼
CRYPTOGRAPHY
Cryptography ▼
RESOURCES

SyncWave

Bootstrap a fresh node in seconds — download a verified state snapshot at a checkpoint block, prove it locally, resume from there.

Overview

SyncWave is Sahyadri's fast-bootstrap primitive. A brand-new node does not need to replay the chain from genesis. It downloads a snapshot of account state at a recent checkpoint block, verifies the snapshot cryptographically against that block's header, loads it into local stores, and resumes normal block sync from the checkpoint forward.

The snapshot is untrusted data. Any peer can serve it. The receiving node rebuilds the SMT root from the transmitted nodes and compares it against the checkpoint header's account_commitment. If they match, the snapshot is genuine. If they don't, the peer is lying — drop it and try another.

One-line summary: SyncWave collapses fresh-node bootstrap from hours of IBD to seconds of verified download — no trust in any peer, no replay of history.

The Bootstrap Problem

Every blockchain faces the same cold-start cost. A new node must reconstruct the current state — every balance, every account — from scratch. Two naive approaches exist, and both fail:

ApproachCostWhy it fails
Replay from genesisHours to daysEvery block must be re-executed. On a growing chain, this window never closes.
Trust a peer's stateSecondsThe peer can lie about balances, omit accounts, or serve a stale fork. No verification.

SyncWave offers a third path: download untrusted state, verify it cryptographically against a header. The peer's role collapses to that of a dumb pipe. The verification is local, cheap, and requires only the checkpoint header the client already trusts.

This is the same trust model as state proofs, scaled up. A state proof proves one account against a root. A SyncWave snapshot proves the entire tree against the same root.

How It Works

1. Peer Discovery & Metadata
The fresh node connects to any peer, asks for the highest checkpoint. The peer returns SyncWaveMetadata — a few hundred bytes: block hash, height, SMT root, total node count, chunk count.
2. Chunked Download
The snapshot is streamed as N chunks of chunk_size entries each. Each chunk carries SMT nodes and account states. The client downloads them sequentially, any peer, any order.
3. Local Verification
The client reassembles the snapshot and runs verify(). Every SMT node is content-addressed — its hash must equal its declared key. The walk from account_root must resolve completely, without dangling references. If it does, the snapshot equals the header's committed root.
4. Atomic Load
Verified nodes and states are inserted into local stores via content-addressed writes. The operation is idempotent — re-running it produces the same stores. A crash mid-load leaves a partial but self-consistent tree; the next run completes it.
5. Resume Normal Sync
From the checkpoint forward, the node runs ordinary IBD. Because the tip is close, this takes seconds to minutes, not hours. The node is now fully online and can serve other peers.

The Snapshot Structure

A snapshot is a self-describing bundle: metadata plus the entire reachable state of the SMT at the checkpoint block.

pub struct SyncWaveSnapshot {
    pub metadata:       SyncWaveMetadata,
    pub smt_nodes:      Vec<(H256, SyncWaveNode)>,
    pub account_states: Vec<(Hash, SyncWaveState)>,
}

pub struct SyncWaveMetadata {
    pub block_hash:      Hash,    // checkpoint block
    pub block_height:    u64,     // blue score
    pub account_root:    Hash,    // == header.account_commitment
    pub total_smt_nodes: u64,
    pub total_accounts:  u64,
    pub chunk_size:      u32,     // entries per chunk
    pub total_chunks:    u32,
}

pub enum SyncWaveNode {
    Leaf   { key: H256, value: H256 },
    Branch { left: H256, right: H256 },
}

pub struct SyncWaveState {
    pub balance:        u64,
    pub recent_flashes: Vec<SyncWaveFlashEntry>,
}

Every SMT node is stored under its own content-address. The address of a leaf is SHA3("SAHYADRI_SMT_LEAF_V1" || key || value); a branch is SHA3("SAHYADRI_SMT_BRANCH_V1" || left || right). This domain separation is what makes the tree canonical and the snapshot self-verifying.

Account states are stored under their own content-hash — the value of the SMT leaf. The state hash is what a state proof resolves to; the snapshot just carries the states themselves.

Metadata-First Protocol

The client fetches metadata before committing to the download. This is deliberate: a snapshot can be hundreds of megabytes. Metadata tells the client how big the transfer will be, how many chunks, and — critically — the block hash whose header must be verified.

pub struct RpcSyncWaveMetadata {
    pub block_hash:      RpcHash,
    pub block_height:    u64,
    pub account_root:    RpcHash,
    pub total_smt_nodes: u64,
    pub total_accounts:  u64,
    pub chunk_size:      u32,
    pub total_chunks:    u32,
}

The client verifies the checkpoint header first: PoW is valid, the block is on the selected chain, the account commitment in the header equals the metadata's account_root. Only then does it begin downloading chunks. A malicious peer cannot lure the client into wasting bandwidth on a fake snapshot for a block the client would never accept.

Chunk Wire Format

Chunks are bincode-encoded for wire efficiency. The wire type is deliberately minimal — no re-encoding, no hex conversion. The bytes can be sliced, streamed, and reassembled without inspecting structure.

pub struct SyncWaveChunkWire {
    pub chunk_index:    u32,
    pub total_chunks:   u32,
    pub smt_nodes:      Vec<(H256, SyncWaveNode)>,
    pub account_states: Vec<(Hash, SyncWaveState)>,
}

The snapshot's full entry list is treated as a single sequence: SMT nodes first, then account states. Chunks are slices of this sequence. The client concatenates chunks in order and reconstructs the original two vectors.

The total_chunks field is repeated in every chunk as a sanity check. A client that receives a chunk claiming a different total than the metadata is talking to a peer with a different snapshot; it drops the connection and tries another.

Server-Side Export

On the serving node, SyncWaveExporter::export produces a snapshot for a given block. The work is a single DFS over the SMT, followed by a batched read of every reachable state.

pub fn export(&self, block_hash: Hash) -> SyncWaveResult<SyncWaveSnapshot> {
    let header = self.headers_store.get_header(block_hash)?;
    let root = hash_to_h256(header.account_commitment);

    // 1. Walk the SMT from the committed root, collect every node.
    let base = DbSmtNodeStoreBase::new(&self.smt_nodes_store);
    let mut nodes: HashMap<H256, SyncWaveNode> = HashMap::new();
    self.collect_smt_nodes(&base, root, &mut nodes)?;

    // 2. For each leaf, fetch its AccountState from the state store.
    let mut states: Vec<(Hash, SyncWaveState)> = Vec::new();
    for node in nodes.values() {
        if let SyncWaveNode::Leaf { value, .. } = node {
            let state_hash = h256_to_hash(*value);
            let state = self.account_states_store.get(state_hash)?;
            states.push((state_hash, to_sync_state(&state)));
        }
    }

    let total_smt_nodes = nodes.len() as u64;
    let total_accounts = states.len() as u64;
    let chunk_size = DEFAULT_CHUNK_SIZE;
    let total_chunks = ((total_smt_nodes + total_accounts) as u32)
        .div_ceil(chunk_size);

    Ok(SyncWaveSnapshot {
        metadata: SyncWaveMetadata {
            block_hash,
            block_height: header.blue_score,
            account_root: header.account_commitment,
            total_smt_nodes,
            total_accounts,
            chunk_size,
            total_chunks,
        },
        smt_nodes: nodes.into_iter().collect(),
        account_states: states,
    })
}

The export is read-only. It touches no persistent state, mutates nothing. Any archival node can serve snapshots for any historical block whose header it still has — the SMT is content-addressed, so old roots remain queryable as long as the nodes are retained.

The DFS memoizes visited hashes so shared subtrees are visited only once. In practice the effective depth is under 64, so even a tree with millions of accounts walks in milliseconds.

Client-Side Verification

Verification is entirely local. It makes no network calls. It requires only the assembled snapshot and the trusted root that came from the checkpoint header.

pub fn verify(snapshot: &SyncWaveSnapshot) -> SyncWaveResult<()> {
    let root = hash_to_h256(snapshot.metadata.account_root);

    // 1. Every node's content-address must equal its declared key.
    let mut index: HashMap<H256, &SyncWaveNode> = HashMap::new();
    for (h, node) in &snapshot.smt_nodes {
        if node.hash() != *h {
            return Err(SyncWaveError::NodeHashMismatch(*h));
        }
        index.insert(*h, node);
    }

    // 2. Walk from the root. Every reference must resolve.
    let mut visited: HashMap<H256, ()> = HashMap::new();
    Self::walk_and_check(root, &index, &mut visited)?;

    // 3. The walk must visit exactly as many nodes as declared.
    if visited.len() as u64 != snapshot.metadata.total_smt_nodes {
        return Err(SyncWaveError::NodeCountMismatch {
            declared: snapshot.metadata.total_smt_nodes,
            actual: visited.len() as u64,
        });
    }

    // 4. Every state's content-hash must match its key.
    for (state_hash, state) in &snapshot.account_states {
        if from_sync_state(state).content_hash() != *state_hash {
            return Err(SyncWaveError::StateHashMismatch(*state_hash));
        }
    }

    Ok(())
}

Each check defends against a specific attack:

If all four pass, the snapshot is the state at the checkpoint block — not a copy, not an approximation. There is exactly one tree that hashes to the committed root, and the client has reconstructed it.

Loading & Atomicity

Once verified, the snapshot is inserted into the local stores. The design goal is crash-safety: a node killed mid-load should not be left in a state that requires re-downloading the entire snapshot.

pub fn load(&self, snapshot: &SyncWaveSnapshot) -> SyncWaveResult<()> {
    // SMT nodes — content-addressed, so writes are idempotent.
    for (h, node) in &snapshot.smt_nodes {
        self.smt_nodes_store.insert_sync(*h, into_smt_node(node))?;
    }

    // Account states — content-addressed by state hash.
    for (state_hash, state) in &snapshot.account_states {
        self.account_states_store.insert_sync(*state_hash, &from_sync_state(state))?;
    }

    // The root mapping — keyed by block hash, not content-addressed.
    let mut batch = WriteBatch::default();
    self.account_roots_store.insert_batch(
        &mut batch,
        snapshot.metadata.block_hash,
        hash_to_h256(snapshot.metadata.account_root),
    )?;
    self.db.write(batch)?;

    Ok(())
}

Nodes and states are content-addressed. Writing the same (hash, value) pair twice is a no-op — the second write overwrites with identical bytes. If the node crashes halfway through, the SMT store holds a partial but self-consistent subset of the tree. Re-running the load completes it.

The root mapping is the only non-idempotent write. It is a single key-value insert, wrapped in a write batch so it either lands fully or not at all. If the checkpoint root was never persisted, the node treats its store as incomplete on restart and re-downloads.

Bootstrap Flow

1

Fetch Metadata

get_sync_wave_metadata() returns a few hundred bytes. The client now knows the checkpoint block hash, its height, and how many chunks to expect.

2

Verify the Checkpoint Header

Download the header for the checkpoint block, verify PoW and DAG ordering, extract account_commitment. This is the only trusted input. Everything after is cryptographic.

3

Download Chunks

Sequentially fetch download_sync_wave_chunk(hash, i) for i in 0..total_chunks. Chunks may come from any peer; each is self-contained.

4

Assemble & Verify

Concatenate chunks into a SyncWaveSnapshot, run verify(). Under a millisecond for typical trees. If verification fails, discard the download and retry with a different peer.

5

Atomic Load

Insert nodes and states into local stores. Content-addressed writes make this idempotent and crash-safe. The checkpoint root is persisted last, in a single write batch.

6

Sync from Checkpoint to Tip

The node now has the state at block N. It runs ordinary IBD from N to the current tip. Because the gap is small (a few minutes of blocks), this completes in seconds.

7

Node Goes Online

Full state, current tip, ready to serve RPC and P2P. Total elapsed time: seconds to low minutes, versus hours of full IBD from genesis.

RPC Endpoints

Two methods, both read-only, both available on any full node. No mining, no consensus interaction, no state mutation.

get_sync_wave_metadata(
    block_hash: Option<Hash>,   // defaults to current tip
) -> {
    block_hash:      Hash,
    block_height:    u64,
    account_root:    Hash,
    total_smt_nodes: u64,
    total_accounts:  u64,
    chunk_size:      u32,
    total_chunks:    u32,
}

download_sync_wave_chunk(
    block_hash:  Hash,
    chunk_index: u32,
) -> {
    data:         Vec<u8>,     // bincode(SyncWaveChunkWire)
    chunk_index:  u32,
    total_chunks: u32,
}

Both methods are exposed over the standard wRPC interface, alongside get_account_proof. Clients that already speak to a Sahyadri node for state proofs need no new transport.

The server caches the exported snapshot in memory between chunk requests. The first request for a given block triggers the full export; subsequent chunks for the same block reuse the cache. A request for a different block evicts the cache.

Benchmark Results

SyncWave bootstrap has been observed end-to-end on a local 2-node testnet. The test used real mining, real SMT state, and real cryptographic verification — not a mock.

Test Configuration

Measured Phases

PhaseTime
Connect to peer (gRPC handshake)~4 ms
Fetch metadata~1 ms
Download chunk (5 nodes, 2 states)~1 ms
SMT verification against checkpoint root< 1 ms
Atomic load into local stores< 1 ms
Total bootstrap time~10 ms

Verification

Node B's rebuilt SMT root equals the checkpoint header's account_commitment at height 258. If any node, any state, or any reference had been corrupted or fabricated, verification would have failed and the bootstrap would have been rejected. The successful bootstrap is therefore cryptographic evidence that the state was transferred intact — not merely that the transfer completed.

Scaling Projection

The test state was minimal (2 accounts). The transfer cost scales with state size; the verification cost stays O(depth) regardless. For production-scale networks:

Network ScaleAccountsEst. PayloadEst. Bootstrap
Devnet10~1 KB< 50 ms
Testnet10,000~1 MB< 500 ms
Production1,000,000~100 MB~10 sec
Large-scale10,000,000~1 GB~100 sec

Estimates assume 10 MB/s consumer download (the dominant cost). Local verify and load remain sub-second because they are dominated by disk I/O on content-addressed writes, not by tree traversal. Full IBD for the same networks would take hours to days.

Note on scale: These projections assume linear growth of SMT node count with account count and a constant 32-byte sibling size. Both are properties of the SMT itself, not of the network. Independent benchmarking at production scale is planned and will be published separately.

CLI — Fast Bootstrap

Any fresh node can opt into SyncWave bootstrap from a peer via two flags. The peer does not need to be special — every full node serves snapshots.

sahyadrid     --sync-wave grpc://seed-node:port     --sync-wave-checkpoint 0xabc123...   # optional

# Or with wrpc (JSON):
sahyadrid --sync-wave wrpc://seed-node:port
FlagPurposeDefault
--sync-wave <PEER_URL>Peer to bootstrap from. Format: grpc://host:port or wrpc://host:port. If unset, the node runs ordinary IBD from genesis.off
--sync-wave-checkpoint <HASH>Checkpoint block hash to load. If omitted, the peer's current tip is used.peer tip

On startup, the node runs the bootstrap before opening its RPC and P2P ports. A node launched with these flags is fully synced to the checkpoint by the time it appears on the network. If the bootstrap fails — bad peer, unreachable, verification mismatch — the process exits with a clear error rather than starting in a partial state.

First-run only: SyncWave is intended for the first startup on an empty data directory. Subsequent restarts use the local stores and do not re-download the snapshot. To force a re-sync, wipe the data directory or point --appdir at a fresh path.

Security Analysis

A SyncWave snapshot is only as trustworthy as the header it is verified against. Every attack surface is explicit:

ThreatMitigation
Forged snapshot (fabricated state)The SMT root is committed in the checkpoint header, which is PoW-protected. A fabricated snapshot would need to hash to the same root — finding such a preimage is beyond any classical or quantum attack on SHA3-256.
Truncated snapshot (missing subtrees)The walk_and_check step rejects any snapshot with a dangling reference. A partial tree cannot have the same root as the full tree.
Inflated metadata (DoS via padding)The declared node count must equal the walk's visit count. An attacker cannot increase the client's workload without failing verification.
Stale snapshot (valid root, old block)The client binds the download to a specific block hash it has verified. A snapshot for an earlier block is simply ignored — the client already knows the tip.
Malicious peer mid-downloadChunks are self-contained. A bad chunk is caught at reassembly. The client rotates peers freely; no correctness loss.
Replay across forksEach block has a unique root. A snapshot valid for fork A's checkpoint block is invalid for fork B's. No cross-fork replay possible.
Reorg after checkpointThe checkpoint is chosen deep enough that the selected chain will not reorg past it. If it does, the node falls back to ordinary IBD from an earlier point.
Data corruption in transitContent-addressed nodes detect any corruption: the hash of the corrupt node does not match its key. The client drops the chunk and retries.

There is no trusted third party in the SyncWave path. The verifier's only assumption is that the checkpoint header was produced by honest consensus — the same assumption every full node makes. SyncWave adds zero new trust assumptions.

Comparison with Full IBD

PropertyFull IBDSyncWave
Downloaded dataAll blocks + transactions since genesisOne SMT snapshot + headers from checkpoint forward
Bootstrap time (1 BPS chain)Hours to daysSeconds to minutes
Verification workRe-execute every transactionOne hash comparison per node
Disk required for full historical replayFull chain archiveState + headers only
Trust in peersZeroZero
Archival peers requiredAny full nodeAny full node with retained state at the checkpoint

SyncWave does not replace full IBD — it makes full IBD optional for operators who do not need to serve historical blocks. Exchanges, wallets, and validating full nodes that only care about current state can use SyncWave. Block explorers and historical indexers continue to run archival nodes.

Use Cases

New Validating Nodes
A new validator spins up on a fresh machine and joins the network in under a minute. It verifies the snapshot, syncs the tip, and starts participating in consensus. No multi-day IBD window.
Exchange Hot Nodes
An exchange runs a full node to credit deposits. It does not need historical state. SyncWave brings a new node online in seconds, with the same trust guarantees as a genesis-synced node.
RPC Providers
A managed RPC provider can autoscale: spin up a new node per demand spike, bootstrap it from SyncWave in seconds, shut it down when idle. No persistent archive required per replica.
Testnet Bringup
Internal testnets can bootstrap new participants in seconds. Developers iterate faster when a fresh node is one command away.
Disaster Recovery
A node that lost its disk restores current state from any peer in seconds. No backup of the full historical archive required — only the checkpoint header, which is reconstructed from peers.

Running an Archival Node

Archival nodes retain the full history of the chain and serve SyncWave snapshots for any historical checkpoint. They are the backbone of fast bootstrap — without them, fresh nodes must replay from genesis.

sahyadrid --archival
ResourceRequirement
StorageGrows with chain (100 GB → TBs over years)
RAM8 GB minimum, 16 GB recommended
BandwidthHigh — serves all peers
PermissionsNone — anyone can run one

Any operator can run an archival node. The network benefits from geographic diversity — three or more archival nodes on different continents provide the resilience the network needs.

Limitations

Being honest about what SyncWave does not do:

None of these are blockers. They are the boundary of what the primitive promises. Everything inside that boundary is cryptographically enforced.

Summary

SyncWave makes fresh-node bootstrap fast without weakening the trust model. A node downloads an untrusted snapshot, verifies it locally against a header it independently trusts, and resumes sync from a checkpoint. The snapshot is not trusted — it is proven. The serving peer is not trusted — it is a pipe. The savings are measured in hours.

The primitive builds on the same machinery that powers state proofs: a single SMT root committed in every block header, content-addressed nodes, and domain-separated hashing. No new cryptography, no new trust assumptions, no new consensus primitive. Just a way to move the state that already exists.

A fresh node, a few hundred sibling hashes, a hash map, one SHA3 walk. That is what stands between an attacker and a forged state bootstrap. It is fast enough to feel like "syncing in seconds" and strong enough to match the guarantees of a node that replayed every block since genesis.