v0.3.14-pre.008

This commit is contained in:
2026-09-12 09:00:36 +02:00
parent 9fc30d3ab9
commit 505cc8c3f6
9 changed files with 1184 additions and 127 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
// version: 28
// version: 29
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
@@ -131,6 +131,62 @@ fn ws_endpoint(
));
}
fn supervisor_contracts(
source_specs: &[(u8, &'static str, u8)],
) -> std::option::Option<std::sync::Arc<std::sync::Mutex<crate::RawTransactionIngestContinuityContracts>>> {
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let mut capabilities = std::vec::Vec::new();
for (source_key_byte, family, fingerprint_byte) in source_specs {
let (scope, reference_bearing, block_material) = if *family == "full_ledger" {
(crate::RawTransactionIngestCoverageScope::full_ledger_transactions(), false, true)
} else {
(crate::RawTransactionIngestCoverageScope::exact_source_scope(*family, [*fingerprint_byte; 32]), true, false)
};
let capability = crate::RawTransactionIngestContinuityCapabilityDescriptor::new(
[*source_key_byte; 32],
network.clone(),
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope,
reference_bearing,
block_material,
false,
reference_bearing,
false,
false,
);
let capability = match capability {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
capabilities.push(capability);
}
return match crate::RawTransactionIngestContinuityContracts::new(capabilities) {
std::result::Result::Ok(value) => std::option::Option::Some(std::sync::Arc::new(std::sync::Mutex::new(value))),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn supervisor_inventory(
source_keys: std::vec::Vec<[u8; 32]>,
states: &[std::option::Option<crate::RawTransactionIngestSourceState>],
) -> std::option::Option<std::sync::Arc<std::sync::Mutex<super::RawTransactionIngestSourceInventory>>> {
if source_keys.len() != states.len() {
return std::option::Option::None;
}
let mut inventory = super::RawTransactionIngestSourceInventory::new(source_keys.clone());
for (index, (source_key, state)) in source_keys.iter().zip(states.iter()).enumerate() {
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(100), std::option::Option::None)
.with_source_continuity(*state, 0, 0, 0);
if inventory.update(index, *source_key, projection).is_err() {
return std::option::Option::None;
}
}
return std::option::Option::Some(std::sync::Arc::new(std::sync::Mutex::new(inventory)));
}
fn standard_block_source(
cluster: &str,
endpoint_name: &str,
@@ -1049,8 +1105,24 @@ async fn v0_3_13_pre_007_source_supervisor_joins_all_children_on_stop() {
let active = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
let source_keys = std::vec![[1_u8; 32], [2_u8; 32], [3_u8; 32]];
let contracts = match supervisor_contracts(&[(1, "fixture-a", 1), (2, "fixture-b", 2), (3, "fixture-c", 3)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let inventory = match supervisor_inventory(
source_keys.clone(),
&[
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
],
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut children = tokio::task::JoinSet::new();
for _ in 0..3 {
for source_key in source_keys {
let active = std::sync::Arc::clone(&active);
let mut source_stop_receiver = source_stop_receiver.clone();
let _abort_handle = children.spawn(async move {
@@ -1059,12 +1131,14 @@ async fn v0_3_13_pre_007_source_supervisor_joins_all_children_on_stop() {
let changed = source_stop_receiver.changed().await;
if changed.is_err() || *source_stop_receiver.borrow() {
active.fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
return std::result::Result::Ok(());
return (source_key, std::result::Result::Ok(()));
}
}
});
}
let supervisor = tokio::spawn(super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children));
let supervisor = tokio::spawn(async move {
return super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children, contracts, inventory).await;
});
for _ in 0..64 {
if active.load(std::sync::atomic::Ordering::Acquire) == 3 {
break;
@@ -1089,11 +1163,25 @@ async fn v0_3_13_pre_007_source_failure_stops_and_joins_sibling_sources() {
let sibling_active = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let (_stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
let contracts = match supervisor_contracts(&[(1, "fixture-a", 1), (2, "fixture-b", 2)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let inventory = match supervisor_inventory(
std::vec![[1_u8; 32], [2_u8; 32]],
&[
std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed),
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
],
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut children = tokio::task::JoinSet::new();
let failing_barrier = std::sync::Arc::clone(&barrier);
let _failing_abort_handle = children.spawn(async move {
let _barrier_wait = failing_barrier.wait().await;
return std::result::Result::Err(crate::runtime_error("test.pre_007_source_failed"));
return ([1_u8; 32], std::result::Result::Err(crate::runtime_error("test.pre_007_source_failed")));
});
let sibling_barrier = std::sync::Arc::clone(&barrier);
let sibling_active_for_task = std::sync::Arc::clone(&sibling_active);
@@ -1105,11 +1193,11 @@ async fn v0_3_13_pre_007_source_failure_stops_and_joins_sibling_sources() {
let changed = sibling_stop_receiver.changed().await;
if changed.is_err() || *sibling_stop_receiver.borrow() {
sibling_active_for_task.store(false, std::sync::atomic::Ordering::Release);
return std::result::Result::Ok(());
return ([2_u8; 32], std::result::Result::Ok(()));
}
}
});
let result = super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children).await;
let result = super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children, contracts, inventory).await;
assert!(result.is_err());
assert!(!sibling_active.load(std::sync::atomic::Ordering::Acquire));
return;
@@ -3701,13 +3789,23 @@ async fn v0_3_13_pre_011_aborting_outer_source_supervisor_aborts_nested_source_t
let active = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let (_stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (source_stop_sender, _source_stop_receiver) = tokio::sync::watch::channel(false);
let contracts = match supervisor_contracts(&[(1, "fixture-a", 1)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let inventory = match supervisor_inventory(std::vec![[1_u8; 32]], &[std::option::Option::Some(crate::RawTransactionIngestSourceState::Active)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut children = tokio::task::JoinSet::new();
let task_active = std::sync::Arc::clone(&active);
let _abort_handle = children.spawn(async move {
let _guard = Pre011SourceActiveGuard::new(task_active);
return std::future::pending::<ksp_core_lib::Result<()>>().await;
return std::future::pending::<([u8; 32], ksp_core_lib::Result<()>)>().await;
});
let supervisor = tokio::spawn(async move {
return super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children, contracts, inventory).await;
});
let supervisor = tokio::spawn(super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children));
for _ in 0..64 {
if active.load(std::sync::atomic::Ordering::Acquire) == 1 {
break;
@@ -3733,11 +3831,25 @@ async fn v0_3_13_pre_011_stop_racing_ready_source_fault_preserves_fault_and_join
let fault_gate = std::sync::Arc::new(tokio::sync::Notify::new());
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
let contracts = match supervisor_contracts(&[(1, "fixture-a", 1), (2, "fixture-b", 2)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let inventory = match supervisor_inventory(
std::vec![[1_u8; 32], [2_u8; 32]],
&[
std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed),
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
],
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut children = tokio::task::JoinSet::new();
let task_fault_gate = std::sync::Arc::clone(&fault_gate);
let _fault_abort_handle = children.spawn(async move {
task_fault_gate.notified().await;
return std::result::Result::Err(crate::runtime_error("test.pre_011_ready_source_fault"));
return ([1_u8; 32], std::result::Result::Err(crate::runtime_error("test.pre_011_ready_source_fault")));
});
let sibling_active_for_task = std::sync::Arc::clone(&sibling_active);
let mut sibling_stop_receiver = source_stop_receiver.clone();
@@ -3746,11 +3858,13 @@ async fn v0_3_13_pre_011_stop_racing_ready_source_fault_preserves_fault_and_join
loop {
let changed = sibling_stop_receiver.changed().await;
if changed.is_err() || *sibling_stop_receiver.borrow() {
return std::result::Result::Ok(());
return ([2_u8; 32], std::result::Result::Ok(()));
}
}
});
let supervisor = tokio::spawn(super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children));
let supervisor = tokio::spawn(async move {
return super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children, contracts, inventory).await;
});
for _ in 0..64 {
if sibling_active.load(std::sync::atomic::Ordering::Acquire) == 1 {
break;
@@ -3773,6 +3887,86 @@ async fn v0_3_13_pre_011_stop_racing_ready_source_fault_preserves_fault_and_join
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_without_worker_respawn() {
let sibling_active = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
let contracts = match supervisor_contracts(&[(1, "standard_logs", 7), (2, "full_ledger", 0)]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
{
let mut contracts_guard = match contracts.lock() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(poisoned) => poisoned.into_inner(),
};
assert!(contracts_guard.record_coverage_epoch([2_u8; 32], 90, 110).is_ok());
}
let inventory = match supervisor_inventory(
std::vec![[1_u8; 32], [2_u8; 32]],
&[
std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed),
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
],
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut children = tokio::task::JoinSet::new();
let _lost_abort_handle = children.spawn(async move {
return ([1_u8; 32], std::result::Result::Err(super::continuity_range_error("source.continuity_gap_proven", 95, 100)));
});
let sibling_active_for_task = std::sync::Arc::clone(&sibling_active);
let mut sibling_stop_receiver = source_stop_receiver.clone();
let _sibling_abort_handle = children.spawn(async move {
sibling_active_for_task.store(true, std::sync::atomic::Ordering::Release);
loop {
let changed = sibling_stop_receiver.changed().await;
if changed.is_err() || *sibling_stop_receiver.borrow() {
sibling_active_for_task.store(false, std::sync::atomic::Ordering::Release);
return ([2_u8; 32], std::result::Result::Ok(()));
}
}
});
let supervisor = tokio::spawn(async move {
return super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children, contracts, inventory).await;
});
for _ in 0..64 {
if sibling_active.load(std::sync::atomic::Ordering::Acquire) {
break;
}
tokio::task::yield_now().await;
}
assert!(sibling_active.load(std::sync::atomic::Ordering::Acquire));
tokio::task::yield_now().await;
assert!(sibling_active.load(std::sync::atomic::Ordering::Acquire));
stop_sender.send_replace(true);
let result = match supervisor.await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(result.is_ok());
assert!(!sibling_active.load(std::sync::atomic::Ordering::Acquire));
return;
}
#[test]
fn v0_3_14_pre_008_only_classified_source_loss_enters_coverage_decision() {
let transport = super::source_transport_error(ksp_onchain_transport_lib::ERROR_CODE_HTTP_CONNECTION_FAILED);
assert!(super::source_loss_is_reconcilable(&transport));
assert!(super::source_loss_is_reconcilable(&crate::runtime_error("source.continuity_gap_proven")));
assert!(super::source_loss_is_reconcilable(&crate::runtime_error("source.replay_coverage_unproven")));
assert!(!super::source_loss_is_reconcilable(&crate::runtime_error("source.hydration_task_join_failed")));
let bounded = super::continuity_range_error("source.continuity_gap_proven", 10, 20);
assert!(matches!(super::source_loss_continuity_range(&bounded), std::result::Result::Ok(std::option::Option::Some((10, 20)))));
assert!(matches!(
super::source_loss_continuity_range(&crate::runtime_error("source.continuity_gap_proven")),
std::result::Result::Ok(std::option::Option::None)
));
return;
}
#[test]
fn v0_3_14_pre_003_websocket_reconnect_is_anchored_before_terminal_gap_projection() {
let (sender, receiver) = tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
@@ -3790,7 +3984,7 @@ fn v0_3_14_pre_003_websocket_reconnect_is_anchored_before_terminal_gap_projectio
assert!(!anchor.saw_overflow());
assert!(reporter.observe_websocket_continuity(crate::RawTransactionIngestSourceState::Active, 1, 0).is_ok());
let bounded = reporter.observe_websocket_post_incident_slot(105);
assert!(matches!(bounded, std::result::Result::Ok(true)));
assert!(matches!(bounded, std::result::Result::Ok(std::option::Option::Some((100, 105)))));
let anchor = match reporter.websocket_incident_anchor {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("bounded reconnect incident anchor missing"),
@@ -3822,7 +4016,7 @@ fn v0_3_14_pre_003_websocket_overflow_and_reconnect_storm_share_earliest_anchor(
assert_eq!(projection.source_reconnect_total(), 1);
assert_eq!(projection.source_continuity_gap_total(), 3);
let bounded = reporter.observe_websocket_post_incident_slot(201);
assert!(matches!(bounded, std::result::Result::Ok(true)));
assert!(matches!(bounded, std::result::Result::Ok(std::option::Option::Some((200, 201)))));
return;
}