v0.3.11-pre.006
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/admission.rs
|
||||
// version: 1
|
||||
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
const RAW_TRANSACTION_INGEST_OBSERVATION_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.observation.v1\0";
|
||||
|
||||
/// Crate-private source-neutral ingress admitted by the central bounded Worker queue.
|
||||
pub(crate) struct RawTransactionIngress {
|
||||
/// Complete Common RAW material supplied by one private source task.
|
||||
pub(crate) material: ksp_raw_transaction_lib::RawTransactionMaterial,
|
||||
/// Logical network expected to match both Worker settings and canonical material.
|
||||
pub(crate) network: ksp_store_lib::RawNetworkId,
|
||||
/// Safe source-independent provenance attached to the acquisition observation.
|
||||
pub(crate) provenance: ksp_store_lib::RawAcquisitionProvenance,
|
||||
/// Opaque deterministic source-owned key material used only for Worker observation-key derivation.
|
||||
pub(crate) source_key: [u8; 32],
|
||||
}
|
||||
|
||||
/// Receiver side of the bounded central RAW transaction admission queue owned by the Worker supervisor.
|
||||
pub(crate) struct RawTransactionAdmission {
|
||||
receiver: tokio::sync::mpsc::Receiver<crate::RawTransactionIngress>,
|
||||
}
|
||||
|
||||
impl crate::RawTransactionAdmission {
|
||||
/// Creates one bounded admission queue and returns its crate-private source sender.
|
||||
#[must_use]
|
||||
pub(crate) fn new(capacity: usize) -> (crate::RawTransactionAdmission, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) {
|
||||
let (sender, receiver) = tokio::sync::mpsc::channel(capacity);
|
||||
return (Self { receiver }, sender);
|
||||
}
|
||||
|
||||
/// Closes new admissions while preserving already queued ingress for deterministic drain.
|
||||
pub(crate) fn close(&mut self) {
|
||||
self.receiver.close();
|
||||
return;
|
||||
}
|
||||
|
||||
/// Receives and converts one queued ingress into a common RAW transaction acquisition.
|
||||
pub(crate) async fn receive(
|
||||
&mut self,
|
||||
expected_network: &ksp_store_lib::RawNetworkId,
|
||||
) -> ksp_core_lib::Result<std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition>> {
|
||||
let ingress = match self.receiver.recv().await {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
let acquisition = canonicalize_ingress(expected_network, ingress);
|
||||
return match acquisition {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_ingress(
|
||||
expected_network: &ksp_store_lib::RawNetworkId,
|
||||
ingress: crate::RawTransactionIngress,
|
||||
) -> ksp_core_lib::Result<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
||||
if &ingress.network != expected_network {
|
||||
return std::result::Result::Err(crate::runtime_error("admission.network_mismatch"));
|
||||
}
|
||||
let transaction = ksp_raw_transaction_lib::canonicalize_raw_transaction(ingress.material);
|
||||
let transaction = match transaction {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("admission.canonicalization_invalid")),
|
||||
};
|
||||
if transaction.reference().network() != expected_network {
|
||||
return std::result::Result::Err(crate::runtime_error("admission.material_network_mismatch"));
|
||||
}
|
||||
let observation_key = observation_key(transaction.reference(), &ingress.source_key);
|
||||
return std::result::Result::Ok(ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(transaction, observation_key, ingress.provenance));
|
||||
}
|
||||
|
||||
fn hash_bytes(hasher: &mut sha2::Sha256, value: &[u8]) {
|
||||
hasher.update((value.len() as u64).to_be_bytes());
|
||||
hasher.update(value);
|
||||
return;
|
||||
}
|
||||
|
||||
fn observation_key(reference: &ksp_store_lib::RawTransactionReference, source_key: &[u8; 32]) -> ksp_store_lib::RawObservationKey {
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(RAW_TRANSACTION_INGEST_OBSERVATION_DOMAIN);
|
||||
hash_bytes(&mut hasher, reference.network().as_str().as_bytes());
|
||||
hash_bytes(&mut hasher, reference.signature().as_bytes());
|
||||
hash_bytes(&mut hasher, source_key);
|
||||
let bytes: [u8; 32] = hasher.finalize().into();
|
||||
return ksp_store_lib::RawObservationKey::new(bytes);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/admission.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -8,10 +8,11 @@
|
||||
//! Source-neutral runtime foundation for continuous KSP RAW transaction ingestion.
|
||||
//!
|
||||
//! This tranche owns the concrete Worker family identity, validated technical settings
|
||||
//! and the caller-runtime-owned lifecycle with private child-task supervision. Bounded
|
||||
//! admission, persistence and latest-value snapshots remain in their dedicated prereleases; no
|
||||
//! live source or Transport dependency exists here.
|
||||
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also
|
||||
//! owns bounded source-neutral admission plus common RAW canonicalization/assembly; persistence and
|
||||
//! latest-value snapshots remain in later prereleases, and no live source or Transport dependency exists.
|
||||
|
||||
mod admission;
|
||||
mod error;
|
||||
mod identity;
|
||||
mod runtime;
|
||||
@@ -50,6 +51,10 @@ pub use self::settings::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT;
|
||||
/// Validated source-neutral runtime settings for one continuous RAW transaction ingest Worker.
|
||||
pub use self::settings::RawTransactionIngestSettings;
|
||||
|
||||
/// Receiver-side owner of the private bounded RAW transaction admission queue.
|
||||
pub(crate) use self::admission::RawTransactionAdmission;
|
||||
/// Crate-private source-neutral ingress sent through the bounded central admission queue.
|
||||
pub(crate) use self::admission::RawTransactionIngress;
|
||||
/// Creates one runtime-domain error without copying runtime/provider/Store values into diagnostics.
|
||||
pub(crate) use self::error::runtime_error;
|
||||
/// Creates one settings-domain error without copying caller-supplied values into diagnostics.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
/// Runtime-neutral boxed future resolving after one RAW transaction ingest Worker has fully reached a terminal lifecycle state.
|
||||
pub type RawTransactionIngestTerminalFuture<'a> =
|
||||
@@ -85,6 +85,19 @@ fn current_runtime_handle() -> ksp_core_lib::Result<tokio::runtime::Handle> {
|
||||
};
|
||||
}
|
||||
|
||||
async fn drain_admission(settings: &crate::RawTransactionIngestSettings, admission: &mut crate::RawTransactionAdmission) -> bool {
|
||||
let mut clean = true;
|
||||
admission.close();
|
||||
loop {
|
||||
let received = admission.receive(settings.network()).await;
|
||||
match received {
|
||||
std::result::Result::Ok(std::option::Option::Some(_acquisition)) => {},
|
||||
std::result::Result::Ok(std::option::Option::None) => return clean,
|
||||
std::result::Result::Err(_) => clean = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn drain_children(children: &mut tokio::task::JoinSet<()>) -> bool {
|
||||
let mut clean = true;
|
||||
while let std::option::Option::Some(joined) = children.join_next().await {
|
||||
@@ -119,13 +132,16 @@ fn finish_stopped(lifecycle: &mut ksp_worker_api::WorkerLifecycle, sender: &toki
|
||||
}
|
||||
|
||||
async fn run_supervisor<Spawner>(
|
||||
settings: crate::RawTransactionIngestSettings,
|
||||
mut lifecycle: ksp_worker_api::WorkerLifecycle,
|
||||
_store_guard: std::option::Option<std::sync::Arc<ksp_store_lib::Store>>,
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
terminal_sender: tokio::sync::watch::Sender<ksp_worker_api::WorkerState>,
|
||||
source_spawner: Spawner,
|
||||
) where
|
||||
Spawner: FnOnce(&mut tokio::task::JoinSet<()>, tokio::sync::watch::Receiver<bool>) + std::marker::Send + 'static,
|
||||
Spawner: FnOnce(&mut tokio::task::JoinSet<()>, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>)
|
||||
+ std::marker::Send
|
||||
+ 'static,
|
||||
{
|
||||
if *stop_receiver.borrow() {
|
||||
finish_stopped(&mut lifecycle, &terminal_sender);
|
||||
@@ -137,9 +153,12 @@ async fn run_supervisor<Spawner>(
|
||||
}
|
||||
terminal_sender.send_replace(lifecycle.state());
|
||||
let mut children = tokio::task::JoinSet::new();
|
||||
source_spawner(&mut children, stop_receiver.clone());
|
||||
let supervised_clean = supervise_until_stop(&mut stop_receiver, &mut children).await;
|
||||
if !supervised_clean {
|
||||
let (mut admission, admission_sender) = crate::RawTransactionAdmission::new(settings.admission_queue_capacity());
|
||||
source_spawner(&mut children, stop_receiver.clone(), admission_sender);
|
||||
let supervised_clean = supervise_until_stop(&settings, &mut stop_receiver, &mut children, &mut admission).await;
|
||||
let admission_clean = drain_admission(&settings, &mut admission).await;
|
||||
let children_clean = drain_children(&mut children).await;
|
||||
if !supervised_clean || !admission_clean || !children_clean {
|
||||
finish_faulted(&mut lifecycle, &terminal_sender);
|
||||
return;
|
||||
}
|
||||
@@ -152,7 +171,7 @@ fn start_foundation(
|
||||
runtime: tokio::runtime::Handle,
|
||||
store_guard: std::option::Option<std::sync::Arc<ksp_store_lib::Store>>,
|
||||
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle> {
|
||||
return start_foundation_with_source_spawner(settings, runtime, store_guard, |_children, _stop_receiver| {});
|
||||
return start_foundation_with_source_spawner(settings, runtime, store_guard, |_children, _stop_receiver, _admission_sender| {});
|
||||
}
|
||||
|
||||
fn start_foundation_with_source_spawner<Spawner>(
|
||||
@@ -162,7 +181,9 @@ fn start_foundation_with_source_spawner<Spawner>(
|
||||
source_spawner: Spawner,
|
||||
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
|
||||
where
|
||||
Spawner: FnOnce(&mut tokio::task::JoinSet<()>, tokio::sync::watch::Receiver<bool>) + std::marker::Send + 'static,
|
||||
Spawner: FnOnce(&mut tokio::task::JoinSet<()>, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>)
|
||||
+ std::marker::Send
|
||||
+ 'static,
|
||||
{
|
||||
let kind = match ksp_worker_api::WorkerKindCode::new(crate::RAW_TRANSACTION_INGEST_WORKER_KIND_CODE) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -176,17 +197,23 @@ where
|
||||
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
|
||||
let (terminal_sender, terminal_receiver) = tokio::sync::watch::channel(lifecycle.state());
|
||||
let handle = crate::RawTransactionIngestHandle { stop_sender, stop_token, terminal_receiver };
|
||||
std::mem::drop(runtime.spawn(run_supervisor(lifecycle, store_guard, stop_receiver, terminal_sender, source_spawner)));
|
||||
std::mem::drop(runtime.spawn(run_supervisor(settings, lifecycle, store_guard, stop_receiver, terminal_sender, source_spawner)));
|
||||
return std::result::Result::Ok(handle);
|
||||
}
|
||||
|
||||
async fn supervise_until_stop(stop_receiver: &mut tokio::sync::watch::Receiver<bool>, children: &mut tokio::task::JoinSet<()>) -> bool {
|
||||
async fn supervise_until_stop(
|
||||
settings: &crate::RawTransactionIngestSettings,
|
||||
stop_receiver: &mut tokio::sync::watch::Receiver<bool>,
|
||||
children: &mut tokio::task::JoinSet<()>,
|
||||
admission: &mut crate::RawTransactionAdmission,
|
||||
) -> bool {
|
||||
let mut admission_open = true;
|
||||
let mut clean = true;
|
||||
loop {
|
||||
if *stop_receiver.borrow() {
|
||||
break;
|
||||
}
|
||||
if children.is_empty() {
|
||||
if children.is_empty() && !admission_open {
|
||||
let changed = stop_receiver.changed().await;
|
||||
if changed.is_err() || *stop_receiver.borrow() {
|
||||
break;
|
||||
@@ -200,15 +227,21 @@ async fn supervise_until_stop(stop_receiver: &mut tokio::sync::watch::Receiver<b
|
||||
break;
|
||||
}
|
||||
}
|
||||
joined = children.join_next() => {
|
||||
joined = children.join_next(), if !children.is_empty() => {
|
||||
if let std::option::Option::Some(std::result::Result::Err(_)) = joined {
|
||||
clean = false;
|
||||
}
|
||||
}
|
||||
received = admission.receive(settings.network()), if admission_open => {
|
||||
match received {
|
||||
std::result::Result::Ok(std::option::Option::Some(_acquisition)) => {},
|
||||
std::result::Result::Ok(std::option::Option::None) => admission_open = false,
|
||||
std::result::Result::Err(_) => clean = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let drained_clean = drain_children(children).await;
|
||||
return clean && drained_clean;
|
||||
return clean;
|
||||
}
|
||||
|
||||
fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
|
||||
|
||||
@@ -52,22 +52,37 @@ fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_source_surface_owns_private_supervisor_without_admission_persistence_or_live_sources() {
|
||||
fn pre_006_source_surface_opens_bounded_admission_and_common_raw_without_persistence_or_live_sources() {
|
||||
let root = include_str!("../src/lib.rs");
|
||||
let runtime = include_str!("../src/runtime.rs");
|
||||
for required in ["tokio::task::JoinSet", "run_supervisor", "supervise_until_stop", "drain_children", "start_foundation_with_source_spawner"] {
|
||||
assert!(runtime.contains(required), "required pre.005 private supervisor contract missing: {required}");
|
||||
let admission = include_str!("../src/admission.rs");
|
||||
for required in [
|
||||
"tokio::task::JoinSet",
|
||||
"tokio::sync::mpsc::channel",
|
||||
"canonicalize_raw_transaction",
|
||||
"assemble_raw_transaction_acquisition",
|
||||
"ksp.raw_transaction_ingest.observation.v1",
|
||||
"RawTransactionIngress",
|
||||
] {
|
||||
assert!(
|
||||
root.contains(required) || runtime.contains(required) || admission.contains(required),
|
||||
"required pre.006 bounded admission/common RAW contract missing: {required}"
|
||||
);
|
||||
}
|
||||
for forbidden in [
|
||||
"pub use self::runtime::JoinSet",
|
||||
"mpsc::",
|
||||
"canonicalize_raw_transaction",
|
||||
"pub use self::admission::RawTransactionAdmission",
|
||||
"pub use self::admission::RawTransactionIngress",
|
||||
"unbounded_channel",
|
||||
"persist_raw_transaction",
|
||||
"RawTransactionAcquisitionMode",
|
||||
"ksp_onchain_transport_lib::",
|
||||
"RawTransactionIngestSnapshot",
|
||||
"WorkerSnapshotSource",
|
||||
] {
|
||||
assert!(!root.contains(forbidden) && !runtime.contains(forbidden), "pre.005 opened later runtime scope too early: {forbidden}");
|
||||
assert!(
|
||||
!root.contains(forbidden) && !runtime.contains(forbidden) && !admission.contains(forbidden),
|
||||
"pre.006 opened later runtime scope too early: {forbidden}"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
|
||||
// version: 1
|
||||
|
||||
fn material(
|
||||
network: &str,
|
||||
signature_byte: u8,
|
||||
transaction_data: &str,
|
||||
) -> std::option::Option<(ksp_store_lib::RawNetworkId, ksp_raw_transaction_lib::RawTransactionMaterial)> {
|
||||
let network = match ksp_store_lib::RawNetworkId::new(network) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let signature = ksp_store_lib::RawTransactionSignature::new([signature_byte; 64]);
|
||||
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
|
||||
network.clone(),
|
||||
signature,
|
||||
42,
|
||||
std::option::Option::Some(1_700_000_000),
|
||||
transaction_data,
|
||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||
);
|
||||
return std::option::Option::Some((network, material));
|
||||
}
|
||||
|
||||
fn provenance() -> std::option::Option<ksp_store_lib::RawAcquisitionProvenance> {
|
||||
let provider = match ksp_store_lib::RawProvenanceCode::new("deterministic-harness") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let protocol = match ksp_store_lib::RawProvenanceCode::new("internal") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let method = match ksp_store_lib::RawProvenanceCode::new("admission") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_700_000_000_000) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
return std::option::Option::Some(ksp_store_lib::RawAcquisitionProvenance::new(
|
||||
provider,
|
||||
protocol,
|
||||
method,
|
||||
ksp_store_lib::RawAcquisitionOrigin::Live,
|
||||
received_at,
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn pre_006_bounded_admission_applies_async_backpressure_until_one_slot_is_consumed() {
|
||||
let (network, first_material) = match material("mainnet", 1, "AQID") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let (_, second_material) = match material("mainnet", 2, "BAUG") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let first_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let second_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
|
||||
let first_ingress = crate::RawTransactionIngress {
|
||||
material: first_material,
|
||||
network: network.clone(),
|
||||
provenance: first_provenance,
|
||||
source_key: [1; 32],
|
||||
};
|
||||
assert!(sender.send(first_ingress).await.is_ok());
|
||||
let second_sender = sender.clone();
|
||||
let second_network = network.clone();
|
||||
let completed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let completed_task = std::sync::Arc::clone(&completed);
|
||||
let second_task = tokio::spawn(async move {
|
||||
let second_ingress = crate::RawTransactionIngress {
|
||||
material: second_material,
|
||||
network: second_network,
|
||||
provenance: second_provenance,
|
||||
source_key: [2; 32],
|
||||
};
|
||||
let admitted = second_sender.send(second_ingress).await.is_ok();
|
||||
completed_task.store(admitted, std::sync::atomic::Ordering::Release);
|
||||
return admitted;
|
||||
});
|
||||
tokio::task::yield_now().await;
|
||||
assert!(!completed.load(std::sync::atomic::Ordering::Acquire));
|
||||
let first = admission.receive(&network).await;
|
||||
assert!(matches!(first, std::result::Result::Ok(std::option::Option::Some(_))));
|
||||
for _ in 0..64 {
|
||||
if completed.load(std::sync::atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
assert!(completed.load(std::sync::atomic::Ordering::Acquire));
|
||||
let joined = second_task.await;
|
||||
assert!(matches!(joined, std::result::Result::Ok(true)));
|
||||
let second = admission.receive(&network).await;
|
||||
assert!(matches!(second, std::result::Result::Ok(std::option::Option::Some(_))));
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn pre_006_stop_preempts_a_blocked_sender_without_silent_post_stop_admission() {
|
||||
let (network, first_material) = match material("mainnet", 3, "BwgJ") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let (_, second_material) = match material("mainnet", 4, "CgsM") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let first_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let second_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
|
||||
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
|
||||
let first_ingress = crate::RawTransactionIngress {
|
||||
material: first_material,
|
||||
network: network.clone(),
|
||||
provenance: first_provenance,
|
||||
source_key: [3; 32],
|
||||
};
|
||||
assert!(sender.send(first_ingress).await.is_ok());
|
||||
let blocked_sender = sender.clone();
|
||||
let blocked_network = network.clone();
|
||||
let blocked_task = tokio::spawn(async move {
|
||||
let mut blocked_stop = stop_receiver;
|
||||
let blocked_ingress = crate::RawTransactionIngress {
|
||||
material: second_material,
|
||||
network: blocked_network,
|
||||
provenance: second_provenance,
|
||||
source_key: [4; 32],
|
||||
};
|
||||
return tokio::select! {
|
||||
biased;
|
||||
_changed = blocked_stop.changed() => false
|
||||
result = blocked_sender.send(blocked_ingress) => result.is_ok()
|
||||
};
|
||||
});
|
||||
tokio::task::yield_now().await;
|
||||
stop_sender.send_replace(true);
|
||||
let blocked = blocked_task.await;
|
||||
assert!(matches!(blocked, std::result::Result::Ok(false)));
|
||||
let first = admission.receive(&network).await;
|
||||
assert!(matches!(first, std::result::Result::Ok(std::option::Option::Some(_))));
|
||||
admission.close();
|
||||
let end = admission.receive(&network).await;
|
||||
assert!(matches!(end, std::result::Result::Ok(std::option::Option::None)));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_006_observation_key_domain_and_common_raw_assembly_golden_are_exact() {
|
||||
let (network, material) = match material("mainnet", 7, "AQID") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let ingress = crate::RawTransactionIngress { material, network: network.clone(), provenance: provenance.clone(), source_key: [9; 32] };
|
||||
let acquisition = super::canonicalize_ingress(&network, ingress);
|
||||
assert!(acquisition.is_ok());
|
||||
let acquisition = match acquisition {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(acquisition.transaction().reference().network(), &network);
|
||||
assert_eq!(acquisition.transaction().reference().signature().as_bytes(), &[7; 64]);
|
||||
assert_eq!(acquisition.transaction().slot(), 42);
|
||||
assert_eq!(acquisition.transaction().payload().format_id().as_str(), ksp_raw_transaction_lib::RAW_TRANSACTION_FORMAT_ID);
|
||||
assert_eq!(acquisition.transaction().payload().format_version(), ksp_raw_transaction_lib::RAW_TRANSACTION_FORMAT_VERSION);
|
||||
assert_eq!(acquisition.transaction().payload().bytes(), br#"{"transaction":["AQID","base64"]}"#);
|
||||
assert_eq!(acquisition.observation().provenance(), &provenance);
|
||||
assert_eq!(acquisition.observation().transaction(), acquisition.transaction().reference());
|
||||
assert_eq!(
|
||||
acquisition.observation().observation_key().as_bytes(),
|
||||
&[
|
||||
248, 209, 97, 220, 168, 168, 167, 42, 13, 161, 16, 252, 135, 136, 46, 231, 19, 104, 72, 70, 28, 106, 163, 43, 9, 227, 10, 97, 136, 185, 93, 204,
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_006_network_guards_reject_ingress_or_material_mismatch_without_echoing_values() {
|
||||
let (mainnet, mainnet_material) = match material("mainnet", 8, "DQ4P") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let (devnet, devnet_material) = match material("devnet", 9, "EBES") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let first_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let second_provenance = match provenance() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let wrong_ingress = crate::RawTransactionIngress { material: mainnet_material, network: devnet, provenance: first_provenance, source_key: [10; 32] };
|
||||
let wrong_ingress = super::canonicalize_ingress(&mainnet, wrong_ingress);
|
||||
assert!(wrong_ingress.is_err());
|
||||
let wrong_material = crate::RawTransactionIngress {
|
||||
material: devnet_material,
|
||||
network: mainnet.clone(),
|
||||
provenance: second_provenance,
|
||||
source_key: [11; 32],
|
||||
};
|
||||
let wrong_material = super::canonicalize_ingress(&mainnet, wrong_material);
|
||||
assert!(wrong_material.is_err());
|
||||
for result in [wrong_ingress, wrong_material] {
|
||||
let error = match result {
|
||||
std::result::Result::Ok(_) => continue,
|
||||
std::result::Result::Err(value) => value,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
||||
let rendered = std::format!("{error:?}");
|
||||
assert!(!rendered.contains("mainnet"));
|
||||
assert!(!rendered.contains("devnet"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
struct ActiveTaskGuard {
|
||||
active: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||
@@ -166,7 +166,7 @@ async fn pre_005_supervisor_joins_all_cooperative_source_tasks_before_terminal()
|
||||
settings,
|
||||
tokio::runtime::Handle::current(),
|
||||
std::option::Option::None,
|
||||
move |children, stop_receiver| {
|
||||
move |children, stop_receiver, _admission_sender| {
|
||||
for _ in 0..3 {
|
||||
let active = std::sync::Arc::clone(&source_active);
|
||||
let mut child_stop = stop_receiver.clone();
|
||||
@@ -211,7 +211,7 @@ async fn pre_005_supervisor_reaps_completed_source_task_and_still_joins_live_chi
|
||||
settings,
|
||||
tokio::runtime::Handle::current(),
|
||||
std::option::Option::None,
|
||||
move |children, stop_receiver| {
|
||||
move |children, stop_receiver, _admission_sender| {
|
||||
let completed = std::sync::Arc::clone(&source_completed);
|
||||
let _completed_abort_handle = children.spawn(async move {
|
||||
completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
||||
|
||||
Reference in New Issue
Block a user