v0.3.13-pre.011

This commit is contained in:
2026-09-10 21:27:18 +02:00
parent f636169783
commit 06319655b0
15 changed files with 778 additions and 69 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
// version: 22
// version: 23
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
@@ -971,13 +971,13 @@ fn v0_3_13_pre_007_source_inventory_aggregates_frontier_and_lifecycle_conservati
source_key: [1_u8; 32],
};
let second = super::RawTransactionIngestSourceInventoryPublisher { aggregate_sender, entry_index: 1, inventory, source_key: [2_u8; 32] };
first.publish(
crate::RawTransactionIngestProcessingFrontierProjection::new(1, std::option::Option::Some(50), std::option::Option::Some(45)).with_source_continuity(
std::option::Option::Some(crate::RawTransactionIngestSourceState::Active),
1,
2,
0,
),
assert!(
first
.publish(
crate::RawTransactionIngestProcessingFrontierProjection::new(1, std::option::Option::Some(50), std::option::Option::Some(45))
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 2, 0),
)
.is_ok()
);
let first_aggregate = *aggregate_receiver.borrow();
assert_eq!(first_aggregate.hydration_pending(), 1);
@@ -988,13 +988,13 @@ fn v0_3_13_pre_007_source_inventory_aggregates_frontier_and_lifecycle_conservati
assert_eq!(first_aggregate.source_active(), 1);
assert_eq!(first_aggregate.source_reconnecting(), 0);
assert_eq!(first_aggregate.source_failed(), 0);
second.publish(
crate::RawTransactionIngestProcessingFrontierProjection::new(2, std::option::Option::Some(42), std::option::Option::Some(40)).with_source_continuity(
std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting),
3,
4,
1,
),
assert!(
second
.publish(
crate::RawTransactionIngestProcessingFrontierProjection::new(2, std::option::Option::Some(42), std::option::Option::Some(40))
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 3, 4, 1),
)
.is_ok()
);
let aggregate = *aggregate_receiver.borrow();
assert_eq!(aggregate.hydration_pending(), 3);
@@ -3146,3 +3146,204 @@ async fn v0_3_13_pre_009_global_hydration_permit_and_leader_cleanup_are_bounded_
assert!(matches!(resubscribed, std::result::Result::Ok((true, _))));
return;
}
#[test]
fn v0_3_13_pre_011_source_inventory_rejects_stale_identity_and_counter_exhaustion() {
let mut identity_inventory = super::RawTransactionIngestSourceInventory::new(std::vec![[1_u8; 32]]);
let mismatch = identity_inventory.update(0, [2_u8; 32], crate::RawTransactionIngestProcessingFrontierProjection::empty());
let mismatch = match mismatch {
std::result::Result::Ok(()) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(mismatch.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert_eq!(mismatch.context()[0].value(), "source.inventory_key_mismatch");
let missing = identity_inventory.update(1, [1_u8; 32], crate::RawTransactionIngestProcessingFrontierProjection::empty());
let missing = match missing {
std::result::Result::Ok(()) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(missing.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert_eq!(missing.context()[0].value(), "source.inventory_entry_missing");
let mut pending_inventory = super::RawTransactionIngestSourceInventory::new(std::vec![[3_u8; 32], [4_u8; 32]]);
assert!(
pending_inventory
.update(
0,
[3_u8; 32],
crate::RawTransactionIngestProcessingFrontierProjection::new(usize::MAX, std::option::Option::Some(10), std::option::Option::None),
)
.is_ok()
);
assert!(
pending_inventory
.update(1, [4_u8; 32], crate::RawTransactionIngestProcessingFrontierProjection::new(1, std::option::Option::Some(11), std::option::Option::None),)
.is_ok()
);
let pending_error = match pending_inventory.aggregate() {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(pending_error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED);
assert_eq!(pending_error.context()[0].value(), "hydration_pending");
let mut continuity_inventory = super::RawTransactionIngestSourceInventory::new(std::vec![[5_u8; 32], [6_u8; 32]]);
assert!(
continuity_inventory
.update(
0,
[5_u8; 32],
crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(20), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), u64::MAX, 0, 0),
)
.is_ok()
);
assert!(
continuity_inventory
.update(
1,
[6_u8; 32],
crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(21), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 0, 0),
)
.is_ok()
);
let continuity_error = match continuity_inventory.aggregate() {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(continuity_error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED);
assert_eq!(continuity_error.context()[0].value(), "source_reconnect_total");
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_011_aborted_hydration_leader_notifies_followers_and_releases_registry_key() {
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let registry = std::sync::Arc::new(super::RawTransactionIngestGlobalHydrationRegistry::new(2, 1));
let key = super::RawTransactionIngestHydrationKey {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed.as_str(),
network,
signature: ksp_store_lib::RawTransactionSignature::new([91_u8; 64]),
};
let subscribed = registry.subscribe_or_lead(&key);
let (leader, follower) = match subscribed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(leader);
let task_registry = std::sync::Arc::clone(&registry);
let task_key = key.clone();
let leader_task = tokio::spawn(async move {
let _guard = super::RawTransactionIngestHydrationLeaderGuard::new(task_registry, task_key);
return std::future::pending::<()>().await;
});
tokio::task::yield_now().await;
leader_task.abort();
let _joined = leader_task.await;
let published = follower.borrow().clone();
match published {
std::option::Option::Some(super::RawTransactionIngestSharedHydrationResult::Failed(code)) => {
assert_eq!(code, crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED);
},
_ => return,
}
assert!(matches!(registry.subscribe_or_lead(&key), std::result::Result::Ok((true, _))));
return;
}
struct Pre011SourceActiveGuard {
active: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
impl Pre011SourceActiveGuard {
fn new(active: std::sync::Arc<std::sync::atomic::AtomicUsize>) -> Self {
active.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
return Self { active };
}
}
impl std::ops::Drop for Pre011SourceActiveGuard {
fn drop(&mut self) {
self.active.fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
return;
}
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_011_aborting_outer_source_supervisor_aborts_nested_source_tasks() {
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 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;
});
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;
}
tokio::task::yield_now().await;
}
assert_eq!(active.load(std::sync::atomic::Ordering::Acquire), 1);
supervisor.abort();
let _joined = supervisor.await;
for _ in 0..64 {
if active.load(std::sync::atomic::Ordering::Acquire) == 0 {
break;
}
tokio::task::yield_now().await;
}
assert_eq!(active.load(std::sync::atomic::Ordering::Acquire), 0);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn v0_3_13_pre_011_stop_racing_ready_source_fault_preserves_fault_and_joins_sibling() {
let sibling_active = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
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 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"));
});
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 {
let _guard = Pre011SourceActiveGuard::new(sibling_active_for_task);
loop {
let changed = sibling_stop_receiver.changed().await;
if changed.is_err() || *sibling_stop_receiver.borrow() {
return std::result::Result::Ok(());
}
}
});
let supervisor = tokio::spawn(super::supervise_live_source_tasks(stop_receiver, source_stop_sender, children));
for _ in 0..64 {
if sibling_active.load(std::sync::atomic::Ordering::Acquire) == 1 {
break;
}
tokio::task::yield_now().await;
}
assert_eq!(sibling_active.load(std::sync::atomic::Ordering::Acquire), 1);
stop_sender.send_replace(true);
fault_gate.notify_one();
let result = match supervisor.await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let error = match result {
std::result::Result::Ok(()) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(error.context()[0].value(), "test.pre_011_ready_source_fault");
assert_eq!(sibling_active.load(std::sync::atomic::Ordering::Acquire), 0);
return;
}