From 3ef78520da19bf96ef4b3adb2759004b63ea075d Mon Sep 17 00:00:00 2001 From: gamandeepsingh Date: Sat, 25 Jul 2026 02:47:55 +0530 Subject: [PATCH] fix: dragonsmouth-from-slot '*' falls back to gRPC slot on empty blocks_metadata resolve_initial_from_slot hard-errored when blocks_metadata had zero rows, contradicting the documented fallback behavior. Empty table now resolves to (None, FromSlotMode::LatestDb) so the initial subscribe request omits from_slot and gRPC starts from its own reported slot, the same outcome already used when a resolved slot turns out stale. Fixes #56 --- crates/superbank/README.md | 4 +++- crates/superbank/src/ingest/grpc.rs | 30 ++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/superbank/README.md b/crates/superbank/README.md index eef13f9..2389877 100644 --- a/crates/superbank/README.md +++ b/crates/superbank/README.md @@ -227,7 +227,9 @@ cargo run -p superbank -- --config path/to/superbank.yaml - `dragonsmouth-from-slot: 0` attempts slot 0; if unavailable, superbank parses the gRPC error to find the earliest available slot. - `dragonsmouth-from-slot: "*"` uses the highest slot in `blocks_metadata` before subscribing; if - rejected, superbank falls back to the gRPC-reported available slot. + rejected, superbank falls back to the gRPC-reported available slot. If `blocks_metadata` has no + rows yet (fresh deployment), superbank skips straight to that same gRPC-reported available slot + instead of erroring. - For seeded live-tail gRPC deployments, prefer `dragonsmouth-from-slot: "*"` so process restarts resume from the latest durable ClickHouse slot instead of replaying from the earliest available upstream slot. diff --git a/crates/superbank/src/ingest/grpc.rs b/crates/superbank/src/ingest/grpc.rs index 33d455f..fd53498 100644 --- a/crates/superbank/src/ingest/grpc.rs +++ b/crates/superbank/src/ingest/grpc.rs @@ -483,20 +483,24 @@ async fn resolve_initial_from_slot( ) -> Result<(Option, Option)> { match args.dragonsmouth_from_slot { Some(FromSlotSpec::LatestDb) => { - let latest = fetch_latest_slot_from_blocks(clickhouse, &args.blocks_table) - .await? - .ok_or_else(|| { - anyhow!( - "dragonsmouth-from-slot '*' requires at least one row in {}", + match fetch_latest_slot_from_blocks(clickhouse, &args.blocks_table).await? { + Some(latest) => { + info!( + slot = latest, + table = %args.blocks_table, + "resolved dragonsmouth-from-slot='*' to latest slot in blocks_metadata" + ); + Ok((Some(latest), Some(FromSlotMode::LatestDb))) + } + None => { + info!( + table = %args.blocks_table, + "dragonsmouth-from-slot='*' found no rows in {}; falling back to gRPC-reported available slot", args.blocks_table - ) - })?; - info!( - slot = latest, - table = %args.blocks_table, - "resolved dragonsmouth-from-slot='*' to latest slot in blocks_metadata" - ); - Ok((Some(latest), Some(FromSlotMode::LatestDb))) + ); + Ok((None, Some(FromSlotMode::LatestDb))) + } + } } Some(FromSlotSpec::Slot(0)) => Ok((Some(0), Some(FromSlotMode::Zero))), Some(FromSlotSpec::Slot(slot)) => Ok((Some(slot), Some(FromSlotMode::Strict))),