v0.3.12-pre.008

This commit is contained in:
2026-09-09 20:42:32 +02:00
parent 90c266d68a
commit 2aa64cf0a4
17 changed files with 879 additions and 47 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
// version: 10
// version: 11
use sha2::Digest; // rust-rules: trait-import
@@ -382,6 +382,13 @@ impl crate::RawTransactionIngestYellowstoneSource {
};
let mut coordinator = RawTransactionIngestHydrationCoordinator::new(&settings);
let mut processing_frontier = RawTransactionIngestProcessingFrontierReporter::new(processing_frontier_sender);
let snapshot_source = session.snapshot_source();
if let std::result::Result::Err(error) = processing_frontier.observe_session_snapshot(snapshot_source.current()) {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Failed);
let _closed = session.close().await;
return std::result::Result::Err(error);
}
let mut session_snapshot_source = std::option::Option::Some(snapshot_source);
let mut fault = std::option::Option::None;
loop {
if *stop_receiver.borrow() {
@@ -401,6 +408,19 @@ impl crate::RawTransactionIngestYellowstoneSource {
_ = stop_receiver.changed() => {
break;
}
source_snapshot = wait_yellowstone_session_snapshot(&mut session_snapshot_source) => {
match source_snapshot {
std::option::Option::Some(snapshot) => {
if let std::result::Result::Err(error) = processing_frontier.observe_session_snapshot(snapshot) {
fault = std::option::Option::Some(error);
break;
}
},
std::option::Option::None => {
session_snapshot_source = std::option::Option::None;
},
}
}
joined = coordinator.tasks.join_next(), if !coordinator.tasks.is_empty() => {
let joined = match joined {
std::option::Option::Some(value) => value,
@@ -453,13 +473,21 @@ impl crate::RawTransactionIngestYellowstoneSource {
}
}
coordinator.abort_all().await;
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Closing);
let closed = session.close().await;
if let std::option::Option::Some(error) = fault {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Failed);
return std::result::Result::Err(error);
}
return match closed {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(source_transport_error(error.code())),
std::result::Result::Ok(()) => {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Closed);
std::result::Result::Ok(())
},
std::result::Result::Err(error) => {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Failed);
std::result::Result::Err(source_transport_error(error.code()))
},
};
}
}
@@ -774,12 +802,33 @@ fn current_raw_timestamp() -> ksp_core_lib::Result<ksp_store_lib::RawTimestamp>
};
}
async fn wait_yellowstone_session_snapshot(
source: &mut std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshotSource>,
) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot> {
return match source {
std::option::Option::Some(value) => value.wait_for_change().await,
std::option::Option::None => {
return std::future::pending::<std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>>().await;
},
};
}
fn source_transport_error(code: ksp_core_lib::ErrorCode) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED, "RAW transaction ingest Yellowstone source transport failed")
.with_context("transport_domain", code.domain())
.with_context("transport_code", code.code());
}
fn map_yellowstone_source_state(state: ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState) -> crate::RawTransactionIngestSourceState {
return match state {
ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Active => crate::RawTransactionIngestSourceState::Active,
ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting => crate::RawTransactionIngestSourceState::Reconnecting,
ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Closing => crate::RawTransactionIngestSourceState::Closing,
ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Closed => crate::RawTransactionIngestSourceState::Closed,
ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Failed => crate::RawTransactionIngestSourceState::Failed,
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RawTransactionIngestProcessingSlotState {
pending: usize,
@@ -915,11 +964,22 @@ impl RawTransactionIngestProcessingFrontier {
struct RawTransactionIngestProcessingFrontierReporter {
frontier: RawTransactionIngestProcessingFrontier,
sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
source_reconnect_total: u64,
source_replay_attempt_total: u64,
source_continuity_gap_total: u64,
}
impl RawTransactionIngestProcessingFrontierReporter {
fn new(sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>) -> Self {
return Self { frontier: RawTransactionIngestProcessingFrontier::new(), sender };
return Self {
frontier: RawTransactionIngestProcessingFrontier::new(),
sender,
source_state: std::option::Option::None,
source_reconnect_total: 0,
source_replay_attempt_total: 0,
source_continuity_gap_total: 0,
};
}
fn observe_pending(&mut self, slot: u64) -> ksp_core_lib::Result<()> {
@@ -946,8 +1006,57 @@ impl RawTransactionIngestProcessingFrontierReporter {
return std::result::Result::Ok(());
}
fn observe_session_snapshot(&mut self, snapshot: ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot) -> ksp_core_lib::Result<()> {
return self.observe_source_continuity(
map_yellowstone_source_state(snapshot.state()),
snapshot.reconnect_count(),
snapshot.replay_attempt_count(),
snapshot.continuity_gap_count(),
);
}
fn observe_source_continuity(
&mut self,
state: crate::RawTransactionIngestSourceState,
reconnect_total: u64,
replay_attempt_total: u64,
continuity_gap_total: u64,
) -> ksp_core_lib::Result<()> {
if reconnect_total < self.source_reconnect_total
|| replay_attempt_total < self.source_replay_attempt_total
|| continuity_gap_total < self.source_continuity_gap_total
{
return std::result::Result::Err(crate::runtime_error("source.continuity_counter_regression"));
}
let gap_increased = continuity_gap_total > self.source_continuity_gap_total;
self.source_state = std::option::Option::Some(state);
self.source_reconnect_total = reconnect_total;
self.source_replay_attempt_total = replay_attempt_total;
self.source_continuity_gap_total = continuity_gap_total;
self.publish();
if gap_increased {
return std::result::Result::Err(crate::runtime_error("source.continuity_gap_proven"));
}
return std::result::Result::Ok(());
}
fn set_source_state(&mut self, state: crate::RawTransactionIngestSourceState) {
self.source_state = std::option::Option::Some(state);
self.publish();
return;
}
fn projection(&self) -> crate::RawTransactionIngestProcessingFrontierProjection {
return self.frontier.projection().with_source_continuity(
self.source_state,
self.source_reconnect_total,
self.source_replay_attempt_total,
self.source_continuity_gap_total,
);
}
fn publish(&self) {
self.sender.send_replace(self.frontier.projection());
self.sender.send_replace(self.projection());
return;
}
}