v0.3.14-pre.007
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<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.
|
||||
///
|
||||
/// 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<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(
|
||||
hydration: &RawTransactionIngestHydrationContext,
|
||||
settings: &crate::RawTransactionIngestSettings,
|
||||
|
||||
@@ -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<ksp_store_lib::RawNetworkId> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ksp_store_lib::RawNetworkId> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
|
||||
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<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) {
|
||||
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<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(
|
||||
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 = [
|
||||
|
||||
217
deltas/0.3.14/pre.007.md
Normal file
217
deltas/0.3.14/pre.007.md
Normal 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.
|
||||
Reference in New Issue
Block a user