Skip to content

snapshot api added#545

Merged
pepoviola merged 7 commits into
mainfrom
mku-snapshot-api-dev
May 29, 2026
Merged

snapshot api added#545
pepoviola merged 7 commits into
mainfrom
mku-snapshot-api-dev

Conversation

@michalkucharczyk

@michalkucharczyk michalkucharczyk commented May 27, 2026

Copy link
Copy Markdown
Contributor

Implementation of proposal from #541 (also #542).

Tests that build DB snapshot artifacts (cumulus full_node_warp_sync, smoldot smoke/bulletin) each hand-roll the same pause → tar → checksum → bundle dance. This moves it into the SDK.

What's new

  • NetworkNode::snapshot_db(out_path) — tars a node's DB into a .tgz (data/, plus relay-data/ for cumulus collators). Drops keystore/ and network/ so the archive can be loaded on several sibling nodes without key/peer-id clashes.
  • Network::pause() / resume() — SIGSTOP/SIGCONT all nodes in parallel, so the DB on disk is consistent while you snapshot.
  • snapshot::BundleBuilder — packs per-node archives + a JSON user_data blob into one bundle.tar.gz with a manifest.json (sha256 + size per archive). build() won't compile until you've added at least one archive.
  • with_optional_default_db_snapshot(Option<…>) on the relay/parachain builders (and with_optional_db_snapshot per node) — so one network builder works for both the fresh and the from-snapshot case without branching.

Example

// Produce: snapshot a relay validator + a collator, bundle them.
network.pause().await?;
let relay = network.get_node("alice")?.snapshot_db("relay.tgz").await?;
let para  = network.get_node("collator")?.snapshot_db("para.tgz").await?;
network.resume().await?;

let bundle = BundleBuilder::new()
    .add(relay)
    .add(para)
    .user_data(serde_json::json!({ "para_best_block": 42 }))
    .build("bundle.tar.gz")?;          // bundle.path / .sha256 / .size

////////////////////////////////////////////////////////////////////////////////

// Consume: same builder, snapshot optional.
fn network(snapshot: Option<&Path>) -> NetworkConfig {
    NetworkConfigBuilder::new()
        .with_relaychain(|r| {
            r.with_chain("rococo-local")
                .with_default_command("polkadot")
                .with_optional_default_db_snapshot(snapshot)   // None = fresh, Some = resume
                .with_validator(|n| n.with_name("alice"))
        })
        /* … */
        .build().unwrap()
}

Tests

  • Unit tests in crates/sdk/src/snapshot.rs: bundle build → unpack → manifest round-trip (no network, runs in CI).
  • crates/configuration: tests for the three with_optional_*db_snapshot methods.
  • crates/sdk/tests/snapshot_roundtrip.rs (#[ignore], needs polkadot binaries): snapshot a live network, bundle it, re-spawn from the snapshot, and check both chains advance and finalize past the snapshot height.

Notes

  • snapshot_db reads the node's local base dir, so it's native-provider only.
  • Caller must pause the node first — snapshoting a running node risks a torn RocksDB state.
  • A graceful terminate() (instead of pause) before snapshotting is left for a follow-up.
  • Open question if SIGSTOP allows for safe db copy. So far it worked well.

&self,
out_path: impl AsRef<Path>,
) -> Result<NodeSnapshot, anyhow::Error> {
let out_path = out_path.as_ref().to_path_buf();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should be in the native provider and use inner from here to call it. We can let k8s/docker as unimplemented for now (but is also possible to implement).

Thx!

}
}

fn build_bundle(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nit, since we provide a nice way to generate the bundle maybe we can also include the untar_bundle fn here to not allow users to use it directly and not need to re-implement.
wdyt?

@pepoviola

Copy link
Copy Markdown
Collaborator

Awesome job @michalkucharczyk !! I added a couple of comments, if you are agree I can make those changes and also wire your test to run in CI.

Thx!

@michalkucharczyk

Copy link
Copy Markdown
Contributor Author

Awesome job @michalkucharczyk !! I added a couple of comments, if you are agree I can make those changes and also wire your test to run in CI.

Thx!

Yes, feel free to take it from here. But I can also implement your requests. Whatever works for you :)

@skunert skunert left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Comment thread crates/orchestrator/src/network/node.rs Outdated
));
}

let bytes = tokio::fs::read(&out_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not read t he entire file here and hash it, but do this iteratively to save some memory.

@pepoviola

Copy link
Copy Markdown
Collaborator

ping @michalkucharczyk, I applied the changes and wire the test to run in ci. If you are ok, I will merge it and draft a new release.
Thx!

Comment thread crates/provider/src/native/node.rs Outdated
));
}

let mut file = File::open(&out_path).unwrap();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut file = File::open(&out_path).unwrap();
let mut file = File::open(&out_path).map_err(|err| ProviderError::SnapshotDb(self.name().into(), anyhow!(err)))?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, let me remove the unwrap 🤦‍♂️

Comment thread crates/sdk/src/snapshot.rs Outdated
let bytes = std::fs::read(&out_path)
.with_context(|| format!("reading produced bundle {}", out_path.display()))?;
let size = bytes.len() as u64;
let sha256 = hex::encode(sha2::Sha256::digest(&bytes));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream could be used here too (not reading entire file to memory).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I will apply the same logic that in the node

Comment thread crates/sdk/tests/snapshot_roundtrip.rs Outdated
Ok(())
}

fn untar_bundle(bundle_path: &Path, out_dir: &Path) -> anyhow::Result<()> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe this could be used in tests?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean moving to the snapshot module?

@michalkucharczyk

Copy link
Copy Markdown
Contributor Author

Nit: maybe we could keep NetworkNode::base_dir() ? (e.g. for inspection during test authoring?)

size,
} = self.inner.snapshot_db(is_cumulus_based).await?;

// now we need to _move_ the inner file to the out_path

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It acutally copies the file. Maybe this could be avoided by tzg'ing the file to the destination location? Or moving instead copying?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, could be an optimization for native but the idea here is that this method can be used in all the supported providers, so in docker/k8s we need to get the file from the pod.

Thx!

@pepoviola
pepoviola merged commit c05fea8 into main May 29, 2026
13 checks passed
@pepoviola
pepoviola deleted the mku-snapshot-api-dev branch May 29, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants