v0.3.14-pre.007

This commit is contained in:
2026-09-12 07:47:16 +02:00
parent ae712eae64
commit 887664f3c0
9 changed files with 619 additions and 17 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 573 # version: 574
[workspace] [workspace]
resolver = "3" 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"] 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] [workspace.package]
version = "0.3.14-pre.6" version = "0.3.14-pre.7"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs // 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. /// 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; 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<Self> {
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. /// 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 /// 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

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 30 // version: 31
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -119,6 +119,8 @@ pub(crate) use self::continuity::RawTransactionIngestContinuityCapabilityDescrip
pub(crate) use self::continuity::RawTransactionIngestContinuityContracts; pub(crate) use self::continuity::RawTransactionIngestContinuityContracts;
/// Private provider-neutral coverage scope used by run-local continuity proof contracts. /// Private provider-neutral coverage scope used by run-local continuity proof contracts.
pub(crate) use self::continuity::RawTransactionIngestCoverageScope; 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. /// Private run-local WebSocket incident anchor built only from observed source slots and safe Transport counters.
pub(crate) use self::continuity::RawTransactionIngestWebSocketIncidentAnchor; pub(crate) use self::continuity::RawTransactionIngestWebSocketIncidentAnchor;
/// Creates one terminal content-conflict error without copying conflicting material into diagnostics. /// Creates one terminal content-conflict error without copying conflicting material into diagnostics.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
// version: 32 // version: 33
use sha2::Digest; // rust-rules: trait-import use sha2::Digest; // rust-rules: trait-import
@@ -4026,6 +4026,20 @@ struct RawTransactionIngestHydrationFetch {
observed: RawTransactionIngestObservedTransaction, 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)] #[derive(Clone)]
enum RawTransactionIngestSharedHydrationResult { enum RawTransactionIngestSharedHydrationResult {
Available(RawTransactionIngestObservedTransaction), Available(RawTransactionIngestObservedTransaction),
@@ -4219,14 +4233,24 @@ impl RawTransactionIngestHydrationCoordinator {
self.pending_signal_count -= pending.signals.len(); self.pending_signal_count -= pending.signals.len();
for pending_signal in pending.signals { for pending_signal in pending.signals {
let signal_slot = pending_signal.signal.slot; let signal_slot = pending_signal.signal.slot;
let ingress = finalize_hydration(hydration, settings, pending_signal.signal, pending_signal.received_at, &fetched.observed); let resolution = resolve_known_reference_hydration(hydration, settings, pending_signal.signal, pending_signal.received_at, &fetched.observed);
let ingress = match ingress { let resolution = match resolution {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let ingress = match ingress { let ingress = match resolution {
std::option::Option::Some(value) => value, RawTransactionIngestKnownReferenceHydrationResolution::Available(value) => value,
std::option::Option::None => { 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) { if let std::result::Result::Err(error) = processing_frontier.settle_pending(signal_slot) {
return std::result::Result::Err(error); 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<RawTransactionIngestKnownReferenceHydrationResolution> {
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( fn finalize_hydration(
hydration: &RawTransactionIngestHydrationContext, hydration: &RawTransactionIngestHydrationContext,
settings: &crate::RawTransactionIngestSettings, settings: &crate::RawTransactionIngestSettings,

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs // 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<ksp_store_lib::RawNetworkId> { fn network(value: &'static str) -> std::option::Option<ksp_store_lib::RawNetworkId> {
let result = ksp_store_lib::RawNetworkId::new(value); 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"); assert!(!root.contains("repair"), "pre.006 leaked lower-case repair responsibility through crate root");
return; 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;
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs // 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] #[test]
fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> { 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")); assert!(!root.contains("backfill"));
return; 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;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
// version: 4 // version: 5
fn network() -> std::option::Option<ksp_store_lib::RawNetworkId> { fn network() -> std::option::Option<ksp_store_lib::RawNetworkId> {
return match ksp_store_lib::RawNetworkId::new("mainnet") { 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()); assert!(overflow.validate_invariants(std::slice::from_ref(&matching)).is_err());
return; 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;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs // 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<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> { fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider"); 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"; 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<ksp_onchain_transport_lib::HttpTransportPool> { fn http_pool_for_url(url: &str, cluster: &str, endpoint_name: &str, provider: &str) -> std::option::Option<ksp_onchain_transport_lib::HttpTransportPool> {
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<ksp_onchain_transport_lib::HttpTransportPool> {
let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse(url) { let url = match ksp_onchain_transport_lib::HttpEndpointUrl::parse(url) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None, 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( let role = ksp_onchain_transport_lib::HttpEndpointRoleSettings::new(
ksp_onchain_transport_lib::HttpRoleName::new("hydration"), ksp_onchain_transport_lib::HttpRoleName::new("hydration"),
true, 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, 10,
ksp_onchain_transport_lib::HttpRoleLimits::new( ksp_onchain_transport_lib::HttpRoleLimits::new(
std::option::Option::None, 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<crate::RawTransactionIngestYellowstoneSource> {
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( fn standard_logs_source_for_http_url(
url: &str, url: &str,
filter: ksp_onchain_transport_lib::SolanaLogsSubscribeFilter, 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; 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(&registry),
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(&registry),
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] #[test]
fn v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions() { fn v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions() {
let cases = [ let cases = [

217
deltas/0.3.14/pre.007.md Normal file
View File

@@ -0,0 +1,217 @@
<!-- file: deltas/0.3.14/pre.007.md -->
<!-- version: 1 -->
# 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.