v0.3.13-pre.004
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ¬ification, 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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user