v0.3.14-pre.003

This commit is contained in:
2026-09-11 21:54:19 +02:00
parent 7bf938c19f
commit 6721a4142a
15 changed files with 872 additions and 22 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
// version: 3
// version: 4
/// Maximum number of simultaneously retained non-repaired run-local gaps.
const MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS: usize = 64;
@@ -150,6 +150,85 @@ impl RawTransactionIngestGapRange {
}
}
/// Private run-local anchor for one WebSocket continuity incident.
///
/// The anchor never derives slots from wall-clock time. Its inclusive start is the latest slot actually observed by that source before Transport reported a
/// reconnect or notification overflow; the end remains absent until the first post-incident source slot is observed.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestWebSocketIncidentAnchor {
end_slot: std::option::Option<u64>,
overflow_total: u64,
reconnect_total: u64,
saw_overflow: bool,
saw_reconnect: bool,
start_slot: u64,
}
impl crate::RawTransactionIngestWebSocketIncidentAnchor {
/// Creates one open incident anchor from monotone Transport counters and one actually observed source slot.
pub(crate) fn new(start_slot: u64, reconnect_total: u64, overflow_total: u64, saw_reconnect: bool, saw_overflow: bool) -> ksp_core_lib::Result<Self> {
if !saw_reconnect && !saw_overflow {
return std::result::Result::Err(crate::runtime_error("continuity.websocket_incident_reason_missing"));
}
return std::result::Result::Ok(Self {
end_slot: std::option::Option::None,
overflow_total,
reconnect_total,
saw_overflow,
saw_reconnect,
start_slot,
});
}
/// Extends one still-open incident with newer monotone Transport counters without changing its earliest start slot.
pub(crate) fn extend(&mut self, reconnect_total: u64, overflow_total: u64, saw_reconnect: bool, saw_overflow: bool) -> ksp_core_lib::Result<()> {
if self.end_slot.is_some() {
return std::result::Result::Err(crate::runtime_error("continuity.websocket_incident_already_closed"));
}
if reconnect_total < self.reconnect_total || overflow_total < self.overflow_total {
return std::result::Result::Err(crate::runtime_error("continuity.websocket_incident_counter_regression"));
}
self.reconnect_total = reconnect_total;
self.overflow_total = overflow_total;
self.saw_reconnect = self.saw_reconnect || saw_reconnect;
self.saw_overflow = self.saw_overflow || saw_overflow;
return std::result::Result::Ok(());
}
/// Closes the inclusive incident range at the first post-incident source slot.
pub(crate) fn close_at(&mut self, end_slot: u64) -> ksp_core_lib::Result<()> {
let range = match RawTransactionIngestGapRange::new(self.start_slot, end_slot) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
self.end_slot = std::option::Option::Some(range.end_slot());
return std::result::Result::Ok(());
}
/// Returns the first source slot that safely anchors the incident, inclusively.
#[cfg(test)]
pub(crate) const fn start_slot(self) -> u64 {
return self.start_slot;
}
/// Returns the first post-incident source slot when the incident has become range-bounded.
pub(crate) const fn end_slot(self) -> std::option::Option<u64> {
return self.end_slot;
}
/// Returns whether at least one physical WebSocket reconnect contributed to this incident.
#[cfg(test)]
pub(crate) const fn saw_reconnect(self) -> bool {
return self.saw_reconnect;
}
/// Returns whether at least one Transport notification overflow contributed to this incident.
#[cfg(test)]
pub(crate) const fn saw_overflow(self) -> bool {
return self.saw_overflow;
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
struct RawTransactionIngestGapId(u64);