v0.3.12-pre.003

This commit is contained in:
2026-09-09 07:39:42 +02:00
parent 91438e8214
commit fa720d0054
9 changed files with 898 additions and 16 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 520
# version: 521
[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.12-pre.2"
version = "0.3.12-pre.3"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 9
// version: 10
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -11,7 +11,8 @@
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also
//! owns bounded source-neutral admission, common RAW canonicalization/assembly and backend-neutral
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API;
//! the first validated Yellowstone/HTTP runtime-resource contract exists, but no live stream is opened yet.
//! the first validated Yellowstone/HTTP runtime-resource contract exists and deterministically projects
//! Transaction/TransactionStatus updates into private source-neutral signals, but no live stream is opened yet.
mod admission;
mod error;

View File

@@ -1,5 +1,145 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
// version: 1
// version: 2
use sha2::Digest; // rust-rules: trait-import
const RAW_TRANSACTION_INGEST_YELLOWSTONE_FILTER_FINGERPRINT_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.yellowstone.filters.v1\0";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RawTransactionIngestSourceFamily {
Transaction,
TransactionStatus,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RawTransactionIngestSourceRoute {
endpoint_id: ksp_store_lib::RawProvenanceCode,
provider: ksp_store_lib::RawProvenanceCode,
}
#[derive(Clone, Copy, Eq, PartialEq)]
struct RawTransactionIngestSourceTimestamp {
nanos: u32,
seconds: i64,
}
impl std::fmt::Debug for RawTransactionIngestSourceTimestamp {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("RawTransactionIngestSourceTimestamp").field("nanos", &self.nanos).field("seconds", &self.seconds).finish();
}
}
#[derive(Clone, Eq, PartialEq)]
struct RawTransactionIngestSourceSignal {
created_at: std::option::Option<RawTransactionIngestSourceTimestamp>,
family: RawTransactionIngestSourceFamily,
matched_filter_count: usize,
matched_filter_fingerprint: [u8; 32],
network: ksp_store_lib::RawNetworkId,
route: RawTransactionIngestSourceRoute,
signature: ksp_store_lib::RawTransactionSignature,
slot: u64,
transaction_index: std::option::Option<u64>,
}
impl std::fmt::Debug for RawTransactionIngestSourceSignal {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("RawTransactionIngestSourceSignal")
.field("created_at", &self.created_at)
.field("family", &self.family)
.field("matched_filter_count", &self.matched_filter_count)
.field("matched_filter_fingerprint_bytes", &self.matched_filter_fingerprint.len())
.field("network", &self.network)
.field("route", &self.route)
.field("signature_bytes", &self.signature.as_bytes().len())
.field("slot", &self.slot)
.field("has_transaction_index", &self.transaction_index.is_some())
.finish_non_exhaustive();
}
}
trait RawTransactionIngestYellowstoneSignalView {
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp>;
fn family(&self) -> RawTransactionIngestSourceFamily;
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName];
fn index(&self) -> u64;
fn signature(&self) -> ksp_onchain_transport_lib::YellowstoneTransactionSignature;
fn slot(&self) -> u64;
}
impl RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionUpdate {
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
return ksp_onchain_transport_lib::YellowstoneTransactionUpdate::created_at(self);
}
fn family(&self) -> RawTransactionIngestSourceFamily {
return RawTransactionIngestSourceFamily::Transaction;
}
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
return ksp_onchain_transport_lib::YellowstoneTransactionUpdate::filters(self);
}
fn index(&self) -> u64 {
return ksp_onchain_transport_lib::YellowstoneTransactionUpdate::transaction(self).index();
}
fn signature(&self) -> ksp_onchain_transport_lib::YellowstoneTransactionSignature {
return ksp_onchain_transport_lib::YellowstoneTransactionUpdate::transaction(self).signature();
}
fn slot(&self) -> u64 {
return ksp_onchain_transport_lib::YellowstoneTransactionUpdate::slot(self);
}
}
impl RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate {
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
return ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate::created_at(self);
}
fn family(&self) -> RawTransactionIngestSourceFamily {
return RawTransactionIngestSourceFamily::TransactionStatus;
}
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
return ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate::filters(self);
}
fn index(&self) -> u64 {
return ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate::index(self);
}
fn signature(&self) -> ksp_onchain_transport_lib::YellowstoneTransactionSignature {
return ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate::signature(self);
}
fn slot(&self) -> u64 {
return ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate::slot(self);
}
}
impl std::convert::From<(&crate::RawTransactionIngestYellowstoneSource, &ksp_onchain_transport_lib::YellowstoneTransactionUpdate)>
for RawTransactionIngestSourceSignal
{
fn from(value: (&crate::RawTransactionIngestYellowstoneSource, &ksp_onchain_transport_lib::YellowstoneTransactionUpdate)) -> Self {
return project_yellowstone_signal(value.0, value.1);
}
}
impl std::convert::From<(&crate::RawTransactionIngestYellowstoneSource, &ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate)>
for RawTransactionIngestSourceSignal
{
fn from(value: (&crate::RawTransactionIngestYellowstoneSource, &ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate)) -> Self {
return project_yellowstone_signal(value.0, value.1);
}
}
/// Validated Yellowstone plus HTTP runtime source owned by the continuous RAW transaction ingest Worker.
///
@@ -10,6 +150,8 @@ pub struct RawTransactionIngestYellowstoneSource {
subscribe_request: ksp_onchain_transport_lib::YellowstoneSubscribeRequest,
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
network: ksp_store_lib::RawNetworkId,
route: RawTransactionIngestSourceRoute,
}
impl crate::RawTransactionIngestYellowstoneSource {
@@ -33,11 +175,23 @@ impl crate::RawTransactionIngestYellowstoneSource {
return std::result::Result::Err(crate::runtime_error("runtime_resources.hydration_commitment_invalid"));
},
}
let network = match ksp_store_lib::RawNetworkId::new(yellowstone_channel.cluster().as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.yellowstone_network_unrepresentable")),
};
let provider = match ksp_store_lib::RawProvenanceCode::new(yellowstone_channel.provider().as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.yellowstone_provider_unrepresentable")),
};
let endpoint_id = match ksp_store_lib::RawProvenanceCode::new(yellowstone_channel.endpoint_name()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.yellowstone_endpoint_unrepresentable")),
};
let method = match ksp_onchain_transport_lib::find_http_rpc_method("getTransaction") {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("runtime_resources.hydration_method_missing")),
};
let compatible_http_routes = compatible_http_route_count(&http_pool, &hydration_role, method.request_kind(), yellowstone_channel.cluster().as_str());
let compatible_http_routes = compatible_http_route_count(&http_pool, &hydration_role, method.request_kind(), network.as_str());
let compatible_http_routes = match compatible_http_routes {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -45,7 +199,8 @@ impl crate::RawTransactionIngestYellowstoneSource {
if compatible_http_routes == 0 {
return std::result::Result::Err(crate::runtime_error("runtime_resources.hydration_role_unsupported"));
}
return std::result::Result::Ok(Self { yellowstone_channel, subscribe_request, http_pool, hydration_role });
let route = RawTransactionIngestSourceRoute { endpoint_id, provider };
return std::result::Result::Ok(Self { yellowstone_channel, subscribe_request, http_pool, hydration_role, network, route });
}
}
@@ -56,7 +211,7 @@ impl std::fmt::Debug for crate::RawTransactionIngestYellowstoneSource {
.debug_struct("RawTransactionIngestYellowstoneSource")
.field("yellowstone_endpoint_name", &self.yellowstone_channel.endpoint_name())
.field("yellowstone_provider", &self.yellowstone_channel.provider().as_str())
.field("network", &self.yellowstone_channel.cluster().as_str())
.field("network", &self.network.as_str())
.field("transaction_filter_count", &self.subscribe_request.transaction_filter_count())
.field("transaction_status_filter_count", &self.subscribe_request.transaction_status_filter_count())
.field("block_filter_count", &self.subscribe_request.block_filter_count())
@@ -85,7 +240,7 @@ impl crate::RawTransactionIngestRuntimeResources {
/// Validates that caller-owned Worker settings target the same logical network as the composed Yellowstone/HTTP source.
pub(crate) fn validate_network(&self, network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> {
if self.yellowstone_source.yellowstone_channel.cluster().as_str() != network.as_str() {
if &self.yellowstone_source.network != network {
return std::result::Result::Err(crate::runtime_error("runtime_resources.worker_network_mismatch"));
}
return std::result::Result::Ok(());
@@ -131,6 +286,41 @@ fn ingestion_filter_count(request: &ksp_onchain_transport_lib::YellowstoneSubscr
return request.transaction_filter_count().saturating_add(request.transaction_status_filter_count()).saturating_add(request.block_filter_count());
}
fn matched_filter_fingerprint(filters: &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName]) -> [u8; 32] {
let mut names = filters.iter().map(ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::as_str).collect::<std::vec::Vec<_>>();
names.sort_unstable();
names.dedup();
let mut hasher = sha2::Sha256::new();
hasher.update(RAW_TRANSACTION_INGEST_YELLOWSTONE_FILTER_FINGERPRINT_DOMAIN);
hasher.update((names.len() as u64).to_be_bytes());
for name in names {
hasher.update((name.len() as u64).to_be_bytes());
hasher.update(name.as_bytes());
}
return hasher.finalize().into();
}
fn project_yellowstone_signal<T: RawTransactionIngestYellowstoneSignalView>(
source: &crate::RawTransactionIngestYellowstoneSource,
update: &T,
) -> RawTransactionIngestSourceSignal {
let signature = ksp_store_lib::RawTransactionSignature::new(*update.signature().as_bytes());
let created_at = update.created_at().map(|value| {
return RawTransactionIngestSourceTimestamp { nanos: value.nanos(), seconds: value.seconds() };
});
return RawTransactionIngestSourceSignal {
created_at,
family: update.family(),
matched_filter_count: update.filters().len(),
matched_filter_fingerprint: matched_filter_fingerprint(update.filters()),
network: source.network.clone(),
route: source.route.clone(),
signature,
slot: update.slot(),
transaction_index: std::option::Option::Some(update.index()),
};
}
#[cfg(test)]
#[path = "../unit_tests/runtime_resources.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
// version: 9
// version: 10
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
@@ -63,6 +63,51 @@ fn v0_3_12_pre_002_manifest_opens_only_the_onchain_transport_live_source_edge()
return;
}
#[test]
fn v0_3_12_pre_003_transaction_and_status_adapters_are_private_offline_and_transport_facade_only() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionUpdate",
"RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate",
"YellowstoneTransactionUpdate::transaction(self).signature()",
"YellowstoneTransactionUpdate::transaction(self).index()",
"YellowstoneTransactionStatusUpdate::signature(self)",
"YellowstoneTransactionStatusUpdate::index(self)",
"RawTransactionSignature::new",
"matched_filter_fingerprint",
"yellowstone_provider_unrepresentable",
"yellowstone_endpoint_unrepresentable",
] {
assert!(resources.contains(required), "required pre.003 signal-adapter contract missing: {required}");
}
for forbidden in [
"pub struct RawTransactionIngestSourceSignal",
"pub(crate) struct RawTransactionIngestSourceSignal",
"pub use self::runtime_resources::RawTransactionIngestSourceSignal",
"open_standard_subscribe",
"next_update",
"get_transaction_observed",
"get_block_observed",
"ksp_store_postgres_lib::",
"reqwest::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.003 crossed a private/offline boundary: {forbidden}");
}
let status_impl =
match resources.split_once("impl RawTransactionIngestYellowstoneSignalView for ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::convert::From<") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => tail,
},
std::option::Option::None => "",
};
assert!(!status_impl.contains("::error(self)"), "TransactionStatus remote error material must not enter the private signal");
return;
}
#[test]
fn v0_3_12_pre_002_source_surface_hardens_shutdown_and_faults_without_backend_or_premature_live_io() {
let root = include_str!("../src/lib.rs");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
// version: 3
// version: 4
//! External public, security, redaction and release-boundary hardening canaries for `pre.010`.
@@ -278,6 +278,40 @@ fn v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resourc
return;
}
#[test]
fn v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(resources.contains("struct RawTransactionIngestSourceSignal"));
assert!(!root.contains("RawTransactionIngestSourceSignal"));
assert!(!resources.contains(".field(\"matched_filter_fingerprint\", &self.matched_filter_fingerprint)"));
assert!(!resources.contains(".field(\"signature\", &self.signature)"));
let signal_struct = match resources.split_once("struct RawTransactionIngestSourceSignal {") {
std::option::Option::Some((_, tail)) => match tail.split_once("}\n\nimpl std::fmt::Debug for RawTransactionIngestSourceSignal") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for forbidden in ["transaction:", "meta:", "error:", "payload:", "body:", "is_vote:"] {
assert!(!signal_struct.contains(forbidden), "payload/provider-specific field leaked into private source signal: {forbidden}");
}
for required in [
"created_at:",
"family:",
"matched_filter_count:",
"matched_filter_fingerprint:",
"network:",
"route:",
"signature:",
"slot:",
"transaction_index:",
] {
assert!(signal_struct.contains(required), "required private signal field missing: {required}");
}
return;
}
#[test]
fn v0_3_12_pre_002_runtime_resource_contract_performs_no_live_io_or_source_spawn() {
let resources = include_str!("../src/runtime_resources.rs");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
// version: 2
// version: 3
//! Release-completeness canaries through the `pre.010` public/release/security hardening tranche.
@@ -98,6 +98,7 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
"pre_010_external_error_codes_are_stable_unique_and_domain_scoped",
"pre_010_debug_and_settings_errors_redact_worker_identity_and_invalid_values",
"v0_3_12_pre_002_manifest_dependency_surface_opens_only_transport_and_remains_backend_neutral",
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
"pre_010_source_visibility_contract_uses_crate_root_for_shared_items",
"pre_010_production_surface_has_no_historical_backfill_or_retriever_contract",
"v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resources",
@@ -111,6 +112,7 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
assert!(dependency_boundary.contains("pre_002_manifest_dependency_surface_is_exact"));
assert!(dependency_boundary.contains("v0_3_12_pre_002_source_surface_hardens_shutdown_and_faults_without_backend_or_premature_live_io"));
assert!(dependency_boundary.contains("v0_3_12_pre_002_manifest_opens_only_the_onchain_transport_live_source_edge"));
assert!(dependency_boundary.contains("v0_3_12_pre_003_transaction_and_status_adapters_are_private_offline_and_transport_facade_only"));
let public_api = include_str!("public_api.rs");
assert!(public_api.contains("pre_003_kind_code_and_settings_are_consumable_from_crate_root"));
assert!(public_api.contains("pre_004_start_handle_and_terminal_future_are_consumable_without_public_join_handle"));

View File

@@ -1,15 +1,23 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
// version: 1
// version: 2
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
}
fn grpc_endpoint_with_identity(
cluster: &str,
endpoint_name: &str,
provider: &str,
) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
let url = match ksp_onchain_transport_lib::YellowstoneGrpcEndpointUrl::parse("http://127.0.0.1:10000/GRPC-SECRET-CANARY") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings::new(
"yellowstone-fixture",
endpoint_name,
true,
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new("fixture-provider"),
ksp_onchain_transport_lib::YellowstoneGrpcProviderName::new(provider),
ksp_onchain_transport_lib::YellowstoneGrpcClusterName::new(cluster),
url,
ksp_onchain_transport_lib::YellowstoneGrpcSessionSettings::default(),
@@ -69,6 +77,76 @@ fn transaction_request(
return std::option::Option::Some(request);
}
struct SignalViewFixture {
created_at: std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp>,
family: super::RawTransactionIngestSourceFamily,
filters: std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>,
index: u64,
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature,
slot: u64,
}
impl super::RawTransactionIngestYellowstoneSignalView for SignalViewFixture {
fn created_at(&self) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneUpdateTimestamp> {
return self.created_at;
}
fn family(&self) -> super::RawTransactionIngestSourceFamily {
return self.family;
}
fn filters(&self) -> &[ksp_onchain_transport_lib::YellowstoneSubscribeFilterName] {
return self.filters.as_slice();
}
fn index(&self) -> u64 {
return self.index;
}
fn signature(&self) -> ksp_onchain_transport_lib::YellowstoneTransactionSignature {
return self.signature;
}
fn slot(&self) -> u64 {
return self.slot;
}
}
fn filter_names(values: &[&str]) -> std::option::Option<std::vec::Vec<ksp_onchain_transport_lib::YellowstoneSubscribeFilterName>> {
let mut filters = std::vec::Vec::with_capacity(values.len());
for value in values {
let filter = match ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new(*value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
filters.push(filter);
}
return std::option::Option::Some(filters);
}
fn signal_source() -> 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("devnet", "hydration", "get_transaction") {
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,
};
}
#[tokio::test(flavor = "current_thread")]
async fn pre_002_source_accepts_matching_confirmed_transaction_and_get_transaction_route_without_io() {
let endpoint = match grpc_endpoint("devnet") {
@@ -250,3 +328,149 @@ async fn pre_002_runtime_resources_reject_worker_network_mismatch_without_starti
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.worker_network_mismatch"));
return;
}
#[tokio::test(flavor = "current_thread")]
async fn pre_003_transaction_signal_fixture_preserves_exact_source_neutral_identity() {
let source = match signal_source() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let filters = match filter_names(&["filter-b", "filter-a", "filter-a"]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let created_at = match ksp_onchain_transport_lib::YellowstoneUpdateTimestamp::new(1_760_000_123, 456_000_000) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let fixture = SignalViewFixture {
created_at: std::option::Option::Some(created_at),
family: super::RawTransactionIngestSourceFamily::Transaction,
filters,
index: 7,
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0xAB; 64]),
slot: 42,
};
let signal = super::project_yellowstone_signal(&source, &fixture);
assert_eq!(signal.family, super::RawTransactionIngestSourceFamily::Transaction);
assert_eq!(signal.network.as_str(), "devnet");
assert_eq!(signal.signature.as_bytes(), &[0xAB; 64]);
assert_eq!(signal.slot, 42);
assert_eq!(signal.transaction_index, std::option::Option::Some(7));
assert_eq!(signal.route.provider.as_str(), "fixture-provider");
assert_eq!(signal.route.endpoint_id.as_str(), "yellowstone-fixture");
assert_eq!(signal.matched_filter_count, 3);
assert_eq!(
signal.matched_filter_fingerprint,
[
0x20, 0x83, 0x1d, 0xc8, 0x37, 0x8b, 0x60, 0x70, 0xf9, 0x1d, 0x9d, 0xe6, 0xd3, 0xa1, 0xd2, 0xb3, 0xd7, 0x72, 0x98, 0xe3, 0xad, 0xa7, 0x6c, 0x01,
0xfa, 0xa3, 0x08, 0x26, 0xff, 0xab, 0xcd, 0x84,
]
);
assert_eq!(signal.created_at, std::option::Option::Some(super::RawTransactionIngestSourceTimestamp { nanos: 456_000_000, seconds: 1_760_000_123 }));
return;
}
#[tokio::test(flavor = "current_thread")]
async fn pre_003_transaction_status_signal_has_no_payload_or_remote_error_material() {
let source = match signal_source() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let filters = match filter_names(&["status-secret-canary"]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let fixture = SignalViewFixture {
created_at: std::option::Option::None,
family: super::RawTransactionIngestSourceFamily::TransactionStatus,
filters,
index: 9,
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([0xCD; 64]),
slot: 84,
};
let signal = super::project_yellowstone_signal(&source, &fixture);
assert_eq!(signal.family, super::RawTransactionIngestSourceFamily::TransactionStatus);
assert_eq!(signal.signature.as_bytes(), &[0xCD; 64]);
assert_eq!(signal.slot, 84);
assert_eq!(signal.transaction_index, std::option::Option::Some(9));
assert_eq!(signal.created_at, std::option::Option::None);
let debug = std::format!("{signal:?}");
assert!(!debug.contains("status-secret-canary"));
assert!(debug.contains("signature_bytes"));
assert!(!debug.contains("YellowstoneTransactionError"));
assert!(!debug.contains("transaction_body"));
assert!(!debug.contains("transaction_meta"));
return;
}
#[tokio::test(flavor = "current_thread")]
async fn pre_003_filter_fingerprint_is_order_and_duplicate_insensitive() {
let source = match signal_source() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let first_filters = match filter_names(&["filter-b", "filter-a", "filter-a"]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let second_filters = match filter_names(&["filter-a", "filter-b"]) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let first = SignalViewFixture {
created_at: std::option::Option::None,
family: super::RawTransactionIngestSourceFamily::Transaction,
filters: first_filters,
index: 1,
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([1; 64]),
slot: 1,
};
let second = SignalViewFixture {
created_at: std::option::Option::None,
family: super::RawTransactionIngestSourceFamily::Transaction,
filters: second_filters,
index: 1,
signature: ksp_onchain_transport_lib::YellowstoneTransactionSignature::new([1; 64]),
slot: 1,
};
let first_signal = super::project_yellowstone_signal(&source, &first);
let second_signal = super::project_yellowstone_signal(&source, &second);
assert_eq!(first_signal.matched_filter_fingerprint, second_signal.matched_filter_fingerprint);
assert_eq!(first_signal.matched_filter_count, 3);
assert_eq!(second_signal.matched_filter_count, 2);
return;
}
#[tokio::test(flavor = "current_thread")]
async fn pre_003_source_rejects_route_identity_that_cannot_become_safe_provenance() {
for (endpoint_name, provider, expected_condition) in [
("yellowstone-fixture", "fixture/provider", "runtime_resources.yellowstone_provider_unrepresentable"),
("yellowstone/fixture", "fixture-provider", "runtime_resources.yellowstone_endpoint_unrepresentable"),
] {
let endpoint = match grpc_endpoint_with_identity("devnet", endpoint_name, provider) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let channel = match ksp_onchain_transport_lib::YellowstoneGrpcChannel::prepare(&endpoint) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
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,
};
let pool = match http_pool("devnet", "hydration", "get_transaction") {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let result = crate::RawTransactionIngestYellowstoneSource::new(channel, request, pool, ksp_onchain_transport_lib::HttpRoleName::new("hydration"));
let error = match result {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(error.context().iter().any(|context| return context.value() == expected_condition));
}
return;
}

255
deltas/0.3.12/pre.003.md Normal file
View File

@@ -0,0 +1,255 @@
<!-- file: deltas/0.3.12/pre.003.md -->
<!-- version: 1 -->
# Delta `0.3.12-pre.003` — signaux privés Yellowstone Transaction + TransactionStatus
## Base requise
```text
0.3.12-pre.002
workspace.package.version = 0.3.12-pre.2
```
Le gate opérateur communiqué pour `pre.002` est vert sur :
```text
cargo fmt --all
Rust rule audit
Markdown table audit
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib : 41 PASS
cargo tree Worker normal/features
cargo tree --duplicates exécuté pour inspection
```
Le graphe confirme que `reqwest`, `tonic` et `yellowstone-grpc-proto` restent transitifs via `ksp-onchain-transport-lib`, jamais directs dans le Worker.
## Objectif
Matérialiser uniquement les deux adapters déterministes prévus :
```text
YellowstoneTransactionUpdate -> signal Worker privé
YellowstoneTransactionStatusUpdate -> signal Worker privé
```
sans ouvrir de session Yellowstone, sans HTTP et sans persistence réseau.
## Version
```text
workspace.package.version = 0.3.12-pre.3
```
## Signal Worker privé
Le signal est strictement privé à `runtime_resources.rs`. Sa forme contient :
```text
RawNetworkId
RawTransactionSignature
slot u64
transaction_index Option<u64>
family Transaction | TransactionStatus
route sûre provider + endpoint_id
matched_filter_count
matched_filter_fingerprint [u8; 32]
created_at Option<seconds+nanos>
```
Il ne contient volontairement pas :
```text
transaction body Yellowstone
transaction meta Yellowstone
is_vote
TransactionStatus.error
raw/provider payload
HTTP material
Store acquisition
```
La signature Yellowstone `[u8; 64]` est convertie directement vers `RawTransactionSignature`; aucune Base58 intermédiaire n'est introduite.
## Adapter `Transaction`
Le mapping utilise exactement :
```text
update.slot()
update.transaction().signature()
update.transaction().index()
update.filters()
update.created_at()
```
Le body et la meta présents dans `YellowstoneTransactionInfo` restent ignorés jusqu'à l'hydration HTTP qualifiée de `pre.004`.
## Adapter `TransactionStatus`
Le mapping utilise exactement :
```text
update.slot()
update.signature()
update.index()
update.filters()
update.created_at()
```
`update.error()` n'est pas consulté. Le remote error opaque n'est donc ni copié dans le signal, ni transformé en contexte Worker, ni loggé.
## Fingerprint des filtres
Le fingerprint est Worker-owned, SHA-256 et domain-separated :
```text
ksp.raw_transaction_ingest.yellowstone.filters.v1\0
```
Les noms sont triés puis dédupliqués avant hashing ; chaque élément est length-prefixed par un `u64` big-endian. Le résultat est donc déterministe pour le même ensemble de filtres, même si l'ordre d'enveloppe diffère.
Le golden de fixture `filter-a + filter-b` est :
```text
20831dc8378b6070f91d9de6d3a1d2b3d77298e3ada76c01faa30826ffabcd84
```
Les noms eux-mêmes ne sont pas conservés dans le signal et n'apparaissent pas dans son `Debug`.
## Route source sûre
La source Yellowstone convertit dès construction :
```text
cluster -> RawNetworkId
provider -> RawProvenanceCode
logical endpoint name -> RawProvenanceCode
```
Une valeur impossible à représenter selon les bornes Store est rejetée par `worker_raw_transaction_ingest.runtime_invalid` avec une condition statique. Il n'y a ni troncature ni fuite de valeur.
Cela prépare `pre.004`, où le provider/endpoint HTTP gagnant sera combiné avec cette identité Yellowstone dans la provenance composite retenue par le plan.
## Absence volontaire de comportement live
Toujours absents en `pre.003` :
```text
open_standard_subscribe
next_update
get_transaction_observed
get_block_observed
source task Yellowstone
HTTP hydration
RawTransactionIngress issue du réseau
Store write issue du réseau
coalescence hydration
frontier / reconnect / replay
```
L'activation productive de la session reste `pre.006`.
## Tests et hardening ajoutés
```text
Transaction fixture -> identité source-neutral exacte
TransactionStatus fixture -> même forme sans payload/error
fingerprint golden exact
fingerprint order/duplicate insensitive
signal Debug redacted
route provider/endpoint non représentable rejetée
source-scan des deux adapters Transport
TransactionStatus.error absent du mapping
signal non public
aucun I/O live prématuré
```
## Décisions prises
```text
un seul signal privé commun aux deux familles
signature canonique conservée en bytes fixes
transaction_index toujours Some pour ces deux DTOs mais champ du signal reste optionnel pour les familles suivantes
filter fingerprint canonique indépendant de l'ordre
gRPC route identity validée tôt pour la future provenance
remote TransactionStatus.error ignoré au boundary Worker
```
## Questions ouvertes
Aucune question ouverte n'est bloquante pour `pre.003`. Les choix HTTP missing/mismatch, provenance composite finale et fan-out/coalescence restent volontairement dans `pre.004`, conformément au plan.
## Fichiers ajoutés
```text
deltas/0.3.12/pre.003.md
```
## Fichiers modifiés
```text
Cargo.toml
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/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md
```
## Fichiers supprimés
Aucun.
## Validations exécutées dans l'environnement d'assemblage
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
```
L'audit Markdown final est exécuté après création de ce delta.
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'assemblage. Aucun gate Cargo local n'est déclaré PASS.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
## Non-claims
Cette tranche ne revendique pas :
```text
session Yellowstone ouverte
update réseau réellement consommée
hydration HTTP
conversion réseau vers Common RAW
provenance composite finale
persistence réseau
Block / BlockMeta / Slot
coalescence
processing frontier
from_slot / ReplayInfo
smoke live Worker
réconciliation README/USAGE/architecture finale
```
## Suite
Après gate opérateur vert, `pre.004` ferme `signal -> get_transaction_observed -> Common RAW -> RawTransactionIngress`, avec `missing`, mismatches et provenance Yellowstone + HTTP observée. Block et la continuité/replay restent hors scope.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/029-V0_3_12_YELLOWSTONE_HYDRATION_CONTINUITY.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# Validation v0.3.12 — Yellowstone + hydration HTTP + continuité de run du Worker RawTransaction
@@ -648,3 +648,134 @@ réconcilié README/USAGE/architecture pour la surface finale 0.3.12
La tranche suivante reste `pre.003` : adaptation déterministe `Transaction` + `TransactionStatus` vers le signal Worker privé, sans HTTP ni persistence réseau.
## 27. Gate opérateur reçu pour `pre.002`
L'opérateur a exécuté après application de `pre.002` :
```text
cargo fmt --all : sans erreur visible
audit Rust : clean
Rust export completeness : 0 candidate
KSP workspace Rust rule audit : clean
audit Markdown : clean (340 tables, 802 fichiers)
cargo check --workspace : PASS visible
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS visible
cargo test -p ksp-worker-raw-transaction-ingest-lib : 41 PASS, 0 fail
cargo tree Worker normal : exécuté et inspecté
cargo tree Worker features : exécuté et inspecté
cargo tree --duplicates : exécuté ; inventaire informatif des versions dupliquées, sans gate d'unicité artificiel
```
Le graphe confirme que le Worker possède directement uniquement l'edge `ksp-onchain-transport-lib`; `reqwest`, `tonic`, `tonic-prost` et `yellowstone-grpc-proto` restent transitifs sous Transport.
Résultat : `pre.002` est accepté comme base technique de `pre.003`.
## 28. Gate `pre.003` — signal privé Transaction + TransactionStatus
La tranche matérialise :
```text
workspace.package.version = 0.3.12-pre.3
projection Yellowstone Transaction -> signal Worker privé
projection Yellowstone TransactionStatus -> même signal Worker privé
aucune ouverture de stream
aucune hydration HTTP
aucune persistence réseau
```
Le signal reste strictement interne à `runtime_resources.rs` et n'est ni `pub`, ni `pub(crate)`, ni réexporté au crate-root.
Sa forme source-neutral actuelle est :
```text
network
signature RawTransactionSignature
slot
transaction_index optionnel
source family Transaction | TransactionStatus
safe Yellowstone source route provider + endpoint_id
matched_filter_count
matched_filter_fingerprint SHA-256 déterministe
created_at source-neutral optionnel seconds+nanos
```
Aucun transaction body, meta Yellowstone, `is_vote`, remote error ou payload provider n'est conservé dans ce signal.
## 29. Projection déterministe des filtres et de la route
Les filter names Yellowstone sont consommés uniquement pour produire un fingerprint Worker-owned. Le calcul :
```text
domain = ksp.raw_transaction_ingest.yellowstone.filters.v1\0
tri lexical des noms
suppression des doublons
nombre de noms uniques encodé u64 big-endian
pour chaque nom : longueur u64 big-endian + bytes UTF-8
SHA-256 final
```
Le signal conserve séparément le nombre de matches reçu afin de ne pas confondre le fingerprint canonique de l'ensemble et la cardinalité de l'enveloppe reçue.
La source valide aussi dès sa construction que :
```text
cluster Yellowstone -> RawNetworkId représentable
provider Yellowstone -> RawProvenanceCode représentable
endpoint logical name Yellowstone -> RawProvenanceCode représentable
```
Une identité non représentable est rejetée par une condition Worker stable plutôt que tronquée. Cette fermeture prépare la provenance composite de `pre.004` sans modifier Store.
## 30. Canaris déterministes `pre.003`
Les nouvelles preuves couvrent :
```text
fixture Transaction : signature/slot/index/network/route/timestamp exacts
fixture TransactionStatus : même forme sans payload ni remote error
fingerprint golden exact
fingerprint indépendant de l'ordre et des doublons de filter names
Debug du signal sans filter name, signature brute, payload ou remote error
provider/endpoint Yellowstone non représentables rejetés
adapters réels typés YellowstoneTransactionUpdate / YellowstoneTransactionStatusUpdate présents
TransactionStatus.error non consulté par l'adapter
signal absent de la façade publique
aucun open_standard_subscribe / next_update / get_transaction_observed
aucun backend Store / reqwest / tonic / proto direct ajouté
```
La tranche ne modifie pas le graphe Cargo du Worker au-delà de celui déjà accepté en `pre.002`.
## 31. Validation locale et non-claims `pre.003`
Exécuté dans l'environnement d'assemblage après les modifications Rust :
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
```
L'audit Markdown est rejoué après création du delta final.
`cargo`, `rustc` et `rustfmt` ne sont pas disponibles dans l'environnement d'assemblage ; aucun gate Cargo local n'est inventé.
`pre.003` ne prétend pas avoir :
```text
ouvert une session Yellowstone
consommé une update depuis un réseau live
appelé getTransaction
créé une RawTransactionMaterial depuis le réseau
assemblé la provenance composite Yellowstone + HTTP
persisté une transaction issue de Yellowstone
coalescé les hydrations
traité Block / BlockMeta / Slot
modifié la processing frontier
branché from_slot / ReplayInfo
exécuté un smoke live Worker
```
La tranche suivante reste `pre.004` : fermeture déterministe `signal -> get_transaction_observed -> Common RAW -> ingress` avec missing/mismatch et provenance composite, sans Block ni continuité/replay.