v0.3.13-pre.004

This commit is contained in:
2026-09-10 14:25:30 +02:00
parent 31c0a83e51
commit 28e4da3879
13 changed files with 1303 additions and 33 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 545
# version: 546
[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.13-pre.3.fix.3"
version = "0.3.13-pre.4"
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/README.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# ksp-worker-raw-transaction-ingest-lib
@@ -13,8 +13,8 @@ RawTransactionIngestWorker::start
RawTransactionIngestWorker::start_with_runtime_resources
-> même runtime + une source productive supervisée
Yellowstone ou WS standard logsSubscribe
+ hydration HTTP getTransaction
Yellowstone, WS standard logsSubscribe ou WS standard blockSubscribe
+ hydration HTTP getTransaction lorsque la source produit une référence
```
Le Worker reste indépendant de Config et de tout backend Store physique. Le caller compose les ressources Transport et Store, puis les remet à la crate par ses façades publiques.
@@ -31,10 +31,15 @@ Yellowstone standard subscribe
Solana standard WS logsSubscribe
-> context.slot + signature
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
les deux chemins
-> coalescence bornée par (network, signature, commitment)
-> HTTP getTransaction observed, Base64, maxSupportedTransactionVersion=1
Solana standard WS blockSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> qualification explicite Legacy / V0 / V1
-> matériau Common RAW direct par transaction du bloc
les chemins productifs
-> ksp-raw-transaction-lib
-> admission centrale bornée
-> ksp-store-lib
@@ -65,7 +70,7 @@ La construction est sans I/O et refuse notamment :
- une provenance provider/endpoint non représentable ;
- l'absence d'une route HTTP compatible pour `getTransaction` sur le même réseau.
Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques. `pre.003` sait exécuter une source unique Yellowstone ou une source unique Standard Logs ; une activation simultanée de plusieurs sources reste rejetée avant spawn jusqu'au supervisor dédié. La collection interne, les `source_key`, les URLs, les filtres et les clients inférieurs ne sont pas exposés.
Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques. `pre.004` sait exécuter une source unique Yellowstone, Standard Logs ou Standard Block ; une activation simultanée de plusieurs sources reste rejetée avant spawn jusqu'au supervisor dédié. La collection interne, les `source_key`, les URLs, les filtres et les clients inférieurs ne sont pas exposés.
## Contrat de source Standard Logs + HTTP
@@ -83,6 +88,22 @@ La construction est sans I/O. Elle refuse un endpoint WS invalide ou non standar
Au runtime, le Worker ouvre `SolanaStandardWsSession::connect`, puis `logs_subscribe`. Les lignes de logs et `err` restent dans Transport et ne sont jamais stockées dans le signal Worker. Seuls `context.slot` et `signature` sont projetés vers l'hydration commune. Reconnect, resubscribe et backpressure de la subscription restent possédés par Transport.
## Contrat de source Standard Block direct RAW
`RawTransactionIngestStandardBlockSource::new` reçoit :
```text
WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized
```
Le runtime ouvre la `SolanaStandardWsSession` existante et demande exactement `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Aucun `HttpTransportPool` n'est attaché à cette source : les transactions dont la version est explicitement `Legacy`, `0` ou `1` sont transformées directement en `RawTransactionMaterial` à partir du wire Base64 du bloc, avec signature embarquée, slot, block time, meta, version et index de transaction.
La qualification est fermée : une version omise/nulle ou supérieure à `1`, une transaction non Base64, `block: null`, une erreur distante de notification, un slot de contexte incohérent ou un champ transactions absent/nul termine la source par une erreur sûre. Aucun de ces cas n'est converti en bloc vide, en succès silencieux ou en progression artificielle de frontier.
Pour un bloc multi-transaction, le slot n'est projeté settled qu'après l'admission réussie de toutes ses transactions. Cette voie RAW-direct n'incrémente pas `hydration_pending`; un stop ou une erreur au milieu du bloc ne produit aucune progression artificielle. Reconnect, resubscribe et backpressure WebSocket restent possédés par Transport.
## Runtime et lifecycle
Le Worker s'exécute sur le runtime Tokio courant du caller. Il ne crée pas de runtime global et n'expose aucun `JoinHandle` public.
@@ -100,7 +121,7 @@ Le shutdown est borné par `shutdown_drain_timeout`. Les tâches source, hydrati
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes subissent la backpressure ; aucune queue non bornée ni silent drop n'est autorisé.
Le Worker possède un coordinateur d'hydration source-neutral borné, réutilisé par Yellowstone et Standard Logs :
Le Worker possède un coordinateur d'hydration source-neutral borné, réutilisé par Yellowstone et Standard Logs. Standard Block n'entre pas dans ce coordinateur lorsqu'une transaction est direct-qualified :
```text
in-flight hydration <= persistence_concurrency

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Utilisation de ksp-worker-raw-transaction-ingest-lib
@@ -101,7 +101,7 @@ let handle = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWo
};
```
Les deux entrées exigent un runtime Tokio courant et un `Store` portant exactement le même `RawNetworkId` que les settings. Le démarrage avec ressources exige également que chaque source composée cible ce même réseau. Une exécution productive accepte une source unique Yellowstone ou Standard Logs ; une collection de plusieurs sources est validable à la composition mais reste rejetée avant spawn tant que la supervision simultanée n'est pas disponible.
Les deux entrées exigent un runtime Tokio courant et un `Store` portant exactement le même `RawNetworkId` que les settings. Le démarrage avec ressources exige également que chaque source composée cible ce même réseau. Une exécution productive accepte une source unique Yellowstone, Standard Logs ou Standard Block ; une collection de plusieurs sources est validable à la composition mais reste rejetée avant spawn tant que la supervision simultanée n'est pas disponible.
### Source Standard Logs productive
@@ -131,6 +131,26 @@ fn standard_logs_runtime_resources(
`ws_endpoint` doit être un endpoint Transport valide de kind `solana_standard`. Le commitment doit être explicitement `Confirmed` ou `Finalized`. Le pool HTTP doit exposer `getTransaction` via le rôle indiqué sur le même réseau. `All`, `AllWithVotes` et `Mentions(pubkey)` sont acceptés par le contrat Transport ; la valeur du filtre reste privée dans le Worker.
### Source Standard Block productive
Une source `blockSubscribe` standard se compose sans pool HTTP :
```rust
fn standard_block_runtime_resources(
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new(ws_endpoint, filter, commitment) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source(source));
}
```
Le Worker demande `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Une transaction n'est RAW-direct que si sa version est explicitement `Legacy`, `0` ou `1`. Une version omise/nulle ou supérieure, `block: null`, une erreur de bloc, un champ transactions absent/nul ou une transaction non Base64 provoque une faute sûre ; ces cas ne sont jamais assimilés à une progression vide.
## Préparer la source Yellowstone
La `YellowstoneSubscribeRequest` doit :
@@ -153,6 +173,7 @@ Yellowstone Transaction -> signal -> HTTP getTransaction -> Common RAW ->
Yellowstone TransactionStatus -> signal -> HTTP getTransaction -> Common RAW -> admission
Yellowstone Block -> un signal par transaction -> HTTP getTransaction -> Common RAW -> admission
Standard WS logsSubscribe -> context.slot + signature -> HTTP getTransaction -> Common RAW -> admission
Standard WS blockSubscribe -> Full/Base64 Legacy|V0|V1 -> Common RAW direct par transaction -> admission
Yellowstone BlockMeta -> continuity-only
Yellowstone Slot -> continuity-only
Yellowstone Account/Ping/Pong/Entry -> sans RAW Transaction dans cette verticale
@@ -295,8 +316,8 @@ Le pattern attendu est :
```text
Config / application / service owner
-> résout endpoints, credentials et rôles
-> construit une source Transport Yellowstone ou Standard Logs
-> construit HttpTransportPool + hydration role
-> construit une source Transport Yellowstone, Standard Logs ou Standard Block
-> construit HttpTransportPool + hydration role seulement pour les sources hydratées
-> construit le Store
-> construit RawTransactionIngestRuntimeResources
-> construit RawTransactionIngestSettings

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 20
// version: 21
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -10,13 +10,14 @@
//! This tranche owns the concrete Worker family identity, validated technical settings
//! 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. `pre.003` keeps the bounded 1..32 caller-composed
//! aggregate and adds a productive standard Solana `logsSubscribe` + observed HTTP `getTransaction` source beside Yellowstone while simultaneous multi-source
//! activation remains gated until the dedicated supervisor tranche. Both live source families converge into one source-neutral hydration coordinator contract and
//! the existing central admission path. Yellowstone Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references;
//! BlockMeta/Slot remain continuity-only signals. Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects hydration pending,
//! oldest pending slot and highest unblocked actually observed slot. The productive source now also projects safe Transport reconnect/replay state and faults
//! conservatively when Transport proves a replay-retention continuity gap; history remediation remains outside this crate.
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API. `pre.004` keeps the bounded 1..32 caller-composed
//! aggregate and adds a productive standard Solana `blockSubscribe` direct RAW source beside Yellowstone and Standard Logs while simultaneous multi-source
//! activation remains gated until the dedicated supervisor tranche. Standard Logs and Yellowstone reference paths converge into one source-neutral hydration
//! coordinator contract; qualified Standard Block Legacy/V0/V1 transactions enter the existing central admission path directly. Yellowstone
//! Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references; BlockMeta/Slot remain continuity-only signals.
//! Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects
//! hydration pending, oldest pending slot and highest unblocked actually observed slot. The productive source also projects safe Transport reconnect/replay
//! state and faults conservatively when Transport proves a replay-retention continuity gap; history remediation remains outside this crate.
mod admission;
mod error;
@@ -53,6 +54,8 @@ pub use self::runtime::RawTransactionIngestWorker;
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES;
/// Caller-composed bounded runtime resources for supported continuous RAW transaction live-source families.
pub use self::runtime_resources::RawTransactionIngestRuntimeResources;
/// Validated standard Solana `blockSubscribe` direct RAW source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestStandardBlockSource;
/// Validated standard Solana `logsSubscribe` + HTTP hydration source contract owned by the continuous RAW transaction ingest Worker.
pub use self::runtime_resources::RawTransactionIngestStandardLogsSource;
/// Validated Yellowstone + HTTP source contract owned by the continuous RAW transaction ingest Worker.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
// version: 14
// version: 15
use sha2::Digest; // rust-rules: trait-import
@@ -7,6 +7,9 @@ use sha2::Digest; // rust-rules: trait-import
pub const MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES: usize = 32;
const RAW_TRANSACTION_INGEST_LIVE_SOURCE_KEY_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.live_source.source_key.v1\0";
const RAW_TRANSACTION_INGEST_STANDARD_BLOCK_ACQUISITION_METHOD: &str = "block_subscribe";
const RAW_TRANSACTION_INGEST_STANDARD_BLOCK_FILTER_FINGERPRINT_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.standard_block.filter.v1\0";
const RAW_TRANSACTION_INGEST_STANDARD_BLOCK_PROTOCOL: &str = "solana_ws";
const RAW_TRANSACTION_INGEST_STANDARD_LOGS_FILTER_FINGERPRINT_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.standard_logs.filter.v1\0";
const RAW_TRANSACTION_INGEST_STANDARD_LOGS_HTTP_PROTOCOL: &str = "solana_ws_http";
const RAW_TRANSACTION_INGEST_STANDARD_LOGS_HTTP_SOURCE_KEY_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.standard_logs_http.source_key.v1\0";
@@ -23,6 +26,7 @@ enum RawTransactionIngestSourceFamily {
}
enum RawTransactionIngestLiveSource {
StandardBlock(crate::RawTransactionIngestStandardBlockSource),
StandardLogs(crate::RawTransactionIngestStandardLogsSource),
Yellowstone(crate::RawTransactionIngestYellowstoneSource),
}
@@ -30,6 +34,7 @@ enum RawTransactionIngestLiveSource {
impl RawTransactionIngestLiveSource {
fn network(&self) -> &ksp_store_lib::RawNetworkId {
return match self {
Self::StandardBlock(source) => &source.network,
Self::StandardLogs(source) => &source.network,
Self::Yellowstone(source) => &source.network,
};
@@ -37,6 +42,7 @@ impl RawTransactionIngestLiveSource {
fn source_key(&self) -> [u8; 32] {
return match self {
Self::StandardBlock(source) => source.source_key,
Self::StandardLogs(source) => source.source_key,
Self::Yellowstone(source) => source.source_key,
};
@@ -50,6 +56,7 @@ impl RawTransactionIngestLiveSource {
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
) -> ksp_core_lib::Result<()> {
return match self {
Self::StandardBlock(source) => source.run(settings, stop_receiver, admission_sender, processing_frontier_sender).await,
Self::StandardLogs(source) => source.run(settings, stop_receiver, admission_sender, processing_frontier_sender).await,
Self::Yellowstone(source) => source.run(settings, stop_receiver, admission_sender, processing_frontier_sender).await,
};
@@ -571,6 +578,206 @@ impl crate::RawTransactionIngestYellowstoneSource {
}
}
/// Validated standard Solana `blockSubscribe` direct RAW source owned by the continuous RAW transaction ingest Worker.
///
/// The caller provides one Transport-owned standard WebSocket endpoint, one block filter and a Confirmed/Finalized commitment. The source requests Full/Base64
/// blocks with `maxSupportedTransactionVersion = 1`, admits only explicitly qualified Legacy/V0/V1 transactions, and treats `block: null`, remote block errors,
/// unsupported versions and incomplete block transaction shapes as safe terminal source failures rather than empty progress.
pub struct RawTransactionIngestStandardBlockSource {
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
network: ksp_store_lib::RawNetworkId,
route: RawTransactionIngestSourceRoute,
filter_fingerprint: [u8; 32],
source_key: [u8; 32],
}
impl crate::RawTransactionIngestStandardBlockSource {
/// Creates one validated standard Solana block source without opening WebSocket network I/O.
pub fn new(
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<Self> {
let ws_settings = ksp_onchain_transport_lib::WsTransportSettings::new(std::vec![ws_endpoint.clone()]);
if ws_settings.validate().is_err() {
return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_endpoint_invalid"));
}
if ws_endpoint.protocol() != ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard {
return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_protocol_invalid"));
}
match commitment {
ksp_onchain_transport_lib::SolanaCommitment::Confirmed | ksp_onchain_transport_lib::SolanaCommitment::Finalized => {},
ksp_onchain_transport_lib::SolanaCommitment::Processed => {
return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_commitment_invalid"));
},
}
let network = match ksp_store_lib::RawNetworkId::new(ws_endpoint.cluster().as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_network_unrepresentable")),
};
let provider = match ksp_store_lib::RawProvenanceCode::new(ws_endpoint.provider().as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_provider_unrepresentable")),
};
let endpoint_id = match ksp_store_lib::RawProvenanceCode::new(ws_endpoint.name()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("runtime_resources.standard_block_endpoint_unrepresentable")),
};
let route = RawTransactionIngestSourceRoute { endpoint_id, provider };
let filter_fingerprint = standard_block_filter_fingerprint(&filter);
let source_key = standard_block_live_source_key(&network, &route, commitment, &filter_fingerprint);
return std::result::Result::Ok(Self { ws_endpoint, filter, commitment, network, route, filter_fingerprint, source_key });
}
/// Runs one productive standard `blockSubscribe` source until cooperative stop or one safe terminal source failure.
pub(crate) async fn run(
self,
settings: crate::RawTransactionIngestSettings,
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
) -> ksp_core_lib::Result<()> {
let connected = tokio::select! {
biased;
_ = stop_receiver.changed() => {
return std::result::Result::Ok(());
}
result = ksp_onchain_transport_lib::SolanaStandardWsSession::connect(self.ws_endpoint.clone()) => result,
};
let session = match connected {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(source_transport_error(error.code())),
};
let config = ksp_onchain_transport_lib::SolanaBlockSubscribeConfig::new(
std::option::Option::Some(self.commitment),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::Base64),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(false),
);
let subscribed = tokio::select! {
biased;
_ = stop_receiver.changed() => {
let _closed = session.close().await;
return std::result::Result::Ok(());
}
result = session.block_subscribe(&self.filter, std::option::Option::Some(&config)) => result,
};
let mut subscription = match subscribed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let _closed = session.close().await;
return std::result::Result::Err(source_transport_error(error.code()));
},
};
let mut processing_frontier = RawTransactionIngestProcessingFrontierReporter::new(processing_frontier_sender);
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Active);
let mut fault = std::option::Option::None;
'source: loop {
if *stop_receiver.borrow() {
break;
}
let notification = tokio::select! {
biased;
_ = stop_receiver.changed() => {
break;
}
value = subscription.recv() => value,
};
let notification = match notification {
std::option::Option::Some(std::result::Result::Ok(value)) => value,
std::option::Option::Some(std::result::Result::Err(error)) => {
fault = std::option::Option::Some(source_transport_error(error.code()));
break;
},
std::option::Option::None => {
let error = match subscription.terminal_error_code() {
std::option::Option::Some(code) => source_transport_error(code),
std::option::Option::None => crate::runtime_error("source.standard_block_subscription_closed"),
};
fault = std::option::Option::Some(error);
break;
},
};
let received_at = match current_raw_timestamp() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
fault = std::option::Option::Some(error);
break;
},
};
let (slot, ingresses) = match project_standard_block_ingresses(&self, &settings, &notification, received_at) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
fault = std::option::Option::Some(error);
break;
},
};
if ingresses.is_empty() {
if let std::result::Result::Err(error) = processing_frontier.observe_settled(slot) {
fault = std::option::Option::Some(error);
break;
}
continue;
}
for ingress in ingresses {
let sent = tokio::select! {
biased;
_ = stop_receiver.changed() => {
break 'source;
}
result = admission_sender.send(ingress) => result,
};
if sent.is_err() {
if *stop_receiver.borrow() {
break 'source;
}
fault = std::option::Option::Some(crate::runtime_error("source.admission_closed"));
break 'source;
}
}
if let std::result::Result::Err(error) = processing_frontier.observe_settled(slot) {
fault = std::option::Option::Some(error);
break;
}
}
processing_frontier.discard_all_pending();
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Closing);
let closed = session.close().await;
if let std::option::Option::Some(error) = fault {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Failed);
return std::result::Result::Err(error);
}
return match closed {
std::result::Result::Ok(()) => {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Closed);
std::result::Result::Ok(())
},
std::result::Result::Err(error) => {
processing_frontier.set_source_state(crate::RawTransactionIngestSourceState::Failed);
std::result::Result::Err(source_transport_error(error.code()))
},
};
}
}
impl std::fmt::Debug for crate::RawTransactionIngestStandardBlockSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("RawTransactionIngestStandardBlockSource")
.field("ws_endpoint_name", &self.ws_endpoint.name())
.field("ws_provider", &self.ws_endpoint.provider().as_str())
.field("network", &self.network.as_str())
.field("commitment", &self.commitment)
.field("filter_kind", &standard_block_filter_kind(&self.filter))
.field("filter_fingerprint_bytes", &self.filter_fingerprint.len())
.field("source_key_bytes", &self.source_key.len())
.finish();
}
}
/// Validated standard Solana `logsSubscribe` plus HTTP hydration source owned by the continuous RAW transaction ingest Worker.
///
/// The caller provides one Transport-owned standard WebSocket endpoint, one standard logs filter, a Confirmed/Finalized commitment and one HTTP hydration
@@ -848,8 +1055,8 @@ impl std::fmt::Debug for crate::RawTransactionIngestYellowstoneSource {
/// Caller-composed runtime resources accepted by the continuous RAW transaction ingest Worker.
///
/// The aggregate owns a bounded collection of capability-specific live-source contracts. `pre.003` supports Yellowstone and standard Solana logs sources while
/// deliberately keeping simultaneous multi-source supervision gated until the dedicated supervisor tranche.
/// The aggregate owns a bounded collection of capability-specific live-source contracts. `pre.004` supports Yellowstone, standard Solana logs and standard
/// Solana block sources while deliberately keeping simultaneous multi-source supervision gated until the dedicated supervisor tranche.
pub struct RawTransactionIngestRuntimeResources {
sources: std::vec::Vec<RawTransactionIngestLiveSource>,
}
@@ -861,6 +1068,12 @@ impl crate::RawTransactionIngestRuntimeResources {
return Self { sources: std::vec![RawTransactionIngestLiveSource::Yellowstone(yellowstone_source)] };
}
/// Creates one runtime-resource aggregate from one standard Solana `blockSubscribe` direct RAW source contract.
#[must_use]
pub fn from_standard_block_source(source: crate::RawTransactionIngestStandardBlockSource) -> Self {
return Self { sources: std::vec![RawTransactionIngestLiveSource::StandardBlock(source)] };
}
/// Creates one runtime-resource aggregate from one standard Solana `logsSubscribe` plus HTTP hydration source contract.
#[must_use]
pub fn from_standard_logs_source(source: crate::RawTransactionIngestStandardLogsSource) -> Self {
@@ -894,6 +1107,27 @@ impl crate::RawTransactionIngestRuntimeResources {
return std::result::Result::Ok(());
}
/// Adds one validated standard Solana block source while preserving the global 1..32 bound, one-network invariant and unique logical source identity.
pub fn try_push_standard_block_source(&mut self, source: crate::RawTransactionIngestStandardBlockSource) -> ksp_core_lib::Result<()> {
if self.sources.len() >= crate::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES {
return std::result::Result::Err(crate::runtime_error("runtime_resources.source_count_exceeded"));
}
let candidate = RawTransactionIngestLiveSource::StandardBlock(source);
let expected_network = match self.sources.first() {
std::option::Option::Some(source) => source.network(),
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("runtime_resources.source_collection_empty")),
};
if candidate.network() != expected_network {
return std::result::Result::Err(crate::runtime_error("runtime_resources.source_network_mismatch"));
}
let source_key = candidate.source_key();
if self.sources.iter().any(|source| return source.source_key() == source_key) {
return std::result::Result::Err(crate::runtime_error("runtime_resources.duplicate_source_identity"));
}
self.sources.push(candidate);
return std::result::Result::Ok(());
}
/// Adds one validated standard Solana logs source while preserving the global 1..32 bound, one-network invariant and unique logical source identity.
pub fn try_push_standard_logs_source(&mut self, source: crate::RawTransactionIngestStandardLogsSource) -> ksp_core_lib::Result<()> {
if self.sources.len() >= crate::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES {
@@ -984,6 +1218,43 @@ impl std::hash::Hasher for RawTransactionIngestSourceKeyHashWriter<'_> {
}
}
fn standard_block_filter_fingerprint(filter: &ksp_onchain_transport_lib::SolanaBlockSubscribeFilter) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(RAW_TRANSACTION_INGEST_STANDARD_BLOCK_FILTER_FINGERPRINT_DOMAIN);
match filter {
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All => hash_live_source_key_component(&mut hasher, b"all"),
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::MentionsAccountOrProgram(pubkey) => {
hash_live_source_key_component(&mut hasher, b"mentionsAccountOrProgram");
hash_live_source_key_component(&mut hasher, &pubkey.to_bytes());
},
}
return hasher.finalize().into();
}
fn standard_block_filter_kind(filter: &ksp_onchain_transport_lib::SolanaBlockSubscribeFilter) -> &'static str {
return match filter {
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All => "all",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::MentionsAccountOrProgram(_) => "mentions_account_or_program",
};
}
fn standard_block_live_source_key(
network: &ksp_store_lib::RawNetworkId,
route: &RawTransactionIngestSourceRoute,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
filter_fingerprint: &[u8; 32],
) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(RAW_TRANSACTION_INGEST_LIVE_SOURCE_KEY_DOMAIN);
hash_live_source_key_component(&mut hasher, b"standard_block");
hash_live_source_key_component(&mut hasher, network.as_str().as_bytes());
hash_live_source_key_component(&mut hasher, route.provider.as_str().as_bytes());
hash_live_source_key_component(&mut hasher, route.endpoint_id.as_str().as_bytes());
hash_live_source_key_component(&mut hasher, commitment.as_str().as_bytes());
hash_live_source_key_component(&mut hasher, filter_fingerprint);
return hasher.finalize().into();
}
fn standard_logs_filter_fingerprint(filter: &ksp_onchain_transport_lib::SolanaLogsSubscribeFilter) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(RAW_TRANSACTION_INGEST_STANDARD_LOGS_FILTER_FINGERPRINT_DOMAIN);
@@ -1232,6 +1503,168 @@ fn project_yellowstone_signal<T: RawTransactionIngestYellowstoneSignalView>(
};
}
fn project_standard_block_ingresses(
source: &crate::RawTransactionIngestStandardBlockSource,
settings: &crate::RawTransactionIngestSettings,
notification: &ksp_onchain_transport_lib::SolanaRpcResponse<ksp_onchain_transport_lib::SolanaBlockNotification>,
received_at: ksp_store_lib::RawTimestamp,
) -> ksp_core_lib::Result<(u64, std::vec::Vec<crate::RawTransactionIngress>)> {
let value = notification.value();
if notification.context().slot() != value.slot() {
return std::result::Result::Err(crate::runtime_error("source.standard_block_context_slot_mismatch"));
}
if value.err().is_some() {
return std::result::Result::Err(crate::runtime_error("source.standard_block_remote_error"));
}
let block = match value.block() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("source.standard_block_missing")),
};
let transactions = match block.transactions() {
ksp_onchain_transport_lib::SolanaWireField::Value(value) => value,
ksp_onchain_transport_lib::SolanaWireField::Omitted | ksp_onchain_transport_lib::SolanaWireField::Null => {
return std::result::Result::Err(crate::runtime_error("source.standard_block_transactions_missing"));
},
};
let provenance = build_standard_block_provenance(source, settings, received_at);
let provenance = match provenance {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut ingresses = std::vec::Vec::with_capacity(transactions.len());
for (position, transaction) in transactions.iter().enumerate() {
let material = build_standard_block_material_from_transaction(&source.network, value.slot(), block.block_time(), transaction, position);
let material = match material {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
ingresses.push(crate::RawTransactionIngress {
material,
network: source.network.clone(),
provenance: provenance.clone(),
source_key: source.source_key,
});
}
return std::result::Result::Ok((value.slot(), ingresses));
}
fn build_standard_block_material_from_transaction(
network: &ksp_store_lib::RawNetworkId,
slot: u64,
block_time: std::option::Option<i64>,
transaction: &ksp_onchain_transport_lib::SolanaBlockTransaction,
position: usize,
) -> ksp_core_lib::Result<ksp_raw_transaction_lib::RawTransactionMaterial> {
let transaction_data = match transaction.transaction() {
ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary { data, encoding }
if *encoding == ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64 =>
{
data.as_str()
},
ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary { .. }
| ksp_onchain_transport_lib::SolanaEncodedTransaction::LegacyBinary(_)
| ksp_onchain_transport_lib::SolanaEncodedTransaction::Json(_) => {
return std::result::Result::Err(crate::runtime_error("source.standard_block_transaction_encoding_invalid"));
},
};
return build_standard_block_material(network, slot, block_time, transaction_data, transaction.meta(), transaction.version(), position);
}
fn build_standard_block_material(
network: &ksp_store_lib::RawNetworkId,
slot: u64,
block_time: std::option::Option<i64>,
transaction_data: &str,
meta: &ksp_onchain_transport_lib::SolanaWireField<serde_json::Value>,
version: &ksp_onchain_transport_lib::SolanaWireField<ksp_onchain_transport_lib::SolanaTransactionVersion>,
position: usize,
) -> ksp_core_lib::Result<ksp_raw_transaction_lib::RawTransactionMaterial> {
let version = qualify_standard_block_version(version);
let version = match version {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let transaction_index = match u32::try_from(position) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("source.standard_block_transaction_index_invalid")),
};
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64_with_embedded_signature(
network.clone(),
slot,
block_time,
transaction_data,
map_hydration_wire_field(meta, |value| return value.clone()),
version,
ksp_raw_transaction_lib::RawTransactionWireField::Value(transaction_index),
);
return match material {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(_) => std::result::Result::Err(crate::runtime_error("source.standard_block_transaction_signature_invalid")),
};
}
fn qualify_standard_block_version(
version: &ksp_onchain_transport_lib::SolanaWireField<ksp_onchain_transport_lib::SolanaTransactionVersion>,
) -> ksp_core_lib::Result<ksp_raw_transaction_lib::RawTransactionWireField<ksp_raw_transaction_lib::RawTransactionVersion>> {
return match version {
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Legacy) => {
std::result::Result::Ok(ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Legacy))
},
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Number(0)) => {
std::result::Result::Ok(ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Number(0)))
},
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Number(1)) => {
std::result::Result::Ok(ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Number(1)))
},
ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Number(_)) => {
std::result::Result::Err(crate::runtime_error("source.standard_block_transaction_version_unsupported"))
},
ksp_onchain_transport_lib::SolanaWireField::Omitted | ksp_onchain_transport_lib::SolanaWireField::Null => {
std::result::Result::Err(crate::runtime_error("source.standard_block_transaction_version_unqualified"))
},
};
}
fn build_standard_block_provenance(
source: &crate::RawTransactionIngestStandardBlockSource,
settings: &crate::RawTransactionIngestSettings,
received_at: ksp_store_lib::RawTimestamp,
) -> ksp_core_lib::Result<ksp_store_lib::RawAcquisitionProvenance> {
let protocol = match ksp_store_lib::RawProvenanceCode::new(RAW_TRANSACTION_INGEST_STANDARD_BLOCK_PROTOCOL) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("source.standard_block_protocol_unrepresentable")),
};
let acquisition_method = match ksp_store_lib::RawProvenanceCode::new(RAW_TRANSACTION_INGEST_STANDARD_BLOCK_ACQUISITION_METHOD) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("source.standard_block_method_unrepresentable")),
};
let capture_session = match ksp_store_lib::RawProvenanceCode::new(settings.worker_id().as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("source.standard_block_capture_session_unrepresentable")),
};
let commitment = match ksp_store_lib::RawProvenanceCode::new(source.commitment.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("source.standard_block_commitment_unrepresentable")),
};
let filter_id = match fingerprint_filter_code(&source.filter_fingerprint) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(
ksp_store_lib::RawAcquisitionProvenance::new(
source.route.provider.clone(),
protocol,
acquisition_method,
ksp_store_lib::RawAcquisitionOrigin::Live,
received_at,
)
.with_endpoint_id(source.route.endpoint_id.clone())
.with_commitment(commitment)
.with_capture_session_id(capture_session)
.with_filter_id(filter_id),
);
}
trait RawTransactionIngestStandardLogsView {
fn signature(&self) -> &str;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
// version: 19
// version: 20
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
@@ -255,6 +255,51 @@ fn v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydr
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_source_is_direct_raw_and_transport_facade_only() {
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestStandardBlockSource",
"SolanaStandardWsSession::connect",
".block_subscribe(",
"SolanaBlockSubscribeConfig::new",
"SolanaTransactionEncoding::Base64",
"SolanaTransactionDetails::Full",
"std::option::Option::Some(1)",
"std::option::Option::Some(false)",
"build_standard_block_material",
"RawTransactionMaterial::binary_base64_with_embedded_signature",
"RawTransactionVersion::Number(1)",
"block_subscribe",
"source.standard_block_missing",
"source.standard_block_remote_error",
] {
assert!(resources.contains(required), "required pre.004 standard block contract missing: {required}");
}
for forbidden in [
"get_block_observed",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
"unbounded_channel",
] {
assert!(
!runtime_contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
"pre.004 crossed a forbidden standard-block boundary: {forbidden}",
);
}
return;
}
fn runtime_contains(value: &str) -> bool {
return include_str!("../src/runtime.rs").contains(value);
}
#[test]
fn v0_3_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges() {
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: 12
// version: 13
//! External public, security, redaction and release-boundary hardening canaries for `pre.010`.
@@ -161,7 +161,12 @@ fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() {
(include_str!("../src/persistence.rs"), &["RawTransactionIngestPersistenceOutcome", "RawTransactionIngestPersistencePort"]),
(
include_str!("../src/runtime_resources.rs"),
&["RawTransactionIngestStandardLogsSource", "RawTransactionIngestYellowstoneSource", "RawTransactionIngestRuntimeResources"],
&[
"RawTransactionIngestStandardBlockSource",
"RawTransactionIngestStandardLogsSource",
"RawTransactionIngestYellowstoneSource",
"RawTransactionIngestRuntimeResources",
],
),
];
for (source, symbols) in source_contracts {
@@ -566,6 +571,47 @@ fn v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_expli
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit() {
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"RawTransactionIngestStandardBlockSource",
".field(\"filter_kind\"",
".field(\"filter_fingerprint_bytes\"",
".field(\"source_key_bytes\"",
"source.standard_block_context_slot_mismatch",
"source.standard_block_remote_error",
"source.standard_block_missing",
"source.standard_block_transactions_missing",
"source.standard_block_transaction_version_unsupported",
"source.standard_block_transaction_version_unqualified",
"SolanaTransactionVersion::Number(0)",
"SolanaTransactionVersion::Number(1)",
] {
assert!(resources.contains(required), "required pre.004 standard block hardening guard missing: {required}");
}
let source_struct = match resources.split_once("pub struct RawTransactionIngestStandardBlockSource {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestStandardBlockSource") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
for forbidden in ["err:", "block:", "transaction_data:", "url:"] {
assert!(!source_struct.contains(forbidden), "remote/raw material stored in standard block source: {forbidden}");
}
let source_impl = match resources.split_once("impl crate::RawTransactionIngestStandardBlockSource {") {
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for crate::RawTransactionIngestStandardBlockSource") {
std::option::Option::Some((value, _)) => value,
std::option::Option::None => "",
},
std::option::Option::None => "",
};
assert!(!source_impl.contains("observe_pending("), "direct Standard Block source must not inflate hydration_pending");
assert!(source_impl.contains("processing_frontier.observe_settled(slot)"));
return;
}
#[test]
fn v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit() {
let resources = include_str!("../src/runtime_resources.rs");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
// version: 13
// version: 14
//! External public-surface proofs for the RAW transaction ingest Worker foundation.
@@ -224,6 +224,31 @@ fn v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned() {
let _source_new: fn(
ksp_onchain_transport_lib::WsEndpointSettings,
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
ksp_onchain_transport_lib::SolanaCommitment,
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new;
let _resources_from_block: fn(
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source;
let _push_block: fn(
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_standard_block_source;
let root = include_str!("../src/lib.rs");
let resources = include_str!("../src/runtime_resources.rs");
assert!(root.contains("RawTransactionIngestStandardBlockSource"));
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn source_key("] {
assert!(!resources.contains(forbidden), "standard block source implementation escape hatch present: {forbidden}");
}
return;
}
#[test]
fn v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only() {
let _hydration_pending: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
// version: 11
// version: 12
//! Release-completeness canaries through the `pre.010` public/release/security hardening tranche.
@@ -84,6 +84,7 @@ fn pre_010_public_root_export_inventory_is_exact() {
"RawTransactionIngestSnapshotFuture",
"RawTransactionIngestSnapshotSource",
"RawTransactionIngestSourceState",
"RawTransactionIngestStandardBlockSource",
"RawTransactionIngestStandardLogsSource",
"RawTransactionIngestTerminalFuture",
"RawTransactionIngestWorker",
@@ -115,6 +116,7 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
"pre_010_lower_layers_have_no_dependency_return_to_concrete_worker",
"v0_3_12_pre_002_public_root_exposes_contract_types_without_transport_implementation_paths",
"v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit",
"v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit",
] {
assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}");
}
@@ -129,6 +131,7 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
assert!(dependency_boundary.contains("v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension"));
assert!(dependency_boundary.contains("v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap"));
assert!(dependency_boundary.contains("v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydration_only"));
assert!(dependency_boundary.contains("v0_3_13_pre_004_standard_block_source_is_direct_raw_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"));
@@ -137,6 +140,7 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
assert!(public_api.contains("v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_hatch"));
assert!(public_api.contains("v0_3_13_pre_002_multi_source_runtime_resource_surface_is_bounded_and_source_neutral"));
assert!(public_api.contains("v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport_owned"));
assert!(public_api.contains("v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned"));
assert!(public_api.contains("v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only"));
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
return;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
// version: 14
// version: 15
fn grpc_endpoint(cluster: &str) -> std::option::Option<ksp_onchain_transport_lib::YellowstoneGrpcEndpointSettings> {
return grpc_endpoint_with_identity(cluster, "yellowstone-fixture", "fixture-provider");
@@ -83,6 +83,22 @@ fn ws_endpoint(
));
}
fn standard_block_source(
cluster: &str,
endpoint_name: &str,
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
) -> std::option::Option<crate::RawTransactionIngestStandardBlockSource> {
let endpoint = match ws_endpoint(cluster, endpoint_name, "fixture-ws-provider", ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
return match crate::RawTransactionIngestStandardBlockSource::new(endpoint, filter, commitment) {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn standard_logs_source(
cluster: &str,
endpoint_name: &str,
@@ -1839,6 +1855,218 @@ fn v0_3_13_pre_003_runtime_resources_reject_duplicate_standard_logs_identity() {
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_source_validates_protocol_commitment_and_identity() {
let source = match standard_block_source(
"devnet",
"standard-block-a",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let same = match standard_block_source(
"devnet",
"standard-block-a",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let pubkey = ksp_core_lib::Pubkey::new_from_array([7_u8; 32]);
let filtered = match standard_block_source(
"devnet",
"standard-block-a",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::MentionsAccountOrProgram(pubkey),
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(source.source_key, same.source_key);
assert_ne!(source.source_key, filtered.source_key);
let debug = std::format!("{filtered:?}");
assert!(debug.contains("mentions_account_or_program"));
assert!(!debug.contains("WS-SECRET-CANARY"));
assert!(!debug.contains(&pubkey.to_string()));
let invalid_endpoint = match ws_endpoint("devnet", "invalid-block", "fixture-ws-provider", ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(
crate::RawTransactionIngestStandardBlockSource::new(
invalid_endpoint,
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
)
.is_err()
);
let processed_endpoint = match ws_endpoint("devnet", "processed-block", "fixture-ws-provider", ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(
crate::RawTransactionIngestStandardBlockSource::new(
processed_endpoint,
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Processed,
)
.is_err()
);
return;
}
#[test]
fn v0_3_13_pre_004_standard_block_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions() {
for version in [
ksp_onchain_transport_lib::SolanaTransactionVersion::Legacy,
ksp_onchain_transport_lib::SolanaTransactionVersion::Number(0),
ksp_onchain_transport_lib::SolanaTransactionVersion::Number(1),
] {
let qualified = super::qualify_standard_block_version(&ksp_onchain_transport_lib::SolanaWireField::Value(version));
assert!(qualified.is_ok());
}
assert!(
super::qualify_standard_block_version(&ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Number(
2
),))
.is_err()
);
assert!(super::qualify_standard_block_version(&ksp_onchain_transport_lib::SolanaWireField::Omitted).is_err());
assert!(super::qualify_standard_block_version(&ksp_onchain_transport_lib::SolanaWireField::Null).is_err());
return;
}
#[test]
fn v0_3_13_pre_004_legacy_v0_v1_block_materials_are_direct_common_raw_with_exact_version_and_index() {
let legacy_message = ksp_raw_transaction_lib::RawSolanaTransactionMessage::new(
ksp_raw_transaction_lib::RawSolanaMessageVersion::Legacy,
ksp_raw_transaction_lib::RawSolanaMessageHeader::new(1, 0, 1),
vec![[0x21_u8; 32], [0x22_u8; 32]],
[0x31_u8; 32],
vec![ksp_raw_transaction_lib::RawSolanaCompiledInstruction::new(1, vec![0], vec![0xaa, 0xbb])],
vec![],
std::option::Option::None,
);
let v0_message = ksp_raw_transaction_lib::RawSolanaTransactionMessage::new(
ksp_raw_transaction_lib::RawSolanaMessageVersion::V0,
ksp_raw_transaction_lib::RawSolanaMessageHeader::new(1, 0, 0),
vec![[0x23_u8; 32]],
[0x32_u8; 32],
vec![ksp_raw_transaction_lib::RawSolanaCompiledInstruction::new(0, vec![1], vec![0xcc])],
vec![ksp_raw_transaction_lib::RawSolanaAddressTableLookup::new([0x24_u8; 32], vec![0], vec![])],
std::option::Option::None,
);
let v1_message = ksp_raw_transaction_lib::RawSolanaTransactionMessage::new(
ksp_raw_transaction_lib::RawSolanaMessageVersion::V1,
ksp_raw_transaction_lib::RawSolanaMessageHeader::new(1, 0, 1),
vec![[0x41_u8; 32], [0x42_u8; 32]],
[0x51_u8; 32],
vec![ksp_raw_transaction_lib::RawSolanaCompiledInstruction::new(1, vec![0], vec![1, 2, 3])],
vec![],
std::option::Option::Some(ksp_raw_transaction_lib::RawSolanaTransactionConfig::new(
std::option::Option::Some(7),
std::option::Option::Some(8),
std::option::Option::Some(9),
std::option::Option::Some(32_768),
)),
);
let fixtures = [
(
ksp_raw_transaction_lib::RawSolanaTransactionWire::new(vec![[0x11_u8; 64]], legacy_message),
ksp_onchain_transport_lib::SolanaTransactionVersion::Legacy,
[0x11_u8; 64],
"\"version\":\"legacy\"",
1_usize,
),
(
ksp_raw_transaction_lib::RawSolanaTransactionWire::new(vec![[0x12_u8; 64]], v0_message),
ksp_onchain_transport_lib::SolanaTransactionVersion::Number(0),
[0x12_u8; 64],
"\"version\":0",
2_usize,
),
(
ksp_raw_transaction_lib::RawSolanaTransactionWire::new(vec![[0xaa_u8; 64]], v1_message),
ksp_onchain_transport_lib::SolanaTransactionVersion::Number(1),
[0xaa_u8; 64],
"\"version\":1",
3_usize,
),
];
let network = match ksp_store_lib::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
for (wire, version, expected_signature, expected_version, position) in fixtures {
let encoded = match ksp_raw_transaction_lib::serialize_solana_transaction_wire_base64(&wire) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let material = super::build_standard_block_material(
&network,
430_000_123,
std::option::Option::Some(1_787_072_400),
encoded.as_str(),
&ksp_onchain_transport_lib::SolanaWireField::Value(serde_json::json!({"err":null,"fee":5000})),
&ksp_onchain_transport_lib::SolanaWireField::Value(version),
position,
);
let material = match material {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let raw = match ksp_raw_transaction_lib::canonicalize_raw_transaction(material) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(raw.reference().signature().as_bytes(), &expected_signature);
assert_eq!(raw.slot(), 430_000_123);
assert_eq!(raw.block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_787_072_400_000));
let payload = match std::str::from_utf8(raw.payload().bytes()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(payload.contains(expected_version));
assert!(payload.contains(&std::format!("\"transactionIndex\":{position}")));
assert!(payload.contains(encoded.as_str()));
}
return;
}
#[test]
fn v0_3_13_pre_004_runtime_resources_reject_duplicate_standard_block_identity() {
let first = match standard_block_source(
"devnet",
"standard-block-duplicate",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Finalized,
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let duplicate = match standard_block_source(
"devnet",
"standard-block-duplicate",
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter::All,
ksp_onchain_transport_lib::SolanaCommitment::Finalized,
) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let mut resources = crate::RawTransactionIngestRuntimeResources::from_standard_block_source(first);
let result = resources.try_push_standard_block_source(duplicate);
let error = match result {
std::result::Result::Ok(()) => return,
std::result::Result::Err(value) => value,
};
assert!(error.context().iter().any(|context| return context.value() == "runtime_resources.duplicate_source_identity"));
assert_eq!(resources.source_count(), 1);
return;
}
#[test]
fn pre_007_processing_frontier_never_advances_through_pending_work() {
let mut frontier = super::RawTransactionIngestProcessingFrontier::new();

267
deltas/0.3.13/pre.004.md Normal file
View File

@@ -0,0 +1,267 @@
<!-- file: deltas/0.3.13/pre.004.md -->
<!-- version: 1 -->
# Delta 0.3.13-pre.004 — Standard Solana blockSubscribe RAW-direct qualifié
## Base requise
```text
delivery précédente : 0.3.13-pre.003-fix.003
Cargo base : 0.3.13-pre.3.fix.3
delta base : deltas/0.3.13/pre.003-fix.003.md
archive delta base : ksp-general-0.3.13-pre.003-fix.003.zip
```
Le gate opérateur reçu sur cette base est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`75` unit, `4` cross-layer, `10` dependency-boundary, `17` hardening, `11` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait pas le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
## Objectif
Ajouter la deuxième source Solana Standard WebSocket de `0.3.13` sans dupliquer Transport :
```text
Solana standard WS blockSubscribe
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
-> qualification explicite Legacy / V0 / V1
-> Common RAW direct par transaction
-> admission centrale Worker
-> Store
```
La source ne possède pas de fallback HTTP. Tout bloc ou transaction non qualifiable échoue explicitement et sûrement.
## Version
```text
livraison : 0.3.13-pre.004
workspace.package.version : 0.3.13-pre.4
archive : ksp-general-0.3.13-pre.004.zip
```
`Cargo.toml` passe également de la version de fichier `545` à `546`.
## Source Standard Block
Nouvelle surface publique Worker :
```text
RawTransactionIngestStandardBlockSource
RawTransactionIngestRuntimeResources::from_standard_block_source
RawTransactionIngestRuntimeResources::try_push_standard_block_source
```
Construction caller-composed :
```text
WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized
```
La construction est sans I/O. Elle valide le protocole, le commitment, le réseau et la représentation sûre provider/endpoint avant spawn.
Au runtime, la source réutilise exclusivement :
```text
SolanaStandardWsSession::connect
SolanaStandardWsSession::block_subscribe
```
Configuration exacte :
```text
encoding = Base64
transactionDetails = Full
maxSupportedTransactionVersion = 1
showRewards = false
```
Aucun client WebSocket direct, `getBlock`, `getTransaction`, `reqwest`, `tokio-tungstenite`, `tonic` ou SDK provider n'est ajouté au Worker.
## Qualification RAW-direct
La version est fail-closed :
```text
Legacy -> RawTransactionVersion::Legacy
0 -> RawTransactionVersion::Number(0)
1 -> RawTransactionVersion::Number(1)
>1 -> source.standard_block_transaction_version_unsupported
omise/null -> source.standard_block_transaction_version_unqualified
```
Le matériau direct conserve :
```text
signature embarquée dans le wire Base64
slot
blockTime
meta
version qualifiée
transactionIndex dans le bloc
```
Les fixtures locales construisent et sérialisent des wires Legacy, V0 et V1 avec les types publics de `ksp-raw-transaction-lib`, puis vérifient le Common RAW canonique obtenu. La preuve V1 ne repose donc pas uniquement sur une étiquette `version=1`.
La parité historique `blockSubscribe` / `getBlock` Legacy reste déjà matérialisée dans `ksp-job-backfill-lib/tests/ws_raw_parity.rs`; `pre.004` rejoue localement les matérialisations Legacy/V0 et ajoute la qualification V1.
## block:null et formes ambiguës
Les cas suivants sont des fautes sûres avant progression de frontier :
```text
context.slot différent du slot de notification
notification err non null
block null
transactions omitted/null
transaction non Base64
version omise/null/>1
signature embarquée invalide
transaction index non représentable en u32
```
`block: null` n'est jamais assimilé à un bloc vide ou à un slot traité. Le décodage Transport qui distingue `block:null` et `err` est conservé tel quel.
Pour un bloc non vide, le slot n'est enregistré settled qu'après l'admission réussie de toutes ses transactions. La voie RAW-direct n'incrémente pas `hydration_pending`; le processing frontier ne peut donc pas avancer transitoirement au milieu du traitement d'un même bloc et un stop/faute partiel ne devient jamais une progression.
## Identité logique et redaction
Le `source_key` Standard Block est dérivé de :
```text
domain KSP live-source v1
family = standard_block
network
safe provider identity
safe endpoint identity
commitment
private block-filter fingerprint
```
Les filtres `All` et `MentionsAccountOrProgram(pubkey)` sont distingués par empreinte privée. Le pubkey brut n'est pas recopié dans `Debug`, snapshot ou provenance textuelle.
Le bound global `1..32`, l'invariant réseau unique et le rejet transactionnel des doublons de `pre.002` s'appliquent à cette nouvelle famille.
## Activation runtime
Le contrat reste volontairement mono-source avant `pre.007` :
```text
1 source Yellowstone : productif
1 source Standard Logs : productif
1 source Standard Block : productif
2..32 sources : composition validable
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
```
Aucune source configurée n'est silencieusement ignorée.
## Preuves ajoutées
Unit tests Worker :
```text
validation protocole/commitment Standard Block
source_key stable et filter-sensitive
redaction filtre/url/source-key
qualification exacte Legacy/V0/V1
rejet version omise/null/>1
wires Legacy/V0/V1 -> Common RAW direct avec signature/version/index exacts
rejet duplicate Standard Block identity
```
Canaris externes :
```text
public_api : construction/composition depuis crate root
dependency_boundary : façade Transport seulement, sans fallback HTTP/direct network crate
hardening : block:null, erreurs, version, encoding et redaction explicites
release_completeness : export et canaris pre.004 obligatoires
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
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/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
```
## Fichiers ajoutés
```text
deltas/0.3.13/pre.004.md
```
## Fichiers supprimés
```text
aucun
```
## Non-claims
`pre.004` ne prétend pas :
```text
Helius transactionSubscribe productif
HTTP live block polling
supervision simultanée de plusieurs sources
fanout Store d'observations multi-source
fallback HTTP getBlock/getTransaction pour Standard Block
RAW-direct pour version >1 ou version ambiguë
block:null considéré comme progression
nouvelle dépendance externe
```
## Validation dans l'environnement d'assemblage
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.004` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan des identifiants normatifs et le contrôle exhaustif du diff sont exécutés avant packaging et leurs résultats finaux sont consignés ci-dessous avant livraison.
## Gate opérateur requis avant pre.005
```bash
cargo fmt --all -- --check
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-onchain-transport-lib
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
```
`pre.005` ne doit pas commencer si ce gate révèle une anomalie.
## Résultats statiques finaux de l'assemblage
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 tables, 835 files)
Normative rule definitions: 489
Unique normative IDs: 489
Duplicates: 0
```
Contrôle exhaustif avant packaging :
```text
12 fichiers existants modifiés
1 fichier ajouté
0 fichier supprimé
12/12 headers versionnés incrémentés exactement de +1
Cargo.toml hors header : uniquement workspace.package.version 0.3.13-pre.3.fix.3 -> 0.3.13-pre.4
aucune dépendance ajoutée, supprimée ou modifiée
```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Plan v0.3.13 — WS standard / Helius / HTTP live + convergence multi-source RawTransaction
@@ -512,6 +512,29 @@ Retry/reconnect/resubscribe : Transport WS.
Preuve : parité bytes/hash entre notification blockSubscribe et `getBlock`/fixture Common RAW pour Legacy/V0 puis V1 ; canari `block:null` ; unsupported validator explicite.
### 14.1 Décisions matérialisées en pre.004
`RawTransactionIngestStandardBlockSource` est caller-composed à partir d'un `WsEndpointSettings` `solana_standard`, d'un `SolanaBlockSubscribeFilter` et d'un commitment explicite `Confirmed` ou `Finalized`. Elle réutilise exclusivement `SolanaStandardWsSession` et son `block_subscribe`; aucun acteur WebSocket Worker parallèle n'est ajouté.
La requête runtime est figée à :
```text
encoding = Base64
transactionDetails = Full
maxSupportedTransactionVersion = 1
showRewards = false
```
La tranche qualifie RAW-direct exactement `Legacy`, `V0` et `V1`. La preuve V1 est locale et déterministe : une fixture wire V1 est sérialisée avec les types publics de `ksp-raw-transaction-lib`, injectée dans le projecteur de bloc, puis comparée au Common RAW canonique attendu. Une version omise/nulle ou `>1` reste non qualifiée et produit une faute sûre. Aucun fallback HTTP `getBlock` ou `getTransaction` n'est inventé dans cette source.
Le matériau direct conserve la signature embarquée, le slot de notification, le `blockTime`, le `meta`, la version explicitement qualifiée et l'index de transaction dans le bloc. `block: null`, une erreur distante, un mismatch `context.slot`, un champ transactions omis/nul ou un encodage non Base64 échouent avant toute progression de frontier.
Pour un bloc de plusieurs transactions, le Worker ne projette le slot settled qu'après l'admission réussie de toutes ses transactions. La voie RAW-direct n'utilise pas le compteur `hydration_pending`; un stop ou une faute au milieu du bloc laisse la frontier en deçà du slot.
L'identité logique inclut la famille `standard_block`, le réseau, les identités provider/endpoint sûres, le commitment et une empreinte privée du filtre. La valeur brute d'un filtre `MentionsAccountOrProgram(pubkey)` ne sort pas dans `Debug` ou la provenance.
La barrière de `pre.002` reste active : une seule source Yellowstone, Standard Logs ou Standard Block peut être exécutée. Une collection `2..32` reste rejetée avant spawn jusqu'au supervisor `pre.007`.
## 15. Contrat source Helius transactionSubscribe
Entrée caller-composed :

View File

@@ -1,11 +1,11 @@
<!-- file: docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Validation v0.3.13 — WS standard / Helius / HTTP live + convergence multi-source
## 1. Rôle
Ce document enregistre les preuves réellement exécutées et les non-claims de `0.3.13`. `pre.001` ferme l'audit/planification, `pre.002` matérialise la composition runtime multi-source et `pre.003` ajoute la source productive Standard Solana `logsSubscribe` + hydration HTTP observée, sans activer encore la supervision simultanée.
Ce document enregistre les preuves réellement exécutées et les non-claims de `0.3.13`. `pre.001` ferme l'audit/planification, `pre.002` matérialise la composition runtime multi-source, `pre.003` ajoute Standard Solana `logsSubscribe` + hydration HTTP observée et `pre.004` ajoute Standard Solana `blockSubscribe` RAW-direct qualifié Legacy/V0/V1, sans activer encore la supervision simultanée.
## 2. Archive stable contrôlée
@@ -811,3 +811,157 @@ cargo tree --duplicates
```
`pre.004` ne doit pas commencer si une anomalie de format, règle, compilation, API, dépendance ou test apparaît.
## 35. Gate opérateur pre.003-fix.003 reçu avant pre.004
Le gate fourni par l'opérateur sur `0.3.13-pre.3.fix.3` est vert pour toutes les commandes exécutées :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS, silencieux
General Rust rule audit : clean
Rust export completeness audit : 0 candidate
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables, 834 files)
cargo check --workspace : PASS
clippy workspace/all-targets/all-features -D warnings : PASS
ksp-worker-raw-transaction-ingest-lib unit tests : 75 pass
cross_layer_completeness : 4 pass
dependency_boundary : 10 pass
hardening : 17 pass
public_api : 11 pass
release_completeness : 4 pass
worker doc-tests : 0
```
Cette exécution ne contenait pas `cargo test -p ksp-onchain-transport-lib` ni les commandes `cargo tree`; aucune nouvelle affirmation n'est ajoutée pour ces commandes. Le gate Worker demandé après `pre.003-fix.003` est satisfait.
## 36. Implémentation pre.004 — Standard Block RAW-direct
`pre.004` ajoute `RawTransactionIngestStandardBlockSource` à l'agrégat runtime existant. La construction reste sans I/O et exige :
```text
WsEndpointSettings kind solana_standard
SolanaBlockSubscribeFilter
SolanaCommitment Confirmed ou Finalized
```
Au runtime, la source réutilise `SolanaStandardWsSession::connect` et `block_subscribe` avec :
```text
encoding = Base64
transactionDetails = Full
maxSupportedTransactionVersion = 1
showRewards = false
```
Aucun `HttpTransportPool`, aucun `getBlock` HTTP et aucun second acteur WebSocket ne sont ajoutés à cette source.
## 37. Qualification RAW-direct et sûreté block:null
La projection accepte uniquement des versions explicitement qualifiées :
```text
Legacy -> RawTransactionVersion::Legacy
0 -> RawTransactionVersion::Number(0)
1 -> RawTransactionVersion::Number(1)
>1 -> source.standard_block_transaction_version_unsupported
omise/null -> source.standard_block_transaction_version_unqualified
```
Le matériau Common RAW direct est construit à partir du wire Base64 de chaque transaction avec signature embarquée, slot, `blockTime`, `meta`, version et index de transaction. La fixture V1 locale sérialise un vrai `RawSolanaTransactionMessage::V1` par les types publics de `ksp-raw-transaction-lib` avant projection ; la tranche ne déduit donc pas V1 d'une simple étiquette.
Les gardes suivantes sont fail-closed avant progression de frontier :
```text
context.slot != notification.slot
notification.err != null
block == null
transactions omitted/null
transaction non Base64
version omise/null/>1
signature embarquée invalide
index usize non représentable en u32
```
`block: null` n'est jamais traité comme bloc vide ou slot settled. Pour un bloc non vide, le slot n'est enregistré settled qu'après l'admission réussie de toutes ses transactions. La voie directe ne détourne pas `hydration_pending`; un arrêt ou une faute au milieu du bloc ne produit aucune progression transitoire.
## 38. Identité, redaction et composition pre.004
La clé logique Standard Block est dérivée de :
```text
domain KSP live source key
family = standard_block
network
safe provider identity
safe endpoint identity
commitment
private block-filter fingerprint
SHA-256 -> [u8; 32]
```
`Debug` n'expose que des identités sûres, le type de filtre et les longueurs d'empreinte/clé. Un pubkey de `MentionsAccountOrProgram` n'est pas recopié. L'agrégat public gagne `from_standard_block_source` et `try_push_standard_block_source` avec les mêmes bornes `1..32`, invariant réseau unique et rejet transactionnel des doublons.
Une seule source reste productivement activable. Une collection `2..32` demeure `runtime_resources.multi_source_activation_pending` avant spawn jusqu'à `pre.007`.
## 39. Preuves déterministes ajoutées pre.004
Worker unit tests ajoutés couvrent :
```text
validation protocol/commitment Standard Block
source_key stable, filter-sensitive et redacted
qualification exacte Legacy/V0/V1
rejet version omise/null/>1
fixture V1 wire -> matériau Common RAW direct exact
rejet duplicate Standard Block identity
```
Canaris externes ajoutés/étendus :
```text
public_api : construction et composition Standard Block depuis crate root
dependency_boundary : façade Transport uniquement, sans getBlock HTTP/direct network crate
hardening : block:null/version/encoding/redaction explicites
release_completeness : export et canaris pre.004 obligatoires
```
## 40. Validations locales post-modification pre.004
Le toolchain Cargo/Rustfmt reste indisponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.004` sont donc `NON EXÉCUTÉ LOCAL`; seuls les audits statiques effectivement lancés sont déclarés PASS dans le delta de livraison.
## 41. Non-claims pre.004
`pre.004` ne prétend pas :
```text
avoir ajouté Helius transactionSubscribe productif
avoir ajouté HTTP live polling
avoir activé plusieurs sources simultanément
avoir implémenté le fanout d'observations Store multi-source
avoir ajouté un fallback HTTP getBlock/getTransaction à Standard Block
avoir étendu le RAW-direct à une version >1 ou ambiguë
avoir transformé block:null en progression
avoir ajouté une dépendance réseau directe au Worker
avoir exécuté Cargo localement après modification
```
## 42. Gate opérateur requis avant pre.005
Après application du delta :
```bash
cargo fmt --all -- --check
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-onchain-transport-lib
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
```
`pre.005` ne doit pas commencer si une anomalie de format, règle, compilation, API, dépendance ou test apparaît.