148 lines
5.6 KiB
Rust
148 lines
5.6 KiB
Rust
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
|
|
// version: 8
|
|
|
|
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
|
|
|
|
#[test]
|
|
fn pre_002_manifest_dependency_surface_is_exact() {
|
|
let manifest = include_str!("../Cargo.toml");
|
|
let dependencies = dependency_section(manifest);
|
|
let names = manifest_dependency_names(dependencies);
|
|
assert_eq!(names, vec!["ksp-core-lib", "ksp-logging-lib", "ksp-raw-transaction-lib", "ksp-store-lib", "ksp-worker-api", "sha2", "tokio",],);
|
|
for required in [
|
|
"ksp-core-lib = { path = \"../ksp-core-lib\" }",
|
|
"ksp-logging-lib = { path = \"../ksp-logging-lib\" }",
|
|
"ksp-raw-transaction-lib = { path = \"../ksp-raw-transaction-lib\" }",
|
|
"ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }",
|
|
"ksp-worker-api = { path = \"../ksp-worker-api\" }",
|
|
"sha2 = { workspace = true }",
|
|
"tokio = { workspace = true, features = [\"macros\", \"rt\", \"sync\", \"time\"] }",
|
|
] {
|
|
assert!(dependencies.contains(required), "required Worker dependency missing: {required}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() {
|
|
let manifest = include_str!("../Cargo.toml");
|
|
let dependencies = dependency_section(manifest);
|
|
for forbidden in [
|
|
"futures-util",
|
|
"ksp-config-lib",
|
|
"ksp-interface-lib",
|
|
"ksp-job-api",
|
|
"ksp-job-backfill-lib",
|
|
"ksp-onchain-transport-lib",
|
|
"ksp-program-api",
|
|
"ksp-store-api",
|
|
"ksp-store-postgres-lib",
|
|
"ksp-wallet-lib",
|
|
"reqwest",
|
|
"solana-",
|
|
"tokio-tungstenite",
|
|
"tonic",
|
|
"yellowstone-grpc-proto",
|
|
] {
|
|
assert!(!dependencies.contains(forbidden), "forbidden Worker dependency present: {forbidden}");
|
|
}
|
|
assert!(!manifest.contains("[dev-dependencies]"), "foundation requires no test-only dependency");
|
|
assert!(!manifest.contains("[build-dependencies]"), "foundation requires no build dependency");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_009_source_surface_hardens_shutdown_and_faults_without_backend_or_live_source() {
|
|
let root = include_str!("../src/lib.rs");
|
|
let runtime = include_str!("../src/runtime.rs");
|
|
let admission = include_str!("../src/admission.rs");
|
|
let persistence = include_str!("../src/persistence.rs");
|
|
let snapshot = include_str!("../src/snapshot.rs");
|
|
for required in [
|
|
"tokio::sync::mpsc::channel",
|
|
"canonicalize_raw_transaction",
|
|
"assemble_raw_transaction_acquisition",
|
|
"RawTransactionIngestPersistencePort",
|
|
"RawTransactionAcquisitionMode::Normal",
|
|
"RawTransactionIngestSnapshot",
|
|
"RawTransactionIngestSnapshotSource",
|
|
"WorkerSnapshotSource",
|
|
"tokio::sync::watch::channel",
|
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED",
|
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT",
|
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED",
|
|
"record_source_failure",
|
|
"tokio::time::timeout",
|
|
"abort_all",
|
|
"backpressure_wait_total",
|
|
] {
|
|
assert!(
|
|
root.contains(required)
|
|
|| runtime.contains(required)
|
|
|| admission.contains(required)
|
|
|| persistence.contains(required)
|
|
|| snapshot.contains(required),
|
|
"required pre.009 hardening contract missing: {required}"
|
|
);
|
|
}
|
|
for forbidden in [
|
|
"pub use self::admission::RawTransactionAdmission",
|
|
"pub use self::admission::RawTransactionIngress",
|
|
"pub use self::persistence::RawTransactionIngestPersistencePort",
|
|
"unbounded_channel",
|
|
"ksp_store_postgres_lib::",
|
|
"ksp_onchain_transport_lib::",
|
|
"ForceRehydrate",
|
|
"ksp_config_lib::",
|
|
"reqwest::",
|
|
] {
|
|
assert!(
|
|
!root.contains(forbidden)
|
|
&& !runtime.contains(forbidden)
|
|
&& !admission.contains(forbidden)
|
|
&& !persistence.contains(forbidden)
|
|
&& !snapshot.contains(forbidden),
|
|
"pre.009 crossed a forbidden runtime boundary: {forbidden}"
|
|
);
|
|
}
|
|
assert_eq!(runtime.matches("tokio::sync::watch::channel").count(), 2, "runtime retains only the external-control and private-source stop watches");
|
|
assert_eq!(snapshot.matches("tokio::sync::watch::channel").count(), 1, "exactly one shared concrete snapshot watch is allowed");
|
|
return;
|
|
}
|
|
|
|
fn dependency_section(manifest: &str) -> &str {
|
|
let after_header = match manifest.split_once("[dependencies]") {
|
|
std::option::Option::Some((_, section)) => section,
|
|
std::option::Option::None => {
|
|
return "";
|
|
},
|
|
};
|
|
let end = match after_header.find("\n[") {
|
|
std::option::Option::Some(index) => index,
|
|
std::option::Option::None => after_header.len(),
|
|
};
|
|
return &after_header[..end];
|
|
}
|
|
|
|
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
|
let mut names = std::vec::Vec::new();
|
|
for line in section.lines() {
|
|
let content = match line.split('#').next() {
|
|
std::option::Option::Some(value) => value.trim(),
|
|
std::option::Option::None => continue,
|
|
};
|
|
if content.is_empty() {
|
|
continue;
|
|
}
|
|
let name = match content.split('=').next() {
|
|
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
|
|
std::option::Option::None => continue,
|
|
};
|
|
if !name.is_empty() {
|
|
names.push(name);
|
|
}
|
|
}
|
|
names.sort_unstable();
|
|
return names;
|
|
}
|