// file: crates/ksp-worker-raw-transaction-ingest-lib/src/admission.rs // version: 3 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 { backpressure_wait_observed: bool, capacity: usize, receiver: tokio::sync::mpsc::Receiver, } 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) { let (sender, receiver) = tokio::sync::mpsc::channel(capacity); return (Self { backpressure_wait_observed: false, capacity, receiver }, sender); } /// Returns the latest receiver-side queue depth without consuming an ingress entry. #[must_use] pub(crate) fn queue_depth(&self) -> usize { return self.receiver.len(); } /// 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> { self.backpressure_wait_observed = self.receiver.len() == self.capacity; 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), }; } /// Takes and clears the source-neutral signal that the previous dequeue observed the bounded queue at full capacity. #[must_use] pub(crate) fn take_backpressure_wait_observed(&mut self) -> bool { let observed = self.backpressure_wait_observed; self.backpressure_wait_observed = false; return observed; } } fn canonicalize_ingress( expected_network: &ksp_store_lib::RawNetworkId, ingress: crate::RawTransactionIngress, ) -> ksp_core_lib::Result { 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;