From 887664f3c0742a7398b4168725b49e91100decab Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Sat, 12 Sep 2026 07:47:16 +0200 Subject: [PATCH] v0.3.14-pre.007 --- Cargo.toml | 4 +- .../src/continuity.rs | 39 ++- .../src/lib.rs | 4 +- .../src/runtime_resources.rs | 68 +++++- .../tests/hardening.rs | 31 ++- .../tests/release_completeness.rs | 24 +- .../unit_tests/continuity.rs | 22 +- .../unit_tests/runtime_resources.rs | 227 +++++++++++++++++- deltas/0.3.14/pre.007.md | 217 +++++++++++++++++ 9 files changed, 619 insertions(+), 17 deletions(-) create mode 100644 deltas/0.3.14/pre.007.md diff --git a/Cargo.toml b/Cargo.toml index c58f04a..f217ee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 573 +# version: 574 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"] [workspace.package] -version = "0.3.14-pre.6" +version = "0.3.14-pre.7" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs index 1c03bdf..b3209d6 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs -// version: 6 +// version: 7 /// Maximum number of slots admitted by one private continuity HTTP discovery window outside this module. pub(crate) const MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS: u64 = MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS; @@ -174,6 +174,43 @@ impl RawTransactionIngestGapRange { } } +/// Private run-local obligation for one transaction reference already observed by a live source. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct RawTransactionIngestKnownReferenceObligation { + commitment: ksp_onchain_transport_lib::SolanaCommitment, + reference: ksp_store_lib::RawTransactionReference, + slot: u64, +} + +impl crate::RawTransactionIngestKnownReferenceObligation { + /// Creates one known-reference obligation without inventing any absence or coverage proof. + pub(crate) fn new( + reference: ksp_store_lib::RawTransactionReference, + slot: u64, + commitment: ksp_onchain_transport_lib::SolanaCommitment, + ) -> ksp_core_lib::Result { + if commitment == ksp_onchain_transport_lib::SolanaCommitment::Processed { + return std::result::Result::Err(crate::runtime_error("continuity.known_reference_processed_unsupported")); + } + return std::result::Result::Ok(Self { commitment, reference, slot }); + } + + /// Returns the exact commitment under which this reference must be resolved. + pub(crate) const fn commitment(&self) -> ksp_onchain_transport_lib::SolanaCommitment { + return self.commitment; + } + + /// Returns the already-known canonical transaction reference. + pub(crate) const fn reference(&self) -> &ksp_store_lib::RawTransactionReference { + return &self.reference; + } + + /// Returns the already-observed slot associated with the reference. + pub(crate) const fn slot(&self) -> u64 { + return self.slot; + } +} + /// Private run-local anchor for one WebSocket continuity incident. /// /// The anchor never derives slots from wall-clock time. Its inclusive start is the latest slot actually observed by that source before Transport reported a diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs index 887ce88..7a03466 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs -// version: 30 +// version: 31 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -119,6 +119,8 @@ pub(crate) use self::continuity::RawTransactionIngestContinuityCapabilityDescrip pub(crate) use self::continuity::RawTransactionIngestContinuityContracts; /// Private provider-neutral coverage scope used by run-local continuity proof contracts. pub(crate) use self::continuity::RawTransactionIngestCoverageScope; +/// Private run-local obligation for one transaction reference already observed by a live source. +pub(crate) use self::continuity::RawTransactionIngestKnownReferenceObligation; /// Private run-local WebSocket incident anchor built only from observed source slots and safe Transport counters. pub(crate) use self::continuity::RawTransactionIngestWebSocketIncidentAnchor; /// Creates one terminal content-conflict error without copying conflicting material into diagnostics. diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs b/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs index cfa23bd..2998c69 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs -// version: 32 +// version: 33 use sha2::Digest; // rust-rules: trait-import @@ -4026,6 +4026,20 @@ struct RawTransactionIngestHydrationFetch { observed: RawTransactionIngestObservedTransaction, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum RawTransactionIngestKnownReferenceMissingDisposition { + AwaitCoverage, + PreferBlockSlot, +} + +enum RawTransactionIngestKnownReferenceHydrationResolution { + Available(crate::RawTransactionIngress), + Missing { + disposition: RawTransactionIngestKnownReferenceMissingDisposition, + obligation: crate::RawTransactionIngestKnownReferenceObligation, + }, +} + #[derive(Clone)] enum RawTransactionIngestSharedHydrationResult { Available(RawTransactionIngestObservedTransaction), @@ -4219,14 +4233,24 @@ impl RawTransactionIngestHydrationCoordinator { self.pending_signal_count -= pending.signals.len(); for pending_signal in pending.signals { let signal_slot = pending_signal.signal.slot; - let ingress = finalize_hydration(hydration, settings, pending_signal.signal, pending_signal.received_at, &fetched.observed); - let ingress = match ingress { + let resolution = resolve_known_reference_hydration(hydration, settings, pending_signal.signal, pending_signal.received_at, &fetched.observed); + let resolution = match resolution { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let ingress = match ingress { - std::option::Option::Some(value) => value, - std::option::Option::None => { + let ingress = match resolution { + RawTransactionIngestKnownReferenceHydrationResolution::Available(value) => value, + RawTransactionIngestKnownReferenceHydrationResolution::Missing { disposition, obligation } => { + if obligation.commitment() != hydration.commitment + || obligation.reference().network() != &hydration.network + || obligation.slot() != signal_slot + { + return std::result::Result::Err(crate::runtime_error("source.known_reference_obligation_mismatch")); + } + match disposition { + RawTransactionIngestKnownReferenceMissingDisposition::AwaitCoverage + | RawTransactionIngestKnownReferenceMissingDisposition::PreferBlockSlot => {}, + } if let std::result::Result::Err(error) = processing_frontier.settle_pending(signal_slot) { return std::result::Result::Err(error); } @@ -4389,6 +4413,38 @@ fn shared_hydration_result( }; } +fn resolve_known_reference_hydration( + hydration: &RawTransactionIngestHydrationContext, + settings: &crate::RawTransactionIngestSettings, + signal: RawTransactionIngestSourceSignal, + received_at: ksp_store_lib::RawTimestamp, + observed: &RawTransactionIngestObservedTransaction, +) -> ksp_core_lib::Result { + let reference = ksp_store_lib::RawTransactionReference::new(signal.network.clone(), signal.signature); + let obligation = match crate::RawTransactionIngestKnownReferenceObligation::new(reference, signal.slot, hydration.commitment) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let ingress = finalize_hydration(hydration, settings, signal, received_at, observed); + let ingress = match ingress { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if let std::option::Option::Some(value) = ingress { + return std::result::Result::Ok(RawTransactionIngestKnownReferenceHydrationResolution::Available(value)); + } + let block_slot_supported = match http_role_supports_rpc_method(&hydration.http_pool, &hydration.hydration_role, "getBlock", hydration.network.as_str()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let disposition = if block_slot_supported { + RawTransactionIngestKnownReferenceMissingDisposition::PreferBlockSlot + } else { + RawTransactionIngestKnownReferenceMissingDisposition::AwaitCoverage + }; + return std::result::Result::Ok(RawTransactionIngestKnownReferenceHydrationResolution::Missing { disposition, obligation }); +} + fn finalize_hydration( hydration: &RawTransactionIngestHydrationContext, settings: &crate::RawTransactionIngestSettings, diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs b/crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs index ac49fc1..5efbb20 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs @@ -1,7 +1,7 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs -// version: 30 +// version: 31 -//! External public, security, redaction and release-boundary hardening canaries through `v0.3.14-pre.006`. +//! External public, security, redaction and release-boundary hardening canaries through `v0.3.14-pre.007`. fn network(value: &'static str) -> std::option::Option { let result = ksp_store_lib::RawNetworkId::new(value); @@ -1116,3 +1116,30 @@ fn v0_3_14_pre_006_http_discovery_is_bounded_prefers_closed_range_and_never_uses assert!(!root.contains("repair"), "pre.006 leaked lower-case repair responsibility through crate root"); return; } + +#[test] +fn v0_3_14_pre_007_known_reference_hydration_reuses_one_registry_and_preserves_missing_obligation() { + let continuity = include_str!("../src/continuity.rs"); + let resources = include_str!("../src/runtime_resources.rs"); + let root = include_str!("../src/lib.rs"); + for required in [ + "RawTransactionIngestKnownReferenceObligation", + "known_reference_processed_unsupported", + "RawTransactionIngestKnownReferenceHydrationResolution", + "RawTransactionIngestKnownReferenceMissingDisposition", + "fetch_hydration_shared", + "resolve_known_reference_hydration", + "PreferBlockSlot", + "AwaitCoverage", + "get_transaction_observed", + "http_role_supports_rpc_method", + ] { + assert!(continuity.contains(required) || resources.contains(required), "required pre.007 known-reference guard missing: {required}"); + } + assert_eq!(resources.matches("struct RawTransactionIngestGlobalHydrationRegistry").count(), 1); + assert!(!resources.contains("KnownReferenceHydrationRegistry")); + assert!(!resources.contains("known_reference_retry")); + assert!(!root.contains("pub use self::continuity::RawTransactionIngestKnownReferenceObligation")); + assert!(!root.contains("repair"), "pre.007 leaked lower-case repair responsibility through crate root"); + return; +} diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs b/crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs index 15a7b44..b9b0860 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs @@ -1,7 +1,7 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs -// version: 25 +// version: 26 -//! Release-completeness canaries through the `v0.3.14-pre.006` bounded HTTP continuity-discovery tranche. +//! Release-completeness canaries through the `v0.3.14-pre.007` known-reference hydration tranche. #[test] fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> { @@ -302,3 +302,23 @@ fn v0_3_14_pre_006_bounded_http_discovery_canaries_are_present_without_backfill_ assert!(!root.contains("backfill")); return; } + +#[test] +fn v0_3_14_pre_007_known_reference_hydration_canaries_are_present_without_second_registry_or_public_surface_growth() { + let continuity_tests = include_str!("../unit_tests/continuity.rs"); + let hardening = include_str!("hardening.rs"); + let resource_tests = include_str!("../unit_tests/runtime_resources.rs"); + let root = include_str!("../src/lib.rs"); + for required in [ + "pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment", + "v0_3_14_pre_007_known_reference_hydration_reuses_global_registry_and_available_material", + "v0_3_14_pre_007_missing_known_reference_remains_open_without_block_capability", + "v0_3_14_pre_007_missing_known_reference_prefers_same_role_block_slot_when_supported", + ] { + assert!(continuity_tests.contains(required) || resource_tests.contains(required), "required pre.007 canary missing: {required}"); + } + assert!(hardening.contains("v0_3_14_pre_007_known_reference_hydration_reuses_one_registry_and_preserves_missing_obligation")); + assert!(!root.contains("pub use self::continuity::RawTransactionIngestKnownReferenceObligation")); + assert!(!root.contains("pub use self::runtime_resources::RawTransactionIngestKnownReference")); + return; +} diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs index 277eb53..2c99721 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs -// version: 4 +// version: 5 fn network() -> std::option::Option { return match ksp_store_lib::RawNetworkId::new("mainnet") { @@ -453,3 +453,23 @@ fn pre_005_coverage_epoch_ledger_is_bounded_monotone_and_capability_bound() { assert!(overflow.validate_invariants(std::slice::from_ref(&matching)).is_err()); return; } + +#[test] +fn pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment() { + let network = match network() { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let reference = ksp_store_lib::RawTransactionReference::new(network, ksp_store_lib::RawTransactionSignature::new([9_u8; 64])); + let obligation = + match crate::RawTransactionIngestKnownReferenceObligation::new(reference.clone(), 4242, ksp_onchain_transport_lib::SolanaCommitment::Confirmed) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("unexpected known-reference obligation failure: {error}"), + }; + assert_eq!(obligation.reference(), &reference); + assert_eq!(obligation.slot(), 4242); + assert_eq!(obligation.commitment(), ksp_onchain_transport_lib::SolanaCommitment::Confirmed); + let processed = crate::RawTransactionIngestKnownReferenceObligation::new(reference, 4242, ksp_onchain_transport_lib::SolanaCommitment::Processed); + assert!(processed.is_err()); + return; +} diff --git a/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs index 34366ab..1c72f0b 100644 --- a/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs +++ b/crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs @@ -1,5 +1,5 @@ // file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs -// version: 26 +// version: 27 fn grpc_endpoint(cluster: &str) -> std::option::Option { return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider"); @@ -450,6 +450,16 @@ const PRE_004_ZERO_SIGNATURE_TEXT: &str = "1111111111111111111111111111111111111 const PRE_004_ZERO_TRANSACTION_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; fn http_pool_for_url(url: &str, cluster: &str, endpoint_name: &str, provider: &str) -> std::option::Option { + return http_pool_for_url_with_request_kinds(url, cluster, endpoint_name, provider, &["get_transaction"]); +} + +fn http_pool_for_url_with_request_kinds( + url: &str, + cluster: &str, + endpoint_name: &str, + provider: &str, + request_kinds: &[&str], +) -> std::option::Option { let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse(url) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, @@ -457,7 +467,7 @@ fn http_pool_for_url(url: &str, cluster: &str, endpoint_name: &str, provider: &s let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new( ksp_onchain_transport_lib::HttpRoleName::new("hydration"), true, - std::vec![ksp_onchain_transport_lib::HttpRequestKind::new("get_transaction")], + request_kinds.iter().map(|value| return ksp_onchain_transport_lib::HttpRequestKind::new(*value)).collect(), 10, ksp_onchain_transport_lib::HttpRoleLimits::new( std::option::Option::None, @@ -517,6 +527,29 @@ fn signal_source_for_http_url_with_commitment( }; } +fn signal_source_for_http_url_with_request_kinds(url: &str, request_kinds: &[&str]) -> std::option::Option { + let endpoint = match grpc_endpoint("devnet") { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return std::option::Option::None, + }; + let request = match transaction_request(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed)) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let pool = match http_pool_for_url_with_request_kinds(url, "devnet", "http-hydration-fixture", "fixture-http-provider", request_kinds) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + return match crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration")) { + std::result::Result::Ok(value) => std::option::Option::Some(value), + std::result::Result::Err(_) => std::option::Option::None, + }; +} + fn standard_logs_source_for_http_url( url: &str, filter: ksp_onchain_transport_lib::SolanaLogsSubscribeFilter, @@ -3140,6 +3173,196 @@ fn v0_3_14_pre_006_with_limit_discovery_never_uses_tip_alone_as_tail_coverage_pr return; } +#[tokio::test(flavor = "current_thread")] +async fn v0_3_14_pre_007_known_reference_hydration_reuses_global_registry_and_available_material() { + let body = std::format!( + "{{\"jsonrpc\":\"2.0\",\"result\":{{\"slot\":42,\"blockTime\":1760000120,\"transaction\":[\"{}\",\"base64\"],\"meta\":null,\"version\":0,\"transactionIndex\":7}},\"id\":1}}", + PRE_004_ZERO_TRANSACTION_BASE64, + ); + let (url, server) = match serve_http_once(body) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let source = match signal_source_for_http_url(url.as_str()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let settings = match pre_004_settings() { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 42, 7, &["tx-fixture"], 0) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let hydration = source.hydration_context(); + let key = match super::hydration_key(&hydration, &signal) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let registry = std::sync::Arc::new(super::RawTransactionIngestGlobalHydrationRegistry::new(4, 1)); + let first = super::fetch_hydration_shared( + std::sync::Arc::clone(®istry), + hydration.http_pool.clone(), + hydration.hydration_role.clone(), + hydration.network.clone(), + key.clone(), + hydration.commitment, + ); + let second = super::fetch_hydration_shared( + std::sync::Arc::clone(®istry), + hydration.http_pool.clone(), + hydration.hydration_role.clone(), + hydration.network.clone(), + key, + hydration.commitment, + ); + let (first, second) = tokio::join!(first, second); + let first = match first { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert!(second.is_ok()); + let request = match server.join() { + std::result::Result::Ok(std::result::Result::Ok(value)) => value, + _ => return, + }; + assert!(request.contains("\"method\":\"getTransaction\"")); + let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_200_000) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let resolved = super::resolve_known_reference_hydration(&hydration, &settings, signal, received_at, &first.observed); + let ingress = match resolved { + std::result::Result::Ok(super::RawTransactionIngestKnownReferenceHydrationResolution::Available(value)) => value, + _ => return, + }; + let (mut admission, sender) = crate::RawTransactionAdmission::new(1); + if sender.send(ingress).await.is_err() { + return; + } + std::mem::drop(sender); + let acquisition = match admission.receive(settings.network()).await { + std::result::Result::Ok(std::option::Option::Some(value)) => value, + _ => return, + }; + assert_eq!(acquisition.transaction().slot(), 42); + assert_eq!(acquisition.transaction().reference().signature().as_bytes(), &[0_u8; 64]); + return; +} + +#[tokio::test(flavor = "current_thread")] +async fn v0_3_14_pre_007_missing_known_reference_remains_open_without_block_capability() { + let (url, server) = match serve_http_once("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":1}".to_owned()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let source = match signal_source_for_http_url(url.as_str()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let settings = match pre_004_settings() { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::TransactionStatus, 77, 0, &["status-fixture"], 0) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let hydration = source.hydration_context(); + let key = match super::hydration_key(&hydration, &signal) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let registry = std::sync::Arc::new(super::RawTransactionIngestGlobalHydrationRegistry::new(2, 1)); + let fetched = super::fetch_hydration_shared( + registry, + hydration.http_pool.clone(), + hydration.hydration_role.clone(), + hydration.network.clone(), + key, + hydration.commitment, + ) + .await; + let fetched = match fetched { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let _request = match server.join() { + std::result::Result::Ok(std::result::Result::Ok(value)) => value, + _ => return, + }; + let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_200_000) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let resolved = super::resolve_known_reference_hydration(&hydration, &settings, signal, received_at, &fetched.observed); + let (disposition, obligation) = match resolved { + std::result::Result::Ok(super::RawTransactionIngestKnownReferenceHydrationResolution::Missing { disposition, obligation }) => (disposition, obligation), + _ => return, + }; + assert_eq!(disposition, super::RawTransactionIngestKnownReferenceMissingDisposition::AwaitCoverage); + assert_eq!(obligation.slot(), 77); + assert_eq!(obligation.commitment(), ksp_onchain_transport_lib::SolanaCommitment::Confirmed); + assert_eq!(obligation.reference().signature().as_bytes(), &[0_u8; 64]); + return; +} + +#[tokio::test(flavor = "current_thread")] +async fn v0_3_14_pre_007_missing_known_reference_prefers_same_role_block_slot_when_supported() { + let (url, server) = match serve_http_once("{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":1}".to_owned()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let source = match signal_source_for_http_url_with_request_kinds(url.as_str(), &["get_transaction", "get_block"]) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let settings = match pre_004_settings() { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let signal = match pre_004_signal(&source, super::RawTransactionIngestSourceFamily::Transaction, 88, 0, &["tx-fixture"], 0) { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let hydration = source.hydration_context(); + let key = match super::hydration_key(&hydration, &signal) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let registry = std::sync::Arc::new(super::RawTransactionIngestGlobalHydrationRegistry::new(2, 1)); + let fetched = super::fetch_hydration_shared( + registry, + hydration.http_pool.clone(), + hydration.hydration_role.clone(), + hydration.network.clone(), + key, + hydration.commitment, + ) + .await; + let fetched = match fetched { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let _request = match server.join() { + std::result::Result::Ok(std::result::Result::Ok(value)) => value, + _ => return, + }; + let received_at = match ksp_store_lib::RawTimestamp::from_unix_millis(1_760_000_200_000) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let resolved = super::resolve_known_reference_hydration(&hydration, &settings, signal, received_at, &fetched.observed); + let (disposition, obligation) = match resolved { + std::result::Result::Ok(super::RawTransactionIngestKnownReferenceHydrationResolution::Missing { disposition, obligation }) => (disposition, obligation), + _ => return, + }; + assert_eq!(disposition, super::RawTransactionIngestKnownReferenceMissingDisposition::PreferBlockSlot); + assert_eq!(obligation.slot(), 88); + return; +} + #[test] fn v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions() { let cases = [ diff --git a/deltas/0.3.14/pre.007.md b/deltas/0.3.14/pre.007.md new file mode 100644 index 0000000..2bdbb61 --- /dev/null +++ b/deltas/0.3.14/pre.007.md @@ -0,0 +1,217 @@ + + + +# Delta `0.3.14-pre.007` — hydration des références connues + +## Base requise + +```text +0.3.14-pre.006 +workspace.package.version = 0.3.14-pre.6 +deltas/0.3.14/pre.006.md présent +``` + +## Gate de la base + +Le gate opérateur de `0.3.14-pre.006` est validé avant ouverture de cette tranche : + +```text +cargo fmt --all : PASS +cargo fmt --all -- --check : PASS +audit Rust workspace rules : PASS +audit Markdown tables : PASS +cargo check --workspace : PASS +cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS +cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS +``` + +Le gate Worker comprend notamment : + +```text +129 unit tests : PASS +cross_layer_completeness : 8 PASS +dependency_boundary : 19 PASS +hardening : 31 PASS +public_api : 20 PASS +release_completeness : 8 PASS +``` + +## Objectif + +Implémenter strictement la tranche `pre.007` du plan `035` : + +```text +réutiliser getTransaction observed pour une référence déjà connue +réutiliser le registre global de coalescence network/signature/commitment existant +ne créer aucun second registre d'hydration +représenter getTransaction = null comme une obligation Missing, jamais comme une preuve d'absence +conserver exactement le slot déjà observé avec la référence +préférer le bloc de ce slot lorsque le même rôle HTTP expose getBlock +ne pas déclencher encore la réconciliation du gap-ledger ni modifier le supervisor +ne pas ajouter de retry réseau Worker +``` + +## Obligation de référence connue + +`continuity.rs` matérialise un contrat privé : + +```text +RawTransactionIngestKnownReferenceObligation + reference : network + signature déjà observés + slot : slot déjà observé + commitment : Confirmed ou Finalized exact +``` + +Le contrat refuse `Processed` et ne contient aucun état « absent ». Il ne peut donc pas convertir une réponse HTTP `null` en preuve que la transaction n'existait pas. + +Cette obligation reste distincte de `TargetCoverage` : `KnownReferences` ne devient toujours jamais une configured target coverage. + +## Réutilisation de la coalescence globale + +Le chemin productif existant reste fondé sur : + +```text +RawTransactionIngestGlobalHydrationRegistry +RawTransactionIngestHydrationKey + network + signature + commitment +fetch_hydration_shared(...) +fetch_hydration(...) +HttpTransportPool::get_transaction_observed(...) +``` + +Aucun registre `KnownReferenceHydrationRegistry`, semaphore parallèle ou second fanout HTTP n'est ajouté. + +Deux demandes concurrentes portant la même clé de référence partagent donc toujours le leader/follower global déjà présent depuis `0.3.13`. + +## Résolution de l'hydration + +La qualification après `getTransaction observed` devient explicite : + +```text +Available(ingress) +Missing { + obligation, + disposition +} +``` + +`Available` réutilise exactement `finalize_hydration` et le pipeline central Common RAW -> admission -> Store existant. + +`Missing` conserve la référence et son slot. Il ne produit aucun ingress et ne ferme aucune obligation de continuité. + +Le coordinator nominal existant conserve son comportement stable jusqu'à `pre.008` : lorsqu'une notification live ordinaire hydrate vers `null`, son pending de processing est soldé comme auparavant. La nouvelle représentation `Missing` est toutefois la primitive réutilisable par la réconciliation ; `pre.008` décidera explicitement de la conservation dans le gap-ledger au lieu de confondre ce résultat avec une absence prouvée. + +## Préférence bloc du slot + +Après un `getTransaction = null`, aucune requête bloc n'est lancée automatiquement dans cette tranche. + +Le rôle HTTP exact est inspecté sans I/O supplémentaire : + +```text +getBlock supporté sur le même rôle + -> PreferBlockSlot + +getBlock non supporté + -> AwaitCoverage +``` + +`PreferBlockSlot` signifie uniquement que le slot déjà connu est la prochaine primitive de matériau à privilégier lors de la réconciliation. Cela n'autorise ni scan historique, ni broadening du scope filtré, ni ingestion de transactions inconnues du bloc. + +Le scan/discovery borné de `pre.006` et cette préférence de matériau restent séparés jusqu'à l'unification `pre.008`. + +## Tests ajoutés + +Les unit tests couvrent : + +```text +obligation connue conservant exactement reference/slot/commitment +rejet du commitment Processed +coalescence de deux hydrations concurrentes vers une seule requête getTransaction +résolution Available vers le pipeline Common RAW existant +getTransaction = null sans getBlock -> Missing/AwaitCoverage +getTransaction = null avec getBlock sur le même rôle -> Missing/PreferBlockSlot +``` + +Les canaris `hardening` et `release_completeness` garantissent en plus : + +```text +un seul RawTransactionIngestGlobalHydrationRegistry +aucun second KnownReferenceHydrationRegistry +réutilisation de fetch_hydration_shared et get_transaction_observed +aucune nouvelle surface publique +aucune responsabilité lower-case repair dans crate-root +``` + +## Hors périmètre inchangé + +```text +aucune conservation active du Missing dans le gap-ledger avant pre.008 +aucun getBlock fallback exécuté automatiquement dans cette tranche +aucune modification du supervisor source-loss +aucune modification de la health policy +aucun retry réseau Worker parallèle à Transport +aucune source/provider supplémentaire +aucun EARLY/shred adapter +aucun backfill historique caller-driven +``` + +## Fichiers ajoutés + +```text +deltas/0.3.14/pre.007.md +``` + +## Fichiers modifiés + +```text +Cargo.toml +crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs +crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs +crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs +crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs +crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs +crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs +crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs +``` + +## Fichiers supprimés + +```text +aucun +``` + +## Version Cargo + +Conformément à `VER-ID-009` : + +```text +header Cargo.toml : 573 -> 574 +workspace.package.version : 0.3.14-pre.6 -> 0.3.14-pre.7 +``` + +Versions des fichiers modifiés : + +```text +continuity.rs : 6 -> 7 +lib.rs : 30 -> 31 +runtime_resources.rs : 32 -> 33 +unit_tests/continuity.rs : 4 -> 5 +unit_tests/runtime_resources.rs : 26 -> 27 +tests/hardening.rs : 30 -> 31 +tests/release_completeness.rs : 25 -> 26 +``` + +## Validation exécutée dans l'environnement de préparation + +```text +python3 scripts/audit_rust_workspace_rules.py : PASS +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS +``` + +Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.008`. + +## Prochaine tranche + +`pre.008` : unification replay/redondance/discovery HTTP/hydration dans le ledger, calcul du continuity frontier et remplacement de la terminalité « first source failure » uniquement lorsqu'une `TargetCoverage` présente et future est explicitement prouvée ; aucun respawn Worker d'une source Transport.