v0.3.14-pre.011
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
fn network() -> std::option::Option<ksp_store_lib::RawNetworkId> {
|
||||
return match ksp_store_lib::RawNetworkId::new("mainnet") {
|
||||
@@ -62,6 +62,7 @@ fn gap(
|
||||
reason: super::RawTransactionIngestGapReason::WebSocketReconnect,
|
||||
source_key: [source_key_byte; 32],
|
||||
state,
|
||||
last_method: std::option::Option::None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -647,3 +648,80 @@ fn pre_009_health_projection_rejects_duplicate_and_unknown_active_sources() {
|
||||
assert!(unknown_failed.context().iter().any(|context| return context.value() == "continuity.health_failed_source_unknown"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_011_gap_observability_projects_open_and_recent_repaired_entries() {
|
||||
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("first capability fixture unavailable"),
|
||||
};
|
||||
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("second capability fixture unavailable"),
|
||||
};
|
||||
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
|
||||
};
|
||||
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
|
||||
let pending = match contracts.observability_projection() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("pending observability projection failed: {error}"),
|
||||
};
|
||||
assert_eq!(pending.open_gap_count(), 1);
|
||||
assert_eq!(pending.repairing_gap_count(), 0);
|
||||
assert_eq!(pending.repaired_gap_total(), 0);
|
||||
assert_eq!(pending.unresolved_gap_total(), 0);
|
||||
assert_eq!(pending.oldest_open_gap_start_slot(), std::option::Option::Some(100));
|
||||
assert_eq!(pending.gaps().len(), 1);
|
||||
assert_eq!(pending.gaps()[0].start_slot(), 100);
|
||||
assert_eq!(pending.gaps()[0].end_slot(), 110);
|
||||
assert_eq!(pending.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
|
||||
assert_eq!(pending.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
|
||||
assert_eq!(pending.gaps()[0].last_method(), std::option::Option::None);
|
||||
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
|
||||
let repaired = match contracts.observability_projection() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("repaired observability projection failed: {error}"),
|
||||
};
|
||||
assert_eq!(repaired.open_gap_count(), 0);
|
||||
assert_eq!(repaired.repaired_gap_total(), 1);
|
||||
assert_eq!(repaired.redundant_coverage_repair_total(), 1);
|
||||
assert_eq!(repaired.replay_repair_total(), 0);
|
||||
assert_eq!(repaired.http_scan_repair_total(), 0);
|
||||
assert_eq!(repaired.repair_block_fetch_total(), 0);
|
||||
assert_eq!(repaired.repair_transaction_hydration_total(), 0);
|
||||
assert_eq!(repaired.oldest_open_gap_start_slot(), std::option::Option::None);
|
||||
assert_eq!(repaired.gaps().len(), 1);
|
||||
assert_eq!(repaired.gaps()[0].state(), crate::RawTransactionIngestGapState::Repaired);
|
||||
assert_eq!(repaired.gaps()[0].last_method(), std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_011_unresolved_gap_remains_visible_and_bounded() {
|
||||
let capability = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("capability fixture unavailable"),
|
||||
};
|
||||
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![capability]) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
|
||||
};
|
||||
let unresolved = match gap(1, 1, 200, 205, super::RawTransactionIngestGapState::Unresolved) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("unresolved gap fixture unavailable"),
|
||||
};
|
||||
contracts.gap_ledger.gaps.push(unresolved);
|
||||
contracts.gap_ledger.next_gap_id = 2;
|
||||
let projection = match contracts.observability_projection() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("unresolved observability projection failed: {error}"),
|
||||
};
|
||||
assert_eq!(projection.open_gap_count(), 1);
|
||||
assert_eq!(projection.unresolved_gap_total(), 1);
|
||||
assert_eq!(projection.oldest_open_gap_start_slot(), std::option::Option::Some(200));
|
||||
assert!(projection.gaps().len() <= super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS);
|
||||
assert_eq!(projection.gaps()[0].state(), crate::RawTransactionIngestGapState::Unresolved);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||
// version: 32
|
||||
// version: 33
|
||||
|
||||
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
|
||||
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
|
||||
@@ -1074,7 +1074,7 @@ fn v0_3_13_pre_007_source_inventory_aggregates_frontier_and_lifecycle_conservati
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
let first_aggregate = *aggregate_receiver.borrow();
|
||||
let first_aggregate = aggregate_receiver.borrow().clone();
|
||||
assert_eq!(first_aggregate.hydration_pending(), 1);
|
||||
assert_eq!(first_aggregate.processing_frontier_slot(), std::option::Option::None);
|
||||
assert_eq!(first_aggregate.oldest_pending_slot(), std::option::Option::Some(45));
|
||||
@@ -1093,7 +1093,7 @@ fn v0_3_13_pre_007_source_inventory_aggregates_frontier_and_lifecycle_conservati
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
let aggregate = *aggregate_receiver.borrow();
|
||||
let aggregate = aggregate_receiver.borrow().clone();
|
||||
assert_eq!(aggregate.hydration_pending(), 3);
|
||||
assert_eq!(aggregate.processing_frontier_slot(), std::option::Option::Some(42));
|
||||
assert_eq!(aggregate.oldest_pending_slot(), std::option::Option::Some(40));
|
||||
@@ -2808,7 +2808,7 @@ fn pre_008_reconnect_replay_and_proven_gap_are_distinct_monotone_and_frontier_pr
|
||||
let mut reporter = super::RawTransactionIngestProcessingFrontierReporter::new(sender);
|
||||
assert!(reporter.observe_pending(42).is_ok());
|
||||
assert!(reporter.observe_source_continuity(crate::RawTransactionIngestSourceState::Reconnecting, 0, 1, 0, 0, 0).is_ok());
|
||||
let replaying = *receiver.borrow();
|
||||
let replaying = receiver.borrow().clone();
|
||||
assert_eq!(replaying.hydration_pending(), 1);
|
||||
assert_eq!(replaying.oldest_pending_slot(), std::option::Option::Some(42));
|
||||
assert_eq!(replaying.source_state(), std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting));
|
||||
@@ -2826,7 +2826,7 @@ fn pre_008_reconnect_replay_and_proven_gap_are_distinct_monotone_and_frontier_pr
|
||||
assert!(gap.context().iter().any(|context| {
|
||||
return context.value() == "source.continuity_gap_proven";
|
||||
}));
|
||||
let proven = *receiver.borrow();
|
||||
let proven = receiver.borrow().clone();
|
||||
assert_eq!(proven.hydration_pending(), 1);
|
||||
assert_eq!(proven.oldest_pending_slot(), std::option::Option::Some(42));
|
||||
assert_eq!(proven.source_reconnect_total(), 1);
|
||||
@@ -2849,7 +2849,7 @@ fn v0_3_14_pre_004_replay_delivery_is_not_coverage_and_unproven_coverage_fails_c
|
||||
let (sender, receiver) = tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
|
||||
let mut reporter = super::RawTransactionIngestProcessingFrontierReporter::new(sender);
|
||||
assert!(reporter.observe_source_continuity(crate::RawTransactionIngestSourceState::Active, 1, 1, 0, 0, 0).is_ok());
|
||||
let attempted = *receiver.borrow();
|
||||
let attempted = receiver.borrow().clone();
|
||||
assert_eq!(attempted.source_reconnect_total(), 1);
|
||||
assert_eq!(attempted.source_replay_attempt_total(), 1);
|
||||
assert_eq!(attempted.source_continuity_gap_total(), 0);
|
||||
@@ -2914,7 +2914,7 @@ async fn pre_009_duplicate_storm_is_bounded_coalesced_and_abort_leaves_no_orphan
|
||||
assert_eq!(coordinator.pending.len(), 1);
|
||||
assert_eq!(coordinator.pending_signal_count, 3);
|
||||
assert!(coordinator.tasks.is_empty());
|
||||
let pending = *frontier_receiver.borrow();
|
||||
let pending = frontier_receiver.borrow().clone();
|
||||
assert_eq!(pending.hydration_pending(), 3);
|
||||
assert_eq!(pending.oldest_pending_slot(), std::option::Option::Some(10));
|
||||
assert!(pending.processing_frontier_slot() != std::option::Option::Some(10));
|
||||
@@ -2923,7 +2923,7 @@ async fn pre_009_duplicate_storm_is_bounded_coalesced_and_abort_leaves_no_orphan
|
||||
assert!(coordinator.pending.is_empty());
|
||||
assert_eq!(coordinator.pending_signal_count, 0);
|
||||
assert!(coordinator.tasks.is_empty());
|
||||
let cleaned = *frontier_receiver.borrow();
|
||||
let cleaned = frontier_receiver.borrow().clone();
|
||||
assert_eq!(cleaned.hydration_pending(), 0);
|
||||
assert_eq!(cleaned.oldest_pending_slot(), std::option::Option::None);
|
||||
assert_eq!(cleaned.processing_frontier_slot(), safe_frontier_before_abort);
|
||||
@@ -2992,7 +2992,7 @@ async fn pre_009_abort_joins_in_flight_hydration_and_clears_pending_projection()
|
||||
assert_eq!(coordinator.pending_signal_count, 0);
|
||||
assert!(coordinator.pending.is_empty());
|
||||
assert!(coordinator.tasks.is_empty());
|
||||
let cleaned = *frontier_receiver.borrow();
|
||||
let cleaned = frontier_receiver.borrow().clone();
|
||||
assert_eq!(cleaned.hydration_pending(), 0);
|
||||
assert_eq!(cleaned.oldest_pending_slot(), std::option::Option::None);
|
||||
assert_eq!(cleaned.processing_frontier_slot(), std::option::Option::None);
|
||||
@@ -3983,7 +3983,7 @@ async fn v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_wi
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
assert!(sibling_active.load(std::sync::atomic::Ordering::Acquire));
|
||||
let health = *health_receiver.borrow();
|
||||
let health = health_receiver.borrow().clone();
|
||||
assert!(health.continuity_policy_observed());
|
||||
assert!(!health.continuity_has_open_gaps());
|
||||
assert!(health.future_target_coverage());
|
||||
@@ -4037,7 +4037,7 @@ fn v0_3_14_pre_003_websocket_reconnect_is_anchored_before_terminal_gap_projectio
|
||||
std::option::Option::None => panic!("bounded reconnect incident anchor missing"),
|
||||
};
|
||||
assert_eq!(anchor.end_slot(), std::option::Option::Some(105));
|
||||
let projection = *receiver.borrow();
|
||||
let projection = receiver.borrow().clone();
|
||||
assert_eq!(projection.source_reconnect_total(), 1);
|
||||
assert_eq!(projection.source_replay_attempt_total(), 0);
|
||||
assert_eq!(projection.source_continuity_gap_total(), 1);
|
||||
@@ -4059,7 +4059,7 @@ fn v0_3_14_pre_003_websocket_overflow_and_reconnect_storm_share_earliest_anchor(
|
||||
assert_eq!(anchor.end_slot(), std::option::Option::None);
|
||||
assert!(anchor.saw_reconnect());
|
||||
assert!(anchor.saw_overflow());
|
||||
let projection = *receiver.borrow();
|
||||
let projection = receiver.borrow().clone();
|
||||
assert_eq!(projection.source_reconnect_total(), 1);
|
||||
assert_eq!(projection.source_continuity_gap_total(), 3);
|
||||
let bounded = reporter.observe_websocket_post_incident_slot(201);
|
||||
@@ -4109,9 +4109,9 @@ fn v0_3_14_pre_009_inventory_health_projection_tracks_reconciled_coverage() {
|
||||
};
|
||||
let active = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(60), std::option::Option::None)
|
||||
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 0, 0, 0);
|
||||
assert!(first.publish(active).is_ok());
|
||||
assert!(second.publish(active).is_ok());
|
||||
let healthy = *aggregate_receiver.borrow();
|
||||
assert!(first.publish(active.clone()).is_ok());
|
||||
assert!(second.publish(active.clone()).is_ok());
|
||||
let healthy = aggregate_receiver.borrow().clone();
|
||||
assert!(healthy.continuity_policy_observed());
|
||||
assert_eq!(healthy.continuity_frontier_slot(), std::option::Option::Some(60));
|
||||
assert!(!healthy.continuity_has_open_gaps());
|
||||
@@ -4123,11 +4123,14 @@ fn v0_3_14_pre_009_inventory_health_projection_tracks_reconciled_coverage() {
|
||||
};
|
||||
assert!(contracts.record_source_loss_gap([2_u8; 32], 50, 55).is_ok());
|
||||
}
|
||||
assert!(first.publish(active).is_ok());
|
||||
let pending = *aggregate_receiver.borrow();
|
||||
assert!(first.publish(active.clone()).is_ok());
|
||||
let pending = aggregate_receiver.borrow().clone();
|
||||
assert_eq!(pending.continuity_frontier_slot(), std::option::Option::Some(49));
|
||||
assert!(pending.continuity_has_open_gaps());
|
||||
assert!(pending.future_target_coverage());
|
||||
assert_eq!(pending.continuity_snapshot().open_gap_count(), 1);
|
||||
assert_eq!(pending.continuity_snapshot().oldest_open_gap_start_slot(), std::option::Option::Some(50));
|
||||
assert_eq!(pending.continuity_snapshot().gaps().len(), 1);
|
||||
{
|
||||
let mut contracts = match continuity_contracts.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -4135,11 +4138,18 @@ fn v0_3_14_pre_009_inventory_health_projection_tracks_reconciled_coverage() {
|
||||
};
|
||||
assert!(contracts.record_coverage_epoch([1_u8; 32], 40, 60).is_ok());
|
||||
}
|
||||
assert!(first.publish(active).is_ok());
|
||||
let reconciled = *aggregate_receiver.borrow();
|
||||
assert!(first.publish(active.clone()).is_ok());
|
||||
let reconciled = aggregate_receiver.borrow().clone();
|
||||
assert_eq!(reconciled.continuity_frontier_slot(), std::option::Option::Some(60));
|
||||
assert!(!reconciled.continuity_has_open_gaps());
|
||||
assert!(reconciled.future_target_coverage());
|
||||
assert_eq!(reconciled.continuity_snapshot().open_gap_count(), 0);
|
||||
assert_eq!(reconciled.continuity_snapshot().repaired_gap_total(), 1);
|
||||
assert_eq!(reconciled.continuity_snapshot().redundant_coverage_repair_total(), 1);
|
||||
assert_eq!(
|
||||
reconciled.continuity_snapshot().gaps()[0].last_method(),
|
||||
std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
fn snapshot_foundation_with_source_total(
|
||||
source_total: usize,
|
||||
@@ -304,3 +304,45 @@ fn v0_3_14_pre_009_health_requires_present_and_future_coverage_before_healthy()
|
||||
assert_eq!(faulted.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_14_pre_011_snapshot_carries_checked_gap_observability() {
|
||||
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let gap = crate::RawTransactionIngestGapSnapshot::new(
|
||||
crate::RawTransactionIngestGapId::new(7),
|
||||
100,
|
||||
110,
|
||||
crate::RawTransactionIngestGapState::Pending,
|
||||
crate::RawTransactionIngestGapReason::SourceFailure,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let continuity_snapshot = crate::RawTransactionIngestContinuitySnapshotProjection::empty()
|
||||
.with_gap_state(std::vec![gap], 1, 0, std::option::Option::Some(100))
|
||||
.with_recovery_totals(3, 1, 2, 1, 0)
|
||||
.with_material_totals(4, 5);
|
||||
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(120), std::option::Option::None)
|
||||
.with_continuity_snapshot(continuity_snapshot);
|
||||
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
|
||||
let snapshot = source.current();
|
||||
assert_eq!(snapshot.gaps().len(), 1);
|
||||
assert_eq!(snapshot.gaps()[0].gap_id().value(), 7);
|
||||
assert_eq!(snapshot.gaps()[0].start_slot(), 100);
|
||||
assert_eq!(snapshot.gaps()[0].end_slot(), 110);
|
||||
assert_eq!(snapshot.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
|
||||
assert_eq!(snapshot.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
|
||||
assert_eq!(snapshot.gaps()[0].last_method(), std::option::Option::None);
|
||||
assert_eq!(snapshot.open_gap_count(), 1);
|
||||
assert_eq!(snapshot.repairing_gap_count(), 0);
|
||||
assert_eq!(snapshot.repaired_gap_total(), 3);
|
||||
assert_eq!(snapshot.unresolved_gap_total(), 1);
|
||||
assert_eq!(snapshot.replay_repair_total(), 2);
|
||||
assert_eq!(snapshot.redundant_coverage_repair_total(), 1);
|
||||
assert_eq!(snapshot.http_scan_repair_total(), 0);
|
||||
assert_eq!(snapshot.repair_block_fetch_total(), 4);
|
||||
assert_eq!(snapshot.repair_transaction_hydration_total(), 5);
|
||||
assert_eq!(snapshot.oldest_open_gap_start_slot(), std::option::Option::Some(100));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user