v0.3.14-pre.011

This commit is contained in:
2026-09-12 19:05:43 +02:00
parent c99cb048bf
commit 4e39ddd5d0
13 changed files with 1174 additions and 53 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
// version: 9
// version: 10
/// Maximum number of slots admitted by one private continuity HTTP discovery window outside this module.
pub(crate) const MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS: u64 = MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS;
@@ -105,6 +105,17 @@ impl RawTransactionIngestGapReason {
Self::WebSocketReconnect,
Self::YellowstoneRetention,
];
const fn public(self) -> crate::RawTransactionIngestGapReason {
return match self {
Self::HttpProducedBlockUnavailable => crate::RawTransactionIngestGapReason::HttpProducedBlockUnavailable,
Self::KnownReferenceMissing => crate::RawTransactionIngestGapReason::KnownReferenceMissing,
Self::SourceFailure => crate::RawTransactionIngestGapReason::SourceFailure,
Self::TransportOverflow => crate::RawTransactionIngestGapReason::TransportOverflow,
Self::WebSocketReconnect => crate::RawTransactionIngestGapReason::WebSocketReconnect,
Self::YellowstoneRetention => crate::RawTransactionIngestGapReason::YellowstoneRetention,
};
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -121,6 +132,15 @@ impl RawTransactionIngestGapState {
const fn is_open(self) -> bool {
return !matches!(self, Self::Repaired);
}
const fn public(self) -> crate::RawTransactionIngestGapState {
return match self {
Self::Pending => crate::RawTransactionIngestGapState::Pending,
Self::Repairing => crate::RawTransactionIngestGapState::Repairing,
Self::Repaired => crate::RawTransactionIngestGapState::Repaired,
Self::Unresolved => crate::RawTransactionIngestGapState::Unresolved,
};
}
}
/// Private supervisor decision for one terminal live-source loss after conservative continuity reconciliation.
@@ -317,6 +337,20 @@ struct RawTransactionIngestGap {
reason: RawTransactionIngestGapReason,
source_key: [u8; 32],
state: RawTransactionIngestGapState,
last_method: std::option::Option<crate::RawTransactionIngestRepairMethod>,
}
impl RawTransactionIngestGap {
fn snapshot(&self) -> crate::RawTransactionIngestGapSnapshot {
return crate::RawTransactionIngestGapSnapshot::new(
crate::RawTransactionIngestGapId::new(self.gap_id.0),
self.range.start_slot(),
self.range.end_slot(),
self.state.public(),
self.reason.public(),
self.last_method,
);
}
}
struct RawTransactionIngestGapLedger {
@@ -410,23 +444,134 @@ impl RawTransactionIngestGapLedger {
reason,
source_key,
state: RawTransactionIngestGapState::Pending,
last_method: std::option::Option::None,
});
return self.validate_invariants();
}
fn reconcile_with_coverage_epochs(&mut self, coverage_epochs: &RawTransactionIngestCoverageEpochLedger) -> ksp_core_lib::Result<()> {
for gap in &mut self.gaps {
if !gap.state.is_open() {
if !gap.state.is_open() || gap.state == RawTransactionIngestGapState::Unresolved {
continue;
}
let requirement = RawTransactionIngestCoverageRequirement { commitment: gap.commitment, scope: gap.coverage_requirement.clone() };
if coverage_epochs.redundant_relation_for_gap(gap.source_key, &requirement, gap.range).is_some() {
gap.state = RawTransactionIngestGapState::Repaired;
gap.last_method = std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage);
}
}
return self.validate_invariants();
}
fn observability_projection(&self) -> ksp_core_lib::Result<crate::RawTransactionIngestContinuitySnapshotProjection> {
let mut open_gap_count = 0_usize;
let mut repairing_gap_count = 0_usize;
let mut repaired_gap_total = 0_u64;
let mut unresolved_gap_total = 0_u64;
let mut replay_repair_total = 0_u64;
let mut redundant_coverage_repair_total = 0_u64;
let mut http_scan_repair_total = 0_u64;
let mut repair_block_fetch_total = 0_u64;
let mut repair_transaction_hydration_total = 0_u64;
let mut oldest_open_gap_start_slot = std::option::Option::None;
let mut gaps = std::vec::Vec::with_capacity(MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS);
for gap in &self.gaps {
if gap.state.is_open() {
open_gap_count = match open_gap_count.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crate::counter_exhausted_error("continuity.open_gap_count")),
};
if gap.state == RawTransactionIngestGapState::Repairing {
repairing_gap_count = match repairing_gap_count.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.repairing_gap_count"));
},
};
}
if gap.state == RawTransactionIngestGapState::Unresolved {
unresolved_gap_total = match unresolved_gap_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.unresolved_gap_total"));
},
};
}
oldest_open_gap_start_slot = match oldest_open_gap_start_slot {
std::option::Option::Some(value) => std::option::Option::Some(value.min(gap.range.start_slot())),
std::option::Option::None => std::option::Option::Some(gap.range.start_slot()),
};
gaps.push(gap.snapshot());
continue;
}
repaired_gap_total = match repaired_gap_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crate::counter_exhausted_error("continuity.repaired_gap_total")),
};
match gap.last_method {
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::Replay) => {
replay_repair_total = match replay_repair_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.replay_repair_total"));
},
};
},
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage) => {
redundant_coverage_repair_total = match redundant_coverage_repair_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.redundant_coverage_repair_total"));
},
};
},
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::HttpScan) => {
http_scan_repair_total = match http_scan_repair_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.http_scan_repair_total"));
},
};
},
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::BlockFetch) => {
repair_block_fetch_total = match repair_block_fetch_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.repair_block_fetch_total"));
},
};
},
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::TransactionHydration) => {
repair_transaction_hydration_total = match repair_transaction_hydration_total.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(crate::counter_exhausted_error("continuity.repair_transaction_hydration_total"));
},
};
},
std::option::Option::None => {},
}
}
if gaps.len() < MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS {
for gap in self.gaps.iter().rev() {
if gap.state != RawTransactionIngestGapState::Repaired {
continue;
}
gaps.push(gap.snapshot());
if gaps.len() == MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS {
break;
}
}
}
gaps.sort_unstable_by_key(|gap| return gap.gap_id().value());
return std::result::Result::Ok(
crate::RawTransactionIngestContinuitySnapshotProjection::empty()
.with_gap_state(gaps, open_gap_count, repairing_gap_count, oldest_open_gap_start_slot)
.with_recovery_totals(repaired_gap_total, unresolved_gap_total, replay_repair_total, redundant_coverage_repair_total, http_scan_repair_total)
.with_material_totals(repair_block_fetch_total, repair_transaction_hydration_total),
);
}
fn validate_invariants(&self) -> ksp_core_lib::Result<()> {
if self.next_gap_id == 0 {
return std::result::Result::Err(crate::runtime_error("continuity.gap_id_exhausted"));
@@ -923,6 +1068,11 @@ impl crate::RawTransactionIngestContinuityContracts {
return self.gap_ledger.continuity_frontier(processing_frontier_slot);
}
/// Returns the bounded source-neutral continuity observability projection for the latest Worker snapshot.
pub(crate) fn observability_projection(&self) -> ksp_core_lib::Result<crate::RawTransactionIngestContinuitySnapshotProjection> {
return self.gap_ledger.observability_projection();
}
/// Reconciles known gaps and projects source-neutral present/future coverage evidence for Worker health.
///
/// The returned tuple is `(continuity_frontier, has_open_gaps, future_target_coverage, failed_source_losses_reconciled)`. Configuration alone may