261 lines
13 KiB
Rust
261 lines
13 KiB
Rust
// file: crates/ksp-store-api/unit_tests/model/raw_transaction.rs
|
|
// version: 5
|
|
|
|
fn network() -> std::option::Option<crate::RawNetworkId> {
|
|
return match crate::RawNetworkId::new("mainnet".to_owned()) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn payload() -> std::option::Option<crate::RawPayload> {
|
|
let format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return match crate::RawPayload::try_new(
|
|
format,
|
|
1,
|
|
b"canonical transaction including logs".to_vec().into_boxed_slice(),
|
|
crate::RawContentHash::new([5_u8; 32]),
|
|
) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn provenance() -> std::option::Option<crate::RawAcquisitionProvenance> {
|
|
let provider = match crate::RawProvenanceCode::new("publicnode".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let protocol = match crate::RawProvenanceCode::new("yellowstone_grpc".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let method = match crate::RawProvenanceCode::new("transactions".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let received_at = match crate::RawTimestamp::from_unix_millis(1_000) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return std::option::Option::Some(crate::RawAcquisitionProvenance::new(provider, protocol, method, crate::RawAcquisitionOrigin::Backfill, received_at));
|
|
}
|
|
|
|
#[test]
|
|
fn raw_transaction_identity_is_network_plus_signature_and_payload_remains_whole() {
|
|
let network = match network() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let payload = match payload() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signature = crate::RawTransactionSignature::new([9_u8; 64]);
|
|
let reference = crate::RawTransactionReference::new(network, signature);
|
|
let transaction = crate::RawTransaction::new(reference, 42, std::option::Option::None, payload);
|
|
assert_eq!(transaction.reference().network().as_str(), "mainnet");
|
|
assert_eq!(transaction.reference().signature(), signature);
|
|
assert_eq!(transaction.slot(), 42);
|
|
assert!(transaction.block_time().is_none());
|
|
assert_eq!(transaction.payload().bytes(), b"canonical transaction including logs");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_observation_is_separate_from_canonical_raw_payload() {
|
|
let network = match network() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let provenance = match provenance() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let signature = crate::RawTransactionSignature::new([11_u8; 64]);
|
|
let reference = crate::RawTransactionReference::new(network, signature);
|
|
let observation = crate::RawTransactionObservation::new(crate::RawObservationKey::new([12_u8; 32]), reference, provenance);
|
|
assert_eq!(observation.transaction().signature(), signature);
|
|
assert_eq!(observation.observation_key().as_bytes(), &[12_u8; 32]);
|
|
assert_eq!(observation.provenance().provider().as_str(), "publicnode");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_variant_identity_is_non_zero_and_keeps_transaction_scope() {
|
|
assert!(crate::RawTransactionVariantId::try_new(0).is_err());
|
|
let variant_id = match crate::RawTransactionVariantId::try_new(7) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let network = match network() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let transaction = crate::RawTransactionReference::new(network, crate::RawTransactionSignature::new([13_u8; 64]));
|
|
let variant = crate::RawTransactionVariantReference::new(transaction, variant_id);
|
|
assert_eq!(variant.variant_id().get(), 7);
|
|
assert_eq!(variant.transaction().network().as_str(), "mainnet");
|
|
assert_eq!(crate::RawTransactionVariantOrigin::Native.code(), "native");
|
|
assert_eq!(crate::RawTransactionVariantOrigin::Synthetic.code(), "synthetic");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_variant_relation_reason_pairs_are_validated() {
|
|
let exact = crate::RawTransactionVariantComparison::try_new(
|
|
crate::RawTransactionVariantRelation::Exact,
|
|
crate::RawTransactionVariantRelationReason::ExactCanonicalContent,
|
|
);
|
|
assert!(exact.is_ok());
|
|
let invalid = crate::RawTransactionVariantComparison::try_new(
|
|
crate::RawTransactionVariantRelation::Exact,
|
|
crate::RawTransactionVariantRelationReason::IncomingLogMessagesTruncated,
|
|
);
|
|
assert!(invalid.is_err());
|
|
let conflict = crate::RawTransactionVariantComparison::try_new(
|
|
crate::RawTransactionVariantRelation::Conflict,
|
|
crate::RawTransactionVariantRelationReason::ContentHashCollision,
|
|
);
|
|
assert!(conflict.is_ok());
|
|
let incomparable = crate::RawTransactionVariantComparison::try_new(
|
|
crate::RawTransactionVariantRelation::Incomparable,
|
|
crate::RawTransactionVariantRelationReason::PayloadFormatMismatch,
|
|
);
|
|
assert!(incomparable.is_ok());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_variant_codes_and_outcomes_are_stable_backend_neutral_contracts() {
|
|
assert_eq!(crate::RawTransactionVariantRelation::CompatibleLessComplete.code(), "compatible_less_complete");
|
|
assert_eq!(crate::RawTransactionVariantRelationReason::IncomingLogMessagesTruncated.code(), "incoming_log_messages_truncated");
|
|
assert_eq!(crate::RawTransactionVariantRelationReason::ContentHashCollision.code(), "content_hash_collision");
|
|
assert_eq!(crate::RawTransactionConflictStatus::Open.code(), "open");
|
|
assert_eq!(crate::RawTransactionConflictStatus::Resolved.code(), "resolved");
|
|
assert_ne!(crate::RawTransactionVariantWriteOutcome::InsertedCanonical, crate::RawTransactionVariantWriteOutcome::ObservedExact);
|
|
assert_ne!(crate::RawTransactionVariantWriteOutcome::QuarantinedConflict, crate::RawTransactionVariantWriteOutcome::PromotedCompatibleMoreComplete);
|
|
return;
|
|
}
|
|
|
|
fn comparison_transaction(payload_bytes: &[u8], hash_byte: u8) -> std::option::Option<crate::RawTransaction> {
|
|
let network = match network() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let payload = match crate::RawPayload::try_new(format, 1, payload_bytes.to_vec().into_boxed_slice(), crate::RawContentHash::new([hash_byte; 32])) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let reference = crate::RawTransactionReference::new(network, crate::RawTransactionSignature::new([21_u8; 64]));
|
|
return std::option::Option::Some(crate::RawTransaction::new(reference, 88, std::option::Option::None, payload));
|
|
}
|
|
|
|
fn comparison_relation(
|
|
canonical_payload: &[u8],
|
|
canonical_hash: u8,
|
|
incoming_payload: &[u8],
|
|
incoming_hash: u8,
|
|
) -> std::option::Option<crate::RawTransactionVariantComparison> {
|
|
let canonical = match comparison_transaction(canonical_payload, canonical_hash) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let incoming = match comparison_transaction(incoming_payload, incoming_hash) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
return crate::compare_raw_transaction_variants(&canonical, &incoming).ok();
|
|
}
|
|
|
|
#[test]
|
|
fn v0_3_16_pre_005_comparator_proves_bidirectional_log_messages_truncation_only() {
|
|
let full = br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#;
|
|
let truncated = br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated"],"rewards":[]},"version":0,"transactionIndex":7}"#;
|
|
let less = match comparison_relation(full, 31, truncated, 32) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(less.relation(), crate::RawTransactionVariantRelation::CompatibleLessComplete);
|
|
assert_eq!(less.reason(), crate::RawTransactionVariantRelationReason::IncomingLogMessagesTruncated);
|
|
let more = match comparison_relation(truncated, 32, full, 31) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(more.relation(), crate::RawTransactionVariantRelation::CompatibleMoreComplete);
|
|
assert_eq!(more.reason(), crate::RawTransactionVariantRelationReason::CanonicalLogMessagesTruncated);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn v0_3_16_pre_005_comparator_rejects_unproved_log_shortening_and_marker_shapes() {
|
|
let full = br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["a","b","c"],"rewards":[]},"version":0}"#;
|
|
let shorter = br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["a","b"],"rewards":[]},"version":0}"#;
|
|
let marker_not_last =
|
|
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["a","Log truncated","c"],"rewards":[]},"version":0}"#;
|
|
let marker_not_exact =
|
|
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["a","b","Log truncated by provider"],"rewards":[]},"version":0}"#;
|
|
let bad_prefix =
|
|
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["a","changed","Log truncated"],"rewards":[]},"version":0}"#;
|
|
let candidates: [&[u8]; 4] = [shorter, marker_not_last, marker_not_exact, bad_prefix];
|
|
for (index, candidate) in candidates.iter().enumerate() {
|
|
let hash_offset = match u8::try_from(index) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let comparison = match comparison_relation(full, 40, candidate, 41 + hash_offset) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(comparison.relation(), crate::RawTransactionVariantRelation::Conflict);
|
|
assert_eq!(comparison.reason(), crate::RawTransactionVariantRelationReason::CanonicalPayloadConflict);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn v0_3_16_pre_005_comparator_is_fail_closed_for_optional_and_other_payload_differences() {
|
|
let canonical = br#"{"transaction":["AQID","base64"],"meta":{"fee":5000,"logMessages":["a"],"returnData":null},"version":0}"#;
|
|
let optional_value =
|
|
br#"{"transaction":["AQID","base64"],"meta":{"fee":5000,"logMessages":["a"],"returnData":{"data":["","base64"],"programId":"Vote111"}},"version":0}"#;
|
|
let concrete_conflict = br#"{"transaction":["AQID","base64"],"meta":{"fee":5001,"logMessages":["a"],"returnData":null},"version":0}"#;
|
|
let optional = match comparison_relation(canonical, 50, optional_value, 51) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(optional.relation(), crate::RawTransactionVariantRelation::Incomparable);
|
|
assert_eq!(optional.reason(), crate::RawTransactionVariantRelationReason::UnsupportedCanonicalDifference);
|
|
let conflict = match comparison_relation(canonical, 50, concrete_conflict, 52) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(conflict.relation(), crate::RawTransactionVariantRelation::Conflict);
|
|
assert_eq!(conflict.reason(), crate::RawTransactionVariantRelationReason::CanonicalPayloadConflict);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn v0_3_16_pre_005_comparator_uses_exact_bytes_and_detects_hash_collision_without_hash_equality_shortcut() {
|
|
let canonical = br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["a"]}}"#;
|
|
let same_bytes = match comparison_relation(canonical, 60, canonical, 61) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(same_bytes.relation(), crate::RawTransactionVariantRelation::Exact);
|
|
let different = br#"{"transaction":["AQID","base64"],"meta":{"logMessages":["b"]}}"#;
|
|
let collision = match comparison_relation(canonical, 62, different, 62) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
assert_eq!(collision.relation(), crate::RawTransactionVariantRelation::Conflict);
|
|
assert_eq!(collision.reason(), crate::RawTransactionVariantRelationReason::ContentHashCollision);
|
|
return;
|
|
}
|