Skip to content

baked sync#1366

Closed
gilescope wants to merge 1 commit into
release/node-0.22.5from
giles-baked-sync
Closed

baked sync#1366
gilescope wants to merge 1 commit into
release/node-0.22.5from
giles-baked-sync

Conversation

@gilescope
Copy link
Copy Markdown
Contributor

@gilescope gilescope commented Apr 20, 2026

⛔ Do not merge until #1365 has landed. This branch includes every commit from #1365 plus the file-backed sync work; merging out-of-order will cause conflicts and obscure review.

Overview

Adds a pre-baked ("snapshot") cNIGHT observation source so a fresh node can skip re-querying db-sync for every Cardano block already observed at snapshot generation time. On top of the DB-query perf work from #1365, this cuts initial-sync time substantially on mainnet/preprod/preview.

What's in this PR on top of #1365

  • File-backed CNightObservationDataSource (primitives/mainchain-follower/src/data_source/cnight_observation_file.rs, ~500 LoC):

    • In-memory snapshot of the four observation event streams (registrations, deregistrations, creates, spends), each sorted by tx_position; queries resolved by partition_point slice + merge + tx-capacity truncation.
    • Wire format v2: magic (8) || version (1) || inputs_hash (32) || content_sha256 (32) || zstd(scale(events)). Content sha256 verified at load; inputs_hash binds the file to (network_magic, policy, asset, validator, auth_token) and is re-verified against the live pallet config on first call — rejecting cross-network snapshots.
    • Per-call DB traffic reduced to a single get_block_by_hash(current_tip) lookup, memoised per distinct tip.
  • Pre-built snapshots at res/cnight-observations/{mainnet,preprod,preview}.bin (≈92 MB mainnet, ≈22 KB preprod, ≈272 KB preview — all zstd-compressed).

  • Generator binary (util/cnight-observation-generator/) — runs the legacy DB-backed path across [0 .. end_block_no], sorts events, and writes a snapshot file. Used offline to produce the shipped .bin files.

  • Entrypoint auto-selection (node/bin/entrypoint.sh): maps every known CFG_PRESET onto its Cardano network (mainnet / preprod / preview) and sets CNIGHT_OBSERVATION_FILE + CARDANO_NETWORK_MAGIC accordingly when a matching .bin exists. Operator can still override both directly.

  • Runtime switch in node/src/main_chain_follower.rs: if CNIGHT_OBSERVATION_FILE is set the file-backed source is used; otherwise the DB-backed source from Conservative improvements to syncing #1365 is used unchanged.

  • Extra perf polish on the DB path (also applies when no snapshot is configured):

    • In-process LastObservation cache on MidnightCNightObservationDataSourceImpl — serves unchanged-tip calls from memory, including a start-position-advanced slice filter for the common case where the pallet walks forward while Cardano tip stays still.
    • utxo_capacity = tx_capacity * 64* 4; the pallet post-truncates per-tx anyway, so the old multiplier over-fetched ~16×.
    • Downgraded a noisy per-call bounds log from warndebug.
    • Visibility bumps (pub async fn) on the four observation query helpers so the generator can drive them.

Caveats / follow-ups

  • The file-backed source has no built-in fallback to the DB once live Cardano tip exceeds the snapshot's end_block_no — it will silently report "window complete" while real events exist beyond. Safe while the snapshot is regenerated for each release; a hybrid (snapshot-until, DB-after) source is a natural follow-up.
  • Snapshot binaries are committed to the repo. If that's undesirable, move them to release-artifact download + entrypoint fetch.

🗹 TODO before merging

📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason: see changes/changed/cache-multi-asset-id.md (from Conservative improvements to syncing #1365), changes/changed/cnight-query-coarse-bounds.md (from Conservative improvements to syncing #1365), and the feat: add changefile commit on this branch for the baked-sync entry.
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • Updated AGENTS.md if build commands, architecture, or workflows changed
  • No new todos introduced

🧪 Testing Evidence

  • Generated mainnet/preprod/preview snapshots via util/cnight-observation-generator and loaded them on boot — inputs_hash verification passed, total-events logged.

  • Ran node with and without CNIGHT_OBSERVATION_FILE set; confirmed the file-backed path serves observations without hitting db-sync beyond the per-tip get_block_by_hash.

  • Additional tests are provided (if possible)

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other:
  • N/A

Links

Depends on: #1365
Upstream DB-perf origin: #934

Signed-off-by: Giles Cope <gilescope@gmail.com>
@gilescope gilescope changed the base branch from release/node-0.22.5 to giles-934-on-0.22.5 April 20, 2026 07:39
@gilescope gilescope added the ai-assisted Created or modified with AI assistance label Apr 20, 2026
1000,
);
let cnight_observation: Arc<dyn MidnightCNightObservationDataSource + Send + Sync> =
if let Ok(path) = std::env::var("CNIGHT_OBSERVATION_FILE") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggest instead we use the existing config mechanism for this - named something like CNIGHT_OBSERVATION_SNAPSHOT

Then we can add the correct paths in the cfg/*.toml files

This would remove the need for special-case config in entrypoint.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could be cleaner to add this as a node subcommand instead?

.map_err(|e| std::io::Error::other(format!("zstd compress: {e}")))?;
let content_hash: [u8; 32] = Sha256::digest(&compressed).into();
let mut out = Vec::with_capacity(HEADER_LEN + compressed.len());
out.extend_from_slice(SNAPSHOT_MAGIC);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Custom serialization - consider using a struct with some serialization derive instead (midnight-serialize?)

path: &Path,
) -> Result<LoadedSnapshot, Box<dyn std::error::Error + Send + Sync>> {
let bytes = std::fs::read(path)?;
if bytes.len() < HEADER_LEN {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here - could use a struct with derive Serialize and a validate() method

Base automatically changed from giles-934-on-0.22.5 to release/node-0.22.5 April 21, 2026 06:53
@gilescope
Copy link
Copy Markdown
Contributor Author

This has been replaced by #1436

@gilescope gilescope closed this May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted Created or modified with AI assistance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants