351 lines
14 KiB
Rust
351 lines
14 KiB
Rust
// file: crates/ksp-job-backfill-lib/tests/hardening.rs
|
|
// version: 4
|
|
|
|
//! Adversarial, security, visibility and external-boundary hardening canaries for `pre.010`.
|
|
|
|
fn signature_text() -> std::string::String {
|
|
return "2".repeat(ksp_job_backfill_lib::MIN_BACKFILL_SIGNATURE_TEXT_BYTES);
|
|
}
|
|
|
|
fn signature() -> std::option::Option<ksp_job_backfill_lib::BackfillSignature> {
|
|
let result = ksp_job_backfill_lib::BackfillSignature::new(signature_text());
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => {
|
|
assert_eq!(error.code(), ksp_job_backfill_lib::ERROR_CODE_BACKFILL_SIGNATURE_INVALID);
|
|
std::option::Option::None
|
|
},
|
|
};
|
|
}
|
|
|
|
fn network(value: &'static str) -> std::option::Option<ksp_store_lib::RawNetworkId> {
|
|
let result = ksp_store_lib::RawNetworkId::new(value);
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => {
|
|
assert_eq!(error.code().domain(), "store_api");
|
|
std::option::Option::None
|
|
},
|
|
};
|
|
}
|
|
|
|
fn job_id(value: &'static str) -> std::option::Option<ksp_job_api::JobId> {
|
|
let result = ksp_job_api::JobId::new(value);
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => {
|
|
assert_eq!(error.code().domain(), "job_api");
|
|
std::option::Option::None
|
|
},
|
|
};
|
|
}
|
|
|
|
fn explicit_scope() -> std::option::Option<ksp_job_backfill_lib::BackfillScope> {
|
|
let signature = match signature() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let result = ksp_job_backfill_lib::BackfillScope::explicit_signatures(vec![signature]);
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => {
|
|
assert_eq!(error.code(), ksp_job_backfill_lib::ERROR_CODE_BACKFILL_REQUEST_INVALID);
|
|
std::option::Option::None
|
|
},
|
|
};
|
|
}
|
|
|
|
fn request(
|
|
id: &'static str,
|
|
network_name: &'static str,
|
|
role_name: &'static str,
|
|
scope: ksp_job_backfill_lib::BackfillScope,
|
|
) -> std::option::Option<ksp_job_backfill_lib::BackfillRequest> {
|
|
let id = match job_id(id) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let network = match network(network_name) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let result = ksp_job_backfill_lib::BackfillRequest::new(
|
|
id,
|
|
network,
|
|
ksp_onchain_transport_lib::HttpRoleName::new(role_name),
|
|
ksp_job_backfill_lib::BackfillCommitment::Confirmed,
|
|
scope,
|
|
100,
|
|
10,
|
|
500,
|
|
8,
|
|
std::option::Option::None,
|
|
);
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => {
|
|
assert_eq!(error.code(), ksp_job_backfill_lib::ERROR_CODE_BACKFILL_REQUEST_INVALID);
|
|
std::option::Option::None
|
|
},
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_external_error_codes_are_stable_unique_and_domain_scoped() {
|
|
let codes = [
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_CHECKPOINT_INVALID, "checkpoint_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_DISCOVERY_INVALID, "discovery_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_DISCOVERY_STALLED, "discovery_stalled"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_EXECUTION_INVALID, "execution_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_PERSISTENCE_INVALID, "persistence_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_RAW_CONVERSION_INVALID, "raw_conversion_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_REQUEST_INVALID, "request_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_RUNTIME_INVALID, "runtime_invalid"),
|
|
(ksp_job_backfill_lib::ERROR_CODE_BACKFILL_SIGNATURE_INVALID, "signature_invalid"),
|
|
];
|
|
for (index, (code, expected)) in codes.iter().enumerate() {
|
|
assert_eq!(code.domain(), "job_backfill");
|
|
assert_eq!(code.code(), *expected);
|
|
for (other_index, (other, _)) in codes.iter().enumerate() {
|
|
if index != other_index {
|
|
assert_ne!(code, other);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_external_request_adversarial_bounds_return_stable_codes() {
|
|
let invalid_signature = ksp_job_backfill_lib::BackfillSignature::new("0".repeat(ksp_job_backfill_lib::MIN_BACKFILL_SIGNATURE_TEXT_BYTES));
|
|
let error = match invalid_signature {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), ksp_job_backfill_lib::ERROR_CODE_BACKFILL_SIGNATURE_INVALID);
|
|
let id = match job_id("backfill:hardening:bounds") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let network = match network("devnet") {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let scope = match explicit_scope() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = ksp_job_backfill_lib::BackfillRequest::new(
|
|
id,
|
|
network,
|
|
ksp_onchain_transport_lib::HttpRoleName::new("history"),
|
|
ksp_job_backfill_lib::BackfillCommitment::Finalized,
|
|
scope,
|
|
0,
|
|
1,
|
|
1,
|
|
1,
|
|
std::option::Option::None,
|
|
);
|
|
let error = match result {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), ksp_job_backfill_lib::ERROR_CODE_BACKFILL_REQUEST_INVALID);
|
|
assert!(error.context().iter().any(|context| return context.key() == "field" && context.value() == "page_size"));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_scope_fingerprint_is_network_sensitive_and_transport_source_independent() {
|
|
let scope = match explicit_scope() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let first = match request("backfill:hardening:fingerprint:a", "devnet", "history-primary", scope.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let second = match request("backfill:hardening:fingerprint:b", "devnet", "history-secondary", scope.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let other_network = match request("backfill:hardening:fingerprint:c", "mainnet", "history-primary", scope) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(first.scope_fingerprint(), second.scope_fingerprint());
|
|
assert_ne!(first.scope_fingerprint(), other_network.scope_fingerprint());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_debug_surfaces_redact_signature_fingerprint_and_runtime_snapshot_payloads() {
|
|
let raw_signature = signature_text();
|
|
let scope = match explicit_scope() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let backfill_request = match request("backfill:hardening:debug", "devnet", "history", scope) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let request_debug = format!("{backfill_request:?}");
|
|
assert!(!request_debug.contains(raw_signature.as_str()));
|
|
assert!(request_debug.contains("BackfillScopeFingerprint(..)"));
|
|
assert!(request_debug.contains("signature_count"));
|
|
let fingerprint_debug = format!("{:?}", backfill_request.scope_fingerprint());
|
|
assert_eq!(fingerprint_debug, "BackfillScopeFingerprint(..)");
|
|
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(backfill_request);
|
|
let runtime = match runtime {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let handle_debug = format!("{:?}", runtime.handle());
|
|
assert!(!handle_debug.contains(raw_signature.as_str()));
|
|
assert!(handle_debug.contains("snapshot"));
|
|
assert!(handle_debug.contains("<redacted>"));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_external_runtime_handle_starts_created_and_cancellation_is_idempotent() {
|
|
let scope = match explicit_scope() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let backfill_request = match request("backfill:hardening:control", "devnet", "history", scope) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let runtime = ksp_job_backfill_lib::BackfillJobRuntime::new(backfill_request);
|
|
let runtime = match runtime {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let handle = runtime.handle();
|
|
let source = handle.snapshots();
|
|
let current = <ksp_job_backfill_lib::BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&source);
|
|
assert_eq!(current.state(), ksp_job_api::JobState::Created);
|
|
assert_eq!(current.snapshot().phase(), ksp_job_backfill_lib::BackfillJobPhase::Created);
|
|
assert!(!handle.is_cancellation_requested());
|
|
assert!(handle.cancel());
|
|
assert!(handle.is_cancellation_requested());
|
|
assert!(!handle.cancel());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_manifest_dependency_surface_is_exact_and_backend_neutral() {
|
|
let manifest = include_str!("../Cargo.toml");
|
|
let mut section = "";
|
|
let mut normal = std::collections::BTreeSet::new();
|
|
let mut dev = std::collections::BTreeSet::new();
|
|
for line in manifest.lines() {
|
|
let trimmed = line.trim();
|
|
if trimmed.starts_with('[') && trimmed.ends_with(']') {
|
|
section = trimmed;
|
|
continue;
|
|
}
|
|
if trimmed.is_empty() || trimmed.starts_with('#') {
|
|
continue;
|
|
}
|
|
let name = match trimmed.split_once('=') {
|
|
std::option::Option::Some((name, _)) => name.trim(),
|
|
std::option::Option::None => continue,
|
|
};
|
|
if section == "[dependencies]" {
|
|
normal.insert(name);
|
|
} else if section == "[dev-dependencies]" {
|
|
dev.insert(name);
|
|
}
|
|
}
|
|
assert_eq!(
|
|
normal,
|
|
std::collections::BTreeSet::from([
|
|
"futures-util",
|
|
"ksp-core-lib",
|
|
"ksp-job-api",
|
|
"ksp-logging-lib",
|
|
"ksp-onchain-transport-lib",
|
|
"ksp-store-lib",
|
|
"serde_json.workspace",
|
|
"sha2.workspace",
|
|
"tokio",
|
|
])
|
|
);
|
|
assert_eq!(dev, std::collections::BTreeSet::from(["tokio"]));
|
|
assert!(manifest.contains("ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }"));
|
|
assert!(manifest.contains("tokio = { workspace = true, features = [\"macros\", \"sync\"] }"));
|
|
assert!(!manifest.contains("ksp-store-postgres-lib"));
|
|
assert!(!manifest.contains("ksp-store-api"));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() {
|
|
let root = include_str!("../src/lib.rs");
|
|
assert!(!root.contains("BackfillRuntimeControl"));
|
|
assert!(!root.contains("TerminalClaim"));
|
|
let source_contracts: [(&str, &[&str]); 3] = [
|
|
(include_str!("../src/checkpoint.rs"), &["BackfillCheckpoint", "CompletionFrontier"]),
|
|
(
|
|
include_str!("../src/request.rs"),
|
|
&["BackfillCommitment", "BackfillSignature", "BackfillScopeKind", "BackfillScope", "BackfillScopeFingerprint", "BackfillRequest"],
|
|
),
|
|
(include_str!("../src/runtime.rs"), &["BackfillJobPhase", "BackfillJobSnapshot", "BackfillJobHandle", "BackfillJobRuntime"]),
|
|
];
|
|
for (source, symbols) in source_contracts {
|
|
for symbol in symbols {
|
|
let required = format!("impl crate::{symbol}");
|
|
assert!(source.contains(required.as_str()), "shared item must use crate-root impl path: {symbol}");
|
|
let forbidden = format!("impl {symbol}");
|
|
assert!(!source.contains(forbidden.as_str()), "shared item must not use bare impl path: {symbol}");
|
|
}
|
|
}
|
|
let request = include_str!("../src/request.rs");
|
|
assert!(request.contains("crate::BackfillScopeFingerprint::from_bytes(bytes)"));
|
|
assert!(!request.contains("crate::BackfillScopeFingerprint(bytes)"));
|
|
assert!(!request.contains("return BackfillScopeFingerprint(bytes)"));
|
|
for (module, source) in [
|
|
("checkpoint", include_str!("../src/checkpoint.rs")),
|
|
("conversion", include_str!("../src/conversion.rs")),
|
|
("discovery", include_str!("../src/discovery.rs")),
|
|
("execution", include_str!("../src/execution.rs")),
|
|
("persistence", include_str!("../src/persistence.rs")),
|
|
("request", include_str!("../src/request.rs")),
|
|
("runtime", include_str!("../src/runtime.rs")),
|
|
] {
|
|
let forbidden = format!("crate::{module}::");
|
|
assert!(!source.contains(forbidden.as_str()), "internal module path bypasses crate-root façade: {forbidden}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_lower_layers_have_no_dependency_return_to_job() {
|
|
for manifest in [
|
|
include_str!("../../ksp-core-lib/Cargo.toml"),
|
|
include_str!("../../ksp-logging-lib/Cargo.toml"),
|
|
include_str!("../../ksp-onchain-transport-lib/Cargo.toml"),
|
|
include_str!("../../ksp-store-api/Cargo.toml"),
|
|
include_str!("../../ksp-store-lib/Cargo.toml"),
|
|
include_str!("../../ksp-store-postgres-lib/Cargo.toml"),
|
|
] {
|
|
assert!(!manifest.contains("ksp-job-api"));
|
|
assert!(!manifest.contains("ksp-job-backfill-lib"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_010_public_root_exposes_no_runtime_or_backend_implementation_types() {
|
|
let root = include_str!("../src/lib.rs");
|
|
for forbidden in
|
|
["pub mod ", "tokio::", "FuturesUnordered", "serde_json::", "sha2::", "ksp_store_api::", "ksp_store_postgres_lib::", "reqwest::", "tonic::"]
|
|
{
|
|
assert!(!root.contains(forbidden), "implementation/backend detail leaked into public root: {forbidden}");
|
|
}
|
|
return;
|
|
}
|