0.3.15-pre.014
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/raw_transaction.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
pub(crate) mod cursor;
|
||||
|
||||
@@ -48,8 +48,26 @@ const UPDATE_ARCHIVED_TRANSACTION_SQL: &str =
|
||||
"UPDATE ksp_raw_transactions SET payload = NULL, retention_state = 'archived' WHERE signature = $1 AND retention_state = 'full'";
|
||||
const UPDATE_PURGED_TRANSACTION_SQL: &str = "UPDATE ksp_raw_transactions SET block_time_unix_millis = NULL, payload = NULL, retention_state = 'purged' WHERE signature = $1 AND retention_state = 'archived'";
|
||||
|
||||
struct RawLogMessagesContentConflictDiagnostic {
|
||||
available: bool,
|
||||
first_mismatch_index: std::string::String,
|
||||
incoming_count: usize,
|
||||
incoming_first_kind: &'static str,
|
||||
incoming_first_length: usize,
|
||||
incoming_has_truncation_marker: bool,
|
||||
incoming_prefix_of_stored: bool,
|
||||
incoming_state: &'static str,
|
||||
stored_count: usize,
|
||||
stored_first_kind: &'static str,
|
||||
stored_first_length: usize,
|
||||
stored_has_truncation_marker: bool,
|
||||
stored_prefix_of_incoming: bool,
|
||||
stored_state: &'static str,
|
||||
}
|
||||
|
||||
struct RawPayloadContentConflictDiagnostic {
|
||||
available: bool,
|
||||
log_messages: RawLogMessagesContentConflictDiagnostic,
|
||||
meta_mismatch: bool,
|
||||
meta_mismatch_fields: std::string::String,
|
||||
other_mismatch: bool,
|
||||
@@ -63,6 +81,7 @@ struct RawTransactionContentConflictDiagnostic {
|
||||
content_hash_mismatch: bool,
|
||||
format_id_mismatch: bool,
|
||||
format_version_mismatch: bool,
|
||||
log_messages: RawLogMessagesContentConflictDiagnostic,
|
||||
meta_mismatch_fields: std::string::String,
|
||||
payload_bytes_mismatch: bool,
|
||||
payload_diagnostic_available: bool,
|
||||
@@ -1138,6 +1157,7 @@ fn raw_transaction_content_conflict_diagnostic(
|
||||
content_hash_mismatch: stored.payload().content_hash() != incoming.payload().content_hash(),
|
||||
format_id_mismatch: stored.payload().format_id() != incoming.payload().format_id(),
|
||||
format_version_mismatch: stored.payload().format_version() != incoming.payload().format_version(),
|
||||
log_messages: payload.log_messages,
|
||||
meta_mismatch_fields: payload.meta_mismatch_fields,
|
||||
payload_bytes_mismatch: stored.payload().bytes() != incoming.payload().bytes(),
|
||||
payload_diagnostic_available: payload.available,
|
||||
@@ -1158,6 +1178,7 @@ fn raw_payload_content_conflict_diagnostic(stored: &[u8], incoming: &[u8]) -> Ra
|
||||
_ => {
|
||||
return RawPayloadContentConflictDiagnostic {
|
||||
available: false,
|
||||
log_messages: raw_log_messages_content_conflict_diagnostic(std::option::Option::None, std::option::Option::None),
|
||||
meta_mismatch: false,
|
||||
meta_mismatch_fields: "unavailable".to_owned(),
|
||||
other_mismatch: false,
|
||||
@@ -1173,8 +1194,10 @@ fn raw_payload_content_conflict_diagnostic(stored: &[u8], incoming: &[u8]) -> Ra
|
||||
let transaction_index_mismatch = stored.get("transactionIndex") != incoming.get("transactionIndex");
|
||||
let other_mismatch = json_object_other_fields_mismatch(&stored, &incoming, RAW_TRANSACTION_CONTENT_CONFLICT_PAYLOAD_FIELDS.as_slice());
|
||||
let meta_mismatch_fields = raw_meta_content_conflict_fields(stored.get("meta"), incoming.get("meta"));
|
||||
let log_messages = raw_log_messages_content_conflict_diagnostic(stored.get("meta"), incoming.get("meta"));
|
||||
return RawPayloadContentConflictDiagnostic {
|
||||
available: true,
|
||||
log_messages,
|
||||
meta_mismatch,
|
||||
meta_mismatch_fields,
|
||||
other_mismatch,
|
||||
@@ -1184,6 +1207,140 @@ fn raw_payload_content_conflict_diagnostic(stored: &[u8], incoming: &[u8]) -> Ra
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_log_messages_content_conflict_diagnostic(
|
||||
stored_meta: std::option::Option<&serde_json::Value>,
|
||||
incoming_meta: std::option::Option<&serde_json::Value>,
|
||||
) -> RawLogMessagesContentConflictDiagnostic {
|
||||
let stored = raw_log_messages_value(stored_meta);
|
||||
let incoming = raw_log_messages_value(incoming_meta);
|
||||
let stored_state = raw_log_messages_state(stored);
|
||||
let incoming_state = raw_log_messages_state(incoming);
|
||||
let (stored_values, incoming_values) = match (stored, incoming) {
|
||||
(std::option::Option::Some(serde_json::Value::Array(stored)), std::option::Option::Some(serde_json::Value::Array(incoming))) => {
|
||||
(stored.as_slice(), incoming.as_slice())
|
||||
},
|
||||
_ => {
|
||||
return RawLogMessagesContentConflictDiagnostic {
|
||||
available: false,
|
||||
first_mismatch_index: "unavailable".to_owned(),
|
||||
incoming_count: raw_log_messages_count(incoming),
|
||||
incoming_first_kind: "unavailable",
|
||||
incoming_first_length: 0,
|
||||
incoming_has_truncation_marker: raw_log_messages_has_truncation_marker(incoming),
|
||||
incoming_prefix_of_stored: false,
|
||||
incoming_state,
|
||||
stored_count: raw_log_messages_count(stored),
|
||||
stored_first_kind: "unavailable",
|
||||
stored_first_length: 0,
|
||||
stored_has_truncation_marker: raw_log_messages_has_truncation_marker(stored),
|
||||
stored_prefix_of_incoming: false,
|
||||
stored_state,
|
||||
};
|
||||
},
|
||||
};
|
||||
let shared_len = stored_values.len().min(incoming_values.len());
|
||||
let first_mismatch = stored_values.iter().zip(incoming_values.iter()).position(|(stored, incoming)| return stored != incoming).or_else(|| {
|
||||
if stored_values.len() == incoming_values.len() {
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return std::option::Option::Some(shared_len);
|
||||
});
|
||||
let (stored_first_kind, stored_first_length) = raw_log_message_kind_and_length(first_mismatch.and_then(|index| return stored_values.get(index)));
|
||||
let (incoming_first_kind, incoming_first_length) = raw_log_message_kind_and_length(first_mismatch.and_then(|index| return incoming_values.get(index)));
|
||||
let first_mismatch_index = match first_mismatch {
|
||||
std::option::Option::Some(value) => value.to_string(),
|
||||
std::option::Option::None => "none".to_owned(),
|
||||
};
|
||||
return RawLogMessagesContentConflictDiagnostic {
|
||||
available: true,
|
||||
first_mismatch_index,
|
||||
incoming_count: incoming_values.len(),
|
||||
incoming_first_kind,
|
||||
incoming_first_length,
|
||||
incoming_has_truncation_marker: raw_log_message_array_has_truncation_marker(incoming_values),
|
||||
incoming_prefix_of_stored: raw_json_array_is_prefix(incoming_values, stored_values),
|
||||
incoming_state,
|
||||
stored_count: stored_values.len(),
|
||||
stored_first_kind,
|
||||
stored_first_length,
|
||||
stored_has_truncation_marker: raw_log_message_array_has_truncation_marker(stored_values),
|
||||
stored_prefix_of_incoming: raw_json_array_is_prefix(stored_values, incoming_values),
|
||||
stored_state,
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_log_messages_value(meta: std::option::Option<&serde_json::Value>) -> std::option::Option<&serde_json::Value> {
|
||||
return match meta {
|
||||
std::option::Option::Some(serde_json::Value::Object(meta)) => meta.get("logMessages"),
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_log_messages_state(value: std::option::Option<&serde_json::Value>) -> &'static str {
|
||||
return match value {
|
||||
std::option::Option::None => "missing",
|
||||
std::option::Option::Some(serde_json::Value::Null) => "null",
|
||||
std::option::Option::Some(serde_json::Value::Array(_)) => "array",
|
||||
std::option::Option::Some(_) => "other",
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_log_messages_count(value: std::option::Option<&serde_json::Value>) -> usize {
|
||||
return match value {
|
||||
std::option::Option::Some(serde_json::Value::Array(values)) => values.len(),
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_json_array_is_prefix(prefix: &[serde_json::Value], values: &[serde_json::Value]) -> bool {
|
||||
if prefix.len() > values.len() {
|
||||
return false;
|
||||
}
|
||||
return prefix.iter().zip(values.iter()).all(|(left, right)| return left == right);
|
||||
}
|
||||
|
||||
fn raw_log_message_kind_and_length(value: std::option::Option<&serde_json::Value>) -> (&'static str, usize) {
|
||||
let line = match value {
|
||||
std::option::Option::Some(serde_json::Value::String(value)) => value.as_str(),
|
||||
std::option::Option::Some(_) => return ("non_string", 0),
|
||||
std::option::Option::None => return ("missing", 0),
|
||||
};
|
||||
let kind = if line.starts_with("Log truncated") || line.starts_with("log truncated") {
|
||||
"log_truncated"
|
||||
} else if line.starts_with("Program log:") {
|
||||
"program_log"
|
||||
} else if line.starts_with("Program data:") {
|
||||
"program_data"
|
||||
} else if line.starts_with("Program ") && line.contains(" invoke [") {
|
||||
"program_invoke"
|
||||
} else if line.starts_with("Program ") && line.ends_with(" success") {
|
||||
"program_success"
|
||||
} else if line.starts_with("Program ") && line.contains(" failed:") {
|
||||
"program_failed"
|
||||
} else if line.starts_with("Program ") && line.contains(" consumed ") && line.contains(" compute units") {
|
||||
"compute_units"
|
||||
} else {
|
||||
"other"
|
||||
};
|
||||
return (kind, line.len());
|
||||
}
|
||||
|
||||
fn raw_log_messages_has_truncation_marker(value: std::option::Option<&serde_json::Value>) -> bool {
|
||||
return match value {
|
||||
std::option::Option::Some(serde_json::Value::Array(values)) => raw_log_message_array_has_truncation_marker(values.as_slice()),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_log_message_array_has_truncation_marker(values: &[serde_json::Value]) -> bool {
|
||||
return values.iter().any(|value| {
|
||||
return match value {
|
||||
serde_json::Value::String(line) => line.starts_with("Log truncated") || line.starts_with("log truncated"),
|
||||
_ => false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn raw_meta_content_conflict_fields(stored: std::option::Option<&serde_json::Value>, incoming: std::option::Option<&serde_json::Value>) -> std::string::String {
|
||||
if stored == incoming {
|
||||
return "none".to_owned();
|
||||
@@ -1227,6 +1384,8 @@ fn log_raw_transaction_content_conflict(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = "store.raw_transaction.content_conflict",
|
||||
network = network.as_str(),
|
||||
stored_slot = stored.slot(),
|
||||
incoming_slot = incoming.slot(),
|
||||
slot_mismatch = diagnostic.slot_mismatch,
|
||||
block_time_mismatch = diagnostic.block_time_mismatch,
|
||||
format_id_mismatch = diagnostic.format_id_mismatch,
|
||||
@@ -1240,6 +1399,20 @@ fn log_raw_transaction_content_conflict(
|
||||
transaction_index_mismatch = diagnostic.payload_transaction_index_mismatch,
|
||||
payload_other_mismatch = diagnostic.payload_other_mismatch,
|
||||
meta_mismatch_fields = diagnostic.meta_mismatch_fields.as_str(),
|
||||
log_messages_diagnostic_available = diagnostic.log_messages.available,
|
||||
stored_log_messages_state = diagnostic.log_messages.stored_state,
|
||||
incoming_log_messages_state = diagnostic.log_messages.incoming_state,
|
||||
stored_log_messages_count = diagnostic.log_messages.stored_count,
|
||||
incoming_log_messages_count = diagnostic.log_messages.incoming_count,
|
||||
log_messages_first_mismatch_index = diagnostic.log_messages.first_mismatch_index.as_str(),
|
||||
stored_log_messages_first_kind = diagnostic.log_messages.stored_first_kind,
|
||||
incoming_log_messages_first_kind = diagnostic.log_messages.incoming_first_kind,
|
||||
stored_log_messages_first_length = diagnostic.log_messages.stored_first_length,
|
||||
incoming_log_messages_first_length = diagnostic.log_messages.incoming_first_length,
|
||||
stored_log_messages_prefix_of_incoming = diagnostic.log_messages.stored_prefix_of_incoming,
|
||||
incoming_log_messages_prefix_of_stored = diagnostic.log_messages.incoming_prefix_of_stored,
|
||||
stored_log_messages_has_truncation_marker = diagnostic.log_messages.stored_has_truncation_marker,
|
||||
incoming_log_messages_has_truncation_marker = diagnostic.log_messages.incoming_has_truncation_marker,
|
||||
"PostgreSQL Store rejected divergent canonical RAW transaction"
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||
// version: 24
|
||||
// version: 25
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -606,3 +606,40 @@ fn v0_3_15_pre_012_fix_004_content_conflict_diagnostic_is_field_only_bounded_and
|
||||
assert!(source.contains("fields.push(\"other\")"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_014_content_conflict_diagnostic_exposes_only_block_slot_and_bounded_log_shape() {
|
||||
let source = include_str!("../src/raw_transaction.rs");
|
||||
for required in [
|
||||
"stored_slot",
|
||||
"incoming_slot",
|
||||
"log_messages_first_mismatch_index",
|
||||
"stored_log_messages_count",
|
||||
"incoming_log_messages_count",
|
||||
"stored_log_messages_first_kind",
|
||||
"incoming_log_messages_first_kind",
|
||||
"stored_log_messages_prefix_of_incoming",
|
||||
"incoming_log_messages_prefix_of_stored",
|
||||
"stored_log_messages_has_truncation_marker",
|
||||
"incoming_log_messages_has_truncation_marker",
|
||||
"raw_log_message_kind_and_length",
|
||||
"raw_log_message_array_has_truncation_marker",
|
||||
] {
|
||||
assert!(source.contains(required), "missing pre.014 bounded logMessages diagnostic field: {required}");
|
||||
}
|
||||
let start = source.find("fn log_raw_transaction_content_conflict(");
|
||||
assert!(start.is_some(), "missing pre.014 conflict logger");
|
||||
let Some(start) = start else {
|
||||
return;
|
||||
};
|
||||
let end = source[start..].find("async fn log_raw_transaction_content_conflict_provenance(");
|
||||
assert!(end.is_some(), "missing pre.014 conflict logger end marker");
|
||||
let Some(end) = end else {
|
||||
return;
|
||||
};
|
||||
let logger = &source[start..start + end];
|
||||
for forbidden in ["signature =", "blockhash =", "previous_blockhash =", "log_message =", "log_line =", "payload ="] {
|
||||
assert!(!logger.contains(forbidden), "pre.014 conflict logger exposes forbidden material: {forbidden}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
fn network() -> ksp_store_api::RawNetworkId {
|
||||
return match ksp_store_api::RawNetworkId::new("devnet") {
|
||||
@@ -429,6 +429,52 @@ fn v0_3_15_pre_012_fix_004_content_conflict_diagnostic_collapses_unknown_meta_ke
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_014_log_messages_conflict_diagnostic_distinguishes_prefix_truncation_and_line_kind_without_text() {
|
||||
let stored = raw_transaction(
|
||||
9,
|
||||
br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program Vote111 success"]},"version":0}"#,
|
||||
7,
|
||||
);
|
||||
let incoming = raw_transaction(
|
||||
9,
|
||||
br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated"]},"version":0}"#,
|
||||
8,
|
||||
);
|
||||
let diagnostic = super::raw_transaction_content_conflict_diagnostic(&stored, &incoming);
|
||||
assert!(diagnostic.log_messages.available);
|
||||
assert_eq!(diagnostic.log_messages.stored_state, "array");
|
||||
assert_eq!(diagnostic.log_messages.incoming_state, "array");
|
||||
assert_eq!(diagnostic.log_messages.stored_count, 3);
|
||||
assert_eq!(diagnostic.log_messages.incoming_count, 3);
|
||||
assert_eq!(diagnostic.log_messages.first_mismatch_index, "2");
|
||||
assert_eq!(diagnostic.log_messages.stored_first_kind, "program_success");
|
||||
assert_eq!(diagnostic.log_messages.incoming_first_kind, "log_truncated");
|
||||
assert!(!diagnostic.log_messages.stored_has_truncation_marker);
|
||||
assert!(diagnostic.log_messages.incoming_has_truncation_marker);
|
||||
assert!(!diagnostic.log_messages.stored_prefix_of_incoming);
|
||||
assert!(!diagnostic.log_messages.incoming_prefix_of_stored);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_014_log_messages_conflict_diagnostic_detects_strict_prefix_without_exposing_line_material() {
|
||||
let stored = raw_transaction(
|
||||
9,
|
||||
br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["Program Vote111 invoke [1]","Program Vote111 success"]},"version":0}"#,
|
||||
7,
|
||||
);
|
||||
let incoming = raw_transaction(9, br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["Program Vote111 invoke [1]"]},"version":0}"#, 8);
|
||||
let diagnostic = super::raw_transaction_content_conflict_diagnostic(&stored, &incoming);
|
||||
assert!(diagnostic.log_messages.available);
|
||||
assert_eq!(diagnostic.log_messages.first_mismatch_index, "1");
|
||||
assert_eq!(diagnostic.log_messages.stored_first_kind, "program_success");
|
||||
assert_eq!(diagnostic.log_messages.incoming_first_kind, "missing");
|
||||
assert!(!diagnostic.log_messages.stored_prefix_of_incoming);
|
||||
assert!(diagnostic.log_messages.incoming_prefix_of_stored);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_observation_idempotence_compares_reference_and_complete_provenance() {
|
||||
let incoming_transaction = raw_transaction(8, &[1, 2, 3], 4);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
|
||||
// version: 48
|
||||
// version: 49
|
||||
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL: std::time::Duration = s
|
||||
pub const MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE: u16 = 1;
|
||||
|
||||
const MAX_RAW_TRANSACTION_INGEST_REPAIR_BURST: usize = 1;
|
||||
const RAW_TRANSACTION_INGEST_BLOCK_IDENTITY_FINGERPRINT_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.block_identity.v1\0";
|
||||
const RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_FILTER_FINGERPRINT_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.helius_transaction.filter.v1\0";
|
||||
const RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_HTTP_PROTOCOL: &str = "helius_ws_http";
|
||||
const RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_HTTP_SOURCE_KEY_DOMAIN: &[u8] = b"ksp.raw_transaction_ingest.helius_transaction_http.source_key.v1\0";
|
||||
@@ -3595,6 +3596,7 @@ async fn fetch_yellowstone_block_ingresses(
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("source.yellowstone_block_not_available")),
|
||||
};
|
||||
log_block_identity("yellowstone_block_hydration", &hydration.network, slot, hydration.commitment, observed.endpoint_name(), observed.provider(), block);
|
||||
let received_at = match current_raw_timestamp() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -3665,6 +3667,7 @@ fn project_http_block_polling_ingresses(
|
||||
observed: &ksp_onchain_transport_lib::HttpObservedValue<std::option::Option<ksp_onchain_transport_lib::SolanaConfirmedBlock>>,
|
||||
received_at: ksp_store_lib::RawTimestamp,
|
||||
) -> ksp_core_lib::Result<std::vec::Vec<crate::RawTransactionIngress>> {
|
||||
log_block_identity("http_block_polling", &source.network, slot, source.commitment, observed.endpoint_name(), observed.provider(), block);
|
||||
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 => {
|
||||
@@ -5573,6 +5576,61 @@ fn hydration_method_code(family: RawTransactionIngestSourceFamily) -> &'static s
|
||||
};
|
||||
}
|
||||
|
||||
fn block_identity_fingerprint(block: &ksp_onchain_transport_lib::SolanaConfirmedBlock) -> std::string::String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(RAW_TRANSACTION_INGEST_BLOCK_IDENTITY_FINGERPRINT_DOMAIN);
|
||||
hash_live_source_key_component(&mut hasher, block.blockhash().as_bytes());
|
||||
hash_live_source_key_component(&mut hasher, block.previous_blockhash().as_bytes());
|
||||
hasher.update(block.parent_slot().to_be_bytes());
|
||||
match block.block_height() {
|
||||
std::option::Option::Some(value) => {
|
||||
hasher.update([1_u8]);
|
||||
hasher.update(value.to_be_bytes());
|
||||
},
|
||||
std::option::Option::None => hasher.update([0_u8]),
|
||||
}
|
||||
let digest: [u8; 32] = hasher.finalize().into();
|
||||
let mut value = std::string::String::with_capacity(71);
|
||||
value.push_str("sha256.");
|
||||
for byte in digest {
|
||||
value.push(char::from(HEX[usize::from(byte >> 4)]));
|
||||
value.push(char::from(HEX[usize::from(byte & 0x0f)]));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
fn log_block_identity(
|
||||
source_kind: &'static str,
|
||||
network: &ksp_store_lib::RawNetworkId,
|
||||
slot: u64,
|
||||
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||
endpoint_name: &str,
|
||||
provider_name: &ksp_onchain_transport_lib::HttpProviderName,
|
||||
block: &ksp_onchain_transport_lib::SolanaConfirmedBlock,
|
||||
) {
|
||||
let fingerprint = block_identity_fingerprint(block);
|
||||
let block_height = match block.block_height() {
|
||||
std::option::Option::Some(value) => value.to_string(),
|
||||
std::option::Option::None => "none".to_owned(),
|
||||
};
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = "raw_transaction_ingest.block_identity",
|
||||
source_kind = source_kind,
|
||||
network = network.as_str(),
|
||||
slot = slot,
|
||||
provider = provider_name.as_str(),
|
||||
endpoint_id = endpoint_name,
|
||||
commitment = commitment.as_str(),
|
||||
parent_slot = block.parent_slot(),
|
||||
block_height = block_height.as_str(),
|
||||
block_identity_fingerprint = fingerprint.as_str(),
|
||||
"RAW transaction ingest observed HTTP block identity"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
fn fingerprint_filter_code(fingerprint: &[u8; 32]) -> ksp_core_lib::Result<ksp_store_lib::RawProvenanceCode> {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut value = std::string::String::with_capacity(71);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
|
||||
// version: 39
|
||||
// version: 40
|
||||
|
||||
//! External public, security, redaction and release-boundary hardening canaries through `v0.3.15-pre.004`.
|
||||
|
||||
@@ -1449,3 +1449,35 @@ fn v0_3_15_pre_013_yellowstone_block_hydration_is_bounded_concurrent_and_stop_pr
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_014_cross_provider_block_identity_diagnostic_is_safe_and_covers_both_get_block_routes() {
|
||||
let resources = include_str!("../src/runtime_resources.rs");
|
||||
for required in [
|
||||
"RAW_TRANSACTION_INGEST_BLOCK_IDENTITY_FINGERPRINT_DOMAIN",
|
||||
"fn block_identity_fingerprint(",
|
||||
"fn log_block_identity(",
|
||||
"domain = \"raw_transaction_ingest.block_identity\"",
|
||||
"\"yellowstone_block_hydration\"",
|
||||
"\"http_block_polling\"",
|
||||
"block_identity_fingerprint = fingerprint.as_str()",
|
||||
"parent_slot = block.parent_slot()",
|
||||
] {
|
||||
assert!(resources.contains(required), "missing pre.014 block identity diagnostic guard: {required}");
|
||||
}
|
||||
let start = resources.find("fn log_block_identity(");
|
||||
assert!(start.is_some(), "missing pre.014 block identity logger");
|
||||
let Some(start) = start else {
|
||||
return;
|
||||
};
|
||||
let end = resources[start..].find("fn fingerprint_filter_code(");
|
||||
assert!(end.is_some(), "missing pre.014 block identity logger end marker");
|
||||
let Some(end) = end else {
|
||||
return;
|
||||
};
|
||||
let logger = &resources[start..start + end];
|
||||
for forbidden in ["blockhash =", "previous_blockhash =", "signature =", "payload =", "transaction ="] {
|
||||
assert!(!logger.contains(forbidden), "pre.014 block identity logger exposes forbidden material: {forbidden}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
|
||||
// version: 35
|
||||
// version: 36
|
||||
|
||||
//! Release-completeness canaries through the `v0.3.15-pre.004` WebSocket capability-enforcement tranche.
|
||||
|
||||
@@ -496,3 +496,13 @@ fn v0_3_15_pre_013_yellowstone_block_backpressure_fix_adds_no_public_surface() {
|
||||
assert!(!root.contains("YellowstoneBlockHydrationContext"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_014_cross_provider_convergence_diagnostic_adds_no_public_worker_surface() {
|
||||
let hardening = include_str!("hardening.rs");
|
||||
let root = include_str!("../src/lib.rs");
|
||||
assert!(hardening.contains("v0_3_15_pre_014_cross_provider_block_identity_diagnostic_is_safe_and_covers_both_get_block_routes"));
|
||||
assert!(!root.contains("BlockIdentityFingerprint"));
|
||||
assert!(!root.contains("LogMessagesConflictDiagnostic"));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user