0.3.16-pre.005

This commit is contained in:
2026-09-21 19:31:35 +02:00
parent 19712aa7a6
commit 8b0fbf831e
14 changed files with 895 additions and 132 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/src/raw_transaction.rs
// version: 11
// version: 12
pub(crate) mod cursor;
@@ -712,7 +712,7 @@ pub(crate) async fn persist_raw_transaction_acquisition(
};
match comparison {
ExistingTransactionMatch::Active => (ksp_store_api::RawEntityWriteOutcome::AlreadyPresent, canonical_variant_id),
ExistingTransactionMatch::ActiveIncomingTruncatedLogs => {
ExistingTransactionMatch::ActiveCompatibleLessComplete => {
let variant_result = persist_or_reuse_native_transaction_variant(&sql_transaction, &raw_transaction, linked_at).await;
let variant_id = match variant_result {
std::result::Result::Ok(value) => value,
@@ -896,7 +896,7 @@ pub(crate) async fn transition_raw_transaction_retention(
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ExistingTransactionMatch {
Active,
ActiveIncomingTruncatedLogs,
ActiveCompatibleLessComplete,
Purged,
}
@@ -1439,87 +1439,24 @@ fn compare_existing_transaction(
std::result::Result::Ok(std::option::Option::None) => return std::result::Result::Err(data_invalid("raw_acquisition_active_shape")),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if raw_transactions_equal(&stored, incoming) {
return std::result::Result::Ok(ExistingTransactionMatch::Active);
}
if raw_transaction_incoming_truncated_log_messages_compatible(&stored, incoming) {
log_raw_transaction_compatible_truncated_log_messages(network, &stored, incoming);
return std::result::Result::Ok(ExistingTransactionMatch::ActiveIncomingTruncatedLogs);
}
log_raw_transaction_content_conflict(network, &stored, incoming);
return std::result::Result::Err(conflict("raw_acquisition_content_conflict"));
}
fn raw_transaction_incoming_truncated_log_messages_compatible(stored: &ksp_store_api::RawTransaction, incoming: &ksp_store_api::RawTransaction) -> bool {
if stored.reference() != incoming.reference()
|| stored.slot() != incoming.slot()
|| stored.block_time() != incoming.block_time()
|| stored.payload().format_id() != incoming.payload().format_id()
|| stored.payload().format_version() != incoming.payload().format_version()
{
return false;
}
let stored_payload = serde_json::from_slice::<serde_json::Value>(stored.payload().bytes());
let incoming_payload = serde_json::from_slice::<serde_json::Value>(incoming.payload().bytes());
let (stored_payload, incoming_payload) = match (stored_payload, incoming_payload) {
(std::result::Result::Ok(serde_json::Value::Object(stored)), std::result::Result::Ok(serde_json::Value::Object(incoming))) => (stored, incoming),
_ => return false,
let comparison = match ksp_store_api::compare_raw_transaction_variants(&stored, incoming) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_acquisition_variant_comparison")),
};
if stored_payload.get("transaction") != incoming_payload.get("transaction")
|| stored_payload.get("version") != incoming_payload.get("version")
|| stored_payload.get("transactionIndex") != incoming_payload.get("transactionIndex")
|| json_object_other_fields_mismatch(&stored_payload, &incoming_payload, RAW_TRANSACTION_CONTENT_CONFLICT_PAYLOAD_FIELDS.as_slice())
{
return false;
}
let (stored_meta, incoming_meta) = match (stored_payload.get("meta"), incoming_payload.get("meta")) {
(std::option::Option::Some(serde_json::Value::Object(stored)), std::option::Option::Some(serde_json::Value::Object(incoming))) => (stored, incoming),
_ => return false,
};
if raw_meta_other_than_log_messages_mismatch(stored_meta, incoming_meta) {
return false;
}
let (stored_logs, incoming_logs) = match (stored_meta.get("logMessages"), incoming_meta.get("logMessages")) {
(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 match comparison.relation() {
ksp_store_api::RawTransactionVariantRelation::Exact => std::result::Result::Ok(ExistingTransactionMatch::Active),
ksp_store_api::RawTransactionVariantRelation::CompatibleLessComplete => {
log_raw_transaction_compatible_truncated_log_messages(network, &stored, incoming);
std::result::Result::Ok(ExistingTransactionMatch::ActiveCompatibleLessComplete)
},
_ => return false,
ksp_store_api::RawTransactionVariantRelation::CompatibleMoreComplete
| ksp_store_api::RawTransactionVariantRelation::Conflict
| ksp_store_api::RawTransactionVariantRelation::Incomparable => {
log_raw_transaction_content_conflict(network, &stored, incoming);
std::result::Result::Err(conflict("raw_acquisition_content_conflict"))
},
_ => std::result::Result::Err(data_invalid("raw_acquisition_variant_relation")),
};
return raw_log_messages_incoming_truncated_compatible(stored_logs, incoming_logs);
}
fn raw_meta_other_than_log_messages_mismatch(
stored: &serde_json::Map<std::string::String, serde_json::Value>,
incoming: &serde_json::Map<std::string::String, serde_json::Value>,
) -> bool {
let stored_without_logs = stored.iter().filter(|(key, _)| return key.as_str() != "logMessages").collect::<std::collections::BTreeMap<_, _>>();
let incoming_without_logs = incoming.iter().filter(|(key, _)| return key.as_str() != "logMessages").collect::<std::collections::BTreeMap<_, _>>();
return stored_without_logs != incoming_without_logs;
}
fn raw_log_messages_incoming_truncated_compatible(stored: &[serde_json::Value], incoming: &[serde_json::Value]) -> bool {
if raw_log_messages_exact_truncation_marker_count(stored) != 0 {
return false;
}
let marker_index = match incoming.iter().position(raw_log_message_is_exact_truncation_marker) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
if raw_log_messages_exact_truncation_marker_count(incoming) != 1 || marker_index >= stored.len() {
return false;
}
if incoming[..marker_index] != stored[..marker_index] {
return false;
}
return true;
}
fn raw_log_messages_exact_truncation_marker_count(values: &[serde_json::Value]) -> usize {
return values.iter().filter(|value| return raw_log_message_is_exact_truncation_marker(value)).count();
}
fn raw_log_message_is_exact_truncation_marker(value: &serde_json::Value) -> bool {
return matches!(value, serde_json::Value::String(line) if line == "Log truncated");
}
fn raw_transaction_content_conflict_diagnostic(
@@ -1863,16 +1800,6 @@ async fn log_raw_transaction_content_conflict_provenance(
return;
}
fn raw_transactions_equal(left: &ksp_store_api::RawTransaction, right: &ksp_store_api::RawTransaction) -> bool {
return left.reference() == right.reference()
&& left.slot() == right.slot()
&& left.block_time() == right.block_time()
&& left.payload().format_id() == right.payload().format_id()
&& left.payload().format_version() == right.payload().format_version()
&& left.payload().content_hash() == right.payload().content_hash()
&& left.payload().bytes() == right.payload().bytes();
}
async fn rehydrate_transaction(
sql_transaction: &deadpool_postgres::Transaction<'_>,
raw_transaction: &ksp_store_api::RawTransaction,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
// version: 27
// version: 28
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -594,7 +594,7 @@ fn v0_3_15_pre_012_fix_004_content_conflict_diagnostic_is_field_only_bounded_and
let Some(start) = start else {
return;
};
let end = source[start..].find("fn raw_transactions_equal(");
let end = source[start..].find("async fn rehydrate_transaction(");
assert!(end.is_some(), "missing RAW conflict logger end marker");
let Some(end) = end else {
return;
@@ -645,33 +645,42 @@ fn v0_3_15_pre_014_content_conflict_diagnostic_exposes_only_block_slot_and_bound
}
#[test]
fn v0_3_15_pre_014_fix_001_truncated_log_compatibility_is_narrow_and_keeps_canonical_content() {
fn v0_3_16_pre_005_postgres_uses_shared_fail_closed_variant_comparator_without_promotion() {
let source = include_str!("../src/raw_transaction.rs");
for required in [
"ActiveIncomingTruncatedLogs",
"ksp_store_api::compare_raw_transaction_variants",
"RawTransactionVariantRelation::Exact",
"RawTransactionVariantRelation::CompatibleLessComplete",
"RawTransactionVariantRelation::CompatibleMoreComplete",
"RawTransactionVariantRelation::Conflict",
"RawTransactionVariantRelation::Incomparable",
"ExistingTransactionMatch::ActiveCompatibleLessComplete",
"raw_acquisition_content_conflict",
] {
assert!(source.contains(required), "missing shared comparator integration guard: {required}");
}
for forbidden in [
"raw_transaction_incoming_truncated_log_messages_compatible",
"raw_meta_other_than_log_messages_mismatch",
"raw_log_messages_incoming_truncated_compatible",
"raw_log_message_is_exact_truncation_marker",
"marker_index >= stored.len()",
"incoming[..marker_index] != stored[..marker_index]",
"PostgreSQL Store accepted compatible truncated RAW transaction logs without replacing canonical content",
] {
assert!(source.contains(required), "missing narrow truncated-log compatibility guard: {required}");
assert!(!source.contains(forbidden), "backend-private convergence heuristic survived pre.005: {forbidden}");
}
let start = source.find("fn compare_existing_transaction(");
assert!(start.is_some(), "missing fix.001 existing-transaction comparison path");
assert!(start.is_some(), "missing pre.005 existing-transaction comparison path");
let Some(start) = start else {
return;
};
let end = source[start..].find("fn raw_transaction_incoming_truncated_log_messages_compatible(");
assert!(end.is_some(), "missing fix.001 compatibility helper boundary");
let end = source[start..].find("fn raw_transaction_content_conflict_diagnostic(");
assert!(end.is_some(), "missing pre.005 comparison boundary");
let Some(end) = end else {
return;
};
let comparison = &source[start..start + end];
assert!(comparison.contains("ExistingTransactionMatch::ActiveIncomingTruncatedLogs"));
assert!(comparison.contains("CompatibleLessComplete"));
assert!(comparison.contains("CompatibleMoreComplete"));
assert!(!comparison.contains("UPDATE ksp_raw_transactions SET payload"));
assert!(!comparison.contains("DELETE FROM ksp_raw_transactions"));
assert!(!comparison.contains("canonical_revision"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/tests/v003_variant_persistence.rs
// version: 1
// version: 2
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -59,9 +59,11 @@ fn v0_3_16_pre_004_legacy_observation_without_mapping_is_not_fabricated() {
}
#[test]
fn v0_3_16_pre_004_keeps_comparator_and_durable_conflict_scope_deferred() {
fn v0_3_16_pre_005_shared_comparator_is_active_but_durable_conflict_and_promotion_stay_deferred() {
let source = include_str!("../src/raw_transaction.rs");
assert!(!source.contains("RawTransactionVariantRelation::"));
assert!(source.contains("ksp_store_api::compare_raw_transaction_variants"));
assert!(source.contains("ksp_store_api::RawTransactionVariantRelation::CompatibleLessComplete"));
assert!(source.contains("ksp_store_api::RawTransactionVariantRelation::CompatibleMoreComplete"));
assert!(!source.contains("INSERT INTO ksp_raw_transaction_conflicts"));
assert!(!source.contains("UPDATE ksp_raw_transaction_canonical_selectors SET canonical_variant_id"));
return;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
// version: 11
// version: 12
fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") {
@@ -458,7 +458,7 @@ fn v0_3_15_pre_014_log_messages_conflict_diagnostic_distinguishes_prefix_truncat
}
#[test]
fn v0_3_15_pre_014_fix_001_incoming_truncated_logs_are_compatible_only_when_missing_middle_is_proven() {
fn v0_3_16_pre_005_shared_comparator_accepts_only_proved_incoming_truncation_in_postgres_path() {
let stored = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program log: detail-a","Program log: detail-b","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
@@ -466,10 +466,15 @@ fn v0_3_15_pre_014_fix_001_incoming_truncated_logs_are_compatible_only_when_miss
);
let incoming = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &incoming));
let relation = ksp_store_api::compare_raw_transaction_variants(&stored, &incoming);
let relation = match relation {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(relation.relation(), ksp_store_api::RawTransactionVariantRelation::CompatibleLessComplete);
let mut stored_row = transaction_row("full");
stored_row.signature = stored.reference().signature().as_bytes().to_vec();
stored_row.slot_text = stored.slot().to_string();
@@ -479,12 +484,12 @@ fn v0_3_15_pre_014_fix_001_incoming_truncated_logs_are_compatible_only_when_miss
stored_row.content_hash = stored.payload().content_hash().as_bytes().to_vec();
stored_row.payload = std::option::Option::Some(stored.payload().bytes().to_vec());
let comparison = super::compare_existing_transaction(&network(), stored_row, &incoming);
assert!(matches!(comparison, std::result::Result::Ok(super::ExistingTransactionMatch::ActiveIncomingTruncatedLogs)));
assert!(matches!(comparison, std::result::Result::Ok(super::ExistingTransactionMatch::ActiveCompatibleLessComplete)));
return;
}
#[test]
fn v0_3_15_pre_014_fix_001_truncated_log_compatibility_rejects_unproven_or_reverse_changes() {
fn v0_3_16_pre_005_shared_comparator_rejects_unproved_shapes_and_defers_more_complete_promotion() {
let stored = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program log: detail-a","Program log: detail-b","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
@@ -492,34 +497,52 @@ fn v0_3_15_pre_014_fix_001_truncated_log_compatibility_rejects_unproven_or_rever
);
let bad_prefix = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: changed","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: changed","Log truncated"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &bad_prefix));
let post_marker_material = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Other111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &post_marker_material));
let other_meta = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5001,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5001,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &other_meta));
let non_exact_marker = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated by provider"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &non_exact_marker));
for candidate in [&bad_prefix, &post_marker_material, &other_meta, &non_exact_marker] {
let relation = match ksp_store_api::compare_raw_transaction_variants(&stored, candidate) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_ne!(relation.relation(), ksp_store_api::RawTransactionVariantRelation::CompatibleLessComplete);
assert_ne!(relation.relation(), ksp_store_api::RawTransactionVariantRelation::CompatibleMoreComplete);
}
let stored_truncated = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored_truncated, &stored));
let more = match ksp_store_api::compare_raw_transaction_variants(&stored_truncated, &stored) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(more.relation(), ksp_store_api::RawTransactionVariantRelation::CompatibleMoreComplete);
let mut stored_row = transaction_row("full");
stored_row.signature = stored_truncated.reference().signature().as_bytes().to_vec();
stored_row.slot_text = stored_truncated.slot().to_string();
stored_row.block_time_unix_millis = stored_truncated.block_time().and_then(|value| return i64::try_from(value.unix_millis()).ok());
stored_row.format_id = stored_truncated.payload().format_id().as_str().to_owned();
stored_row.format_version = i64::from(stored_truncated.payload().format_version());
stored_row.content_hash = stored_truncated.payload().content_hash().as_bytes().to_vec();
stored_row.payload = std::option::Option::Some(stored_truncated.payload().bytes().to_vec());
let comparison = super::compare_existing_transaction(&network(), stored_row, &stored);
assert!(matches!(comparison, std::result::Result::Err(error) if error.phase() == "raw_acquisition_content_conflict"));
return;
}