v0.3.3-pre.006

This commit is contained in:
2026-08-30 13:06:33 +02:00
parent 66a3926adb
commit 1352a61d70
15 changed files with 891 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
// version: 2
// version: 3
fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") {
@@ -353,3 +353,108 @@ fn pre_005_observation_origin_encoding_is_exact_and_static() {
assert_eq!(super::encode_origin(ksp_store_api::RawAcquisitionOrigin::Replay), std::result::Result::Ok("replay"));
return;
}
fn page_query(
network_id: &str,
start: std::option::Option<u64>,
end: std::option::Option<u64>,
direction: ksp_store_api::RawSortDirection,
cursor: std::option::Option<ksp_store_api::RawPageCursor>,
) -> ksp_store_api::RawTransactionQuery {
let network = match ksp_store_api::RawNetworkId::new(network_id) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid page-query network rejected: {error:?}"),
};
let range = match ksp_store_api::RawSlotRange::new(start, end) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid page-query range rejected: {error:?}"),
};
let limit = match ksp_store_api::RawPageLimit::new(2) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid page limit rejected: {error:?}"),
};
let page = match cursor {
std::option::Option::Some(value) => ksp_store_api::RawPageRequest::after(limit, value),
std::option::Option::None => ksp_store_api::RawPageRequest::first(limit),
};
return ksp_store_api::RawTransactionQuery::new(network, range, direction, page);
}
#[test]
fn pre_006_cursor_v1_round_trip_is_fixed_109_bytes_and_exact() {
let query = page_query("devnet", std::option::Option::Some(10), std::option::Option::Some(20), ksp_store_api::RawSortDirection::Ascending, None);
let signature = ksp_store_api::RawTransactionSignature::new([7_u8; 64]);
let cursor = match crate::encode_raw_transaction_cursor(&query, 15, &signature) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid cursor encoding rejected: {error:?}"),
};
assert_eq!(cursor.as_bytes().len(), 109);
assert_eq!(cursor.as_bytes().get(0..4), std::option::Option::Some(b"KSPT".as_ref()));
assert_eq!(cursor.as_bytes().get(4).copied(), std::option::Option::Some(1));
let decoded = match crate::decode_raw_transaction_cursor(&query, &cursor) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid cursor decoding rejected: {error:?}"),
};
assert_eq!(decoded.last_slot, 15);
assert_eq!(decoded.last_signature, [7_u8; 64]);
assert_eq!(std::format!("{cursor:?}"), "RawPageCursor { len: 109 }");
return;
}
#[test]
fn pre_006_cursor_v1_rejects_replay_across_network_direction_and_range() {
let query = page_query("devnet", std::option::Option::Some(10), std::option::Option::Some(20), ksp_store_api::RawSortDirection::Ascending, None);
let signature = ksp_store_api::RawTransactionSignature::new([9_u8; 64]);
let cursor = match crate::encode_raw_transaction_cursor(&query, 15, &signature) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid cursor encoding rejected: {error:?}"),
};
let other_network = page_query("mainnet-beta", Some(10), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
let other_direction = page_query("devnet", Some(10), Some(20), ksp_store_api::RawSortDirection::Descending, None);
let other_range = page_query("devnet", Some(11), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
for candidate in [&other_network, &other_direction, &other_range] {
let error = crate::decode_raw_transaction_cursor(candidate, &cursor).err();
assert_eq!(error.map(|value| return value.kind()), Some(crate::PostgresBackendErrorKind::QueryInvalid));
}
let outside_range = page_query("devnet", Some(16), Some(20), ksp_store_api::RawSortDirection::Ascending, None);
let error = crate::decode_raw_transaction_cursor(&outside_range, &cursor).err();
assert_eq!(error.map(|value| return value.phase()), Some("raw_cursor_slot_range"));
return;
}
#[test]
fn pre_006_cursor_v1_hostile_size_magic_version_and_digest_are_rejected() {
let query = page_query("devnet", None, None, ksp_store_api::RawSortDirection::Ascending, None);
for size in [1_usize, 108, 109, 110, 4_096] {
let raw = match ksp_store_api::RawPageCursor::try_new(vec![0_u8; size].into_boxed_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("bounded hostile cursor rejected by API before backend test: {error:?}"),
};
assert!(crate::decode_raw_transaction_cursor(&query, &raw).is_err());
}
let signature = ksp_store_api::RawTransactionSignature::new([3_u8; 64]);
let valid = match crate::encode_raw_transaction_cursor(&query, 42, &signature) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid cursor encoding rejected: {error:?}"),
};
for index in [0_usize, 4, 108] {
let mut bytes = valid.as_bytes().to_vec();
bytes[index] ^= 0xff;
let hostile = match ksp_store_api::RawPageCursor::try_new(bytes.into_boxed_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("fixed-size hostile cursor rejected by API before backend test: {error:?}"),
};
let error = crate::decode_raw_transaction_cursor(&query, &hostile).err();
assert_eq!(error.map(|value| return value.kind()), Some(crate::PostgresBackendErrorKind::QueryInvalid));
}
return;
}
#[test]
fn pre_006_page_limit_exposes_only_the_real_postgres_limit_plus_one_boundary() {
let maximum = crate::raw_transaction_physical_page_limit(9_223_372_036_854_775_806);
assert_eq!(maximum, Ok((9_223_372_036_854_775_806_usize, i64::MAX)));
let rejected = crate::raw_transaction_physical_page_limit(9_223_372_036_854_775_807).err();
assert_eq!(rejected.map(|value| return value.kind()), Some(crate::PostgresBackendErrorKind::PageLimitUnsupported));
return;
}