302 lines
14 KiB
Rust
302 lines
14 KiB
Rust
// file: crates/ksp-job-backfill-lib/unit_tests/conversion.rs
|
|
// version: 2
|
|
|
|
fn signature_text() -> std::option::Option<crate::BackfillSignature> {
|
|
return match crate::BackfillSignature::new("1".repeat(64)) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn request(network: &str, signature: crate::BackfillSignature) -> std::option::Option<crate::BackfillRequest> {
|
|
let network = match ksp_store_lib::RawNetworkId::new(network) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let job_id = match ksp_job_api::JobId::new("backfill:pre006") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
let scope = match crate::BackfillScope::explicit_signatures(std::vec![signature]) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return match crate::BackfillRequest::new(
|
|
job_id,
|
|
network,
|
|
ksp_onchain_transport_lib::HttpRoleName::new("history"),
|
|
crate::BackfillCommitment::Finalized,
|
|
scope,
|
|
100,
|
|
10,
|
|
100,
|
|
4,
|
|
std::option::Option::None,
|
|
) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn candidate(network: &str, signature: crate::BackfillSignature) -> std::option::Option<crate::BackfillCandidate> {
|
|
let network = match ksp_store_lib::RawNetworkId::new(network) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return std::option::Option::None,
|
|
};
|
|
return std::option::Option::Some(crate::BackfillCandidate::new(crate::BackfillCandidateIdentity::new(network, signature), std::option::Option::Some(42)));
|
|
}
|
|
|
|
fn received_at() -> std::option::Option<ksp_store_lib::RawTimestamp> {
|
|
return match ksp_store_lib::RawTimestamp::from_unix_millis(1_700_000_001_000) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn fields<'a>(
|
|
transaction: &'a ksp_onchain_transport_lib::SolanaEncodedTransaction,
|
|
meta: &'a ksp_onchain_transport_lib::SolanaWireField<serde_json::Value>,
|
|
version: &'a ksp_onchain_transport_lib::SolanaWireField<ksp_onchain_transport_lib::SolanaTransactionVersion>,
|
|
transaction_index: &'a ksp_onchain_transport_lib::SolanaWireField<u32>,
|
|
block_time: std::option::Option<i64>,
|
|
) -> super::CanonicalTransactionFields<'a> {
|
|
return super::CanonicalTransactionFields { slot: 123, block_time, transaction, meta, version, transaction_index };
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_signature_decoding_requires_exactly_sixty_four_bytes() {
|
|
let signature = match signature_text() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let decoded = signature.to_raw_transaction_signature();
|
|
assert!(decoded.is_ok());
|
|
if let std::result::Result::Ok(decoded) = decoded {
|
|
assert_eq!(decoded.as_bytes(), &[0_u8; 64]);
|
|
}
|
|
let too_many_zeroes = crate::BackfillSignature::new("1".repeat(65));
|
|
assert!(too_many_zeroes.is_ok());
|
|
if let std::result::Result::Ok(too_many_zeroes) = too_many_zeroes {
|
|
assert!(too_many_zeroes.to_raw_transaction_signature().is_err());
|
|
}
|
|
let overflow = crate::BackfillSignature::new("z".repeat(88));
|
|
assert!(overflow.is_ok());
|
|
if let std::result::Result::Ok(overflow) = overflow {
|
|
assert!(overflow.to_raw_transaction_signature().is_err());
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_canonical_payload_golden_bytes_hash_and_provenance_are_exact() {
|
|
let signature = match signature_text() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let request = match request("devnet", signature.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let candidate = match candidate("devnet", signature) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let reference = match super::canonical_reference(&request, &candidate) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let transaction = ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary {
|
|
data: "AQID".to_owned(),
|
|
encoding: ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64,
|
|
};
|
|
let meta = ksp_onchain_transport_lib::SolanaWireField::Value(serde_json::json!({"z": 1, "a": {"y": true, "x": null}}));
|
|
let version = ksp_onchain_transport_lib::SolanaWireField::Value(ksp_onchain_transport_lib::SolanaTransactionVersion::Legacy);
|
|
let transaction_index = ksp_onchain_transport_lib::SolanaWireField::Value(7_u32);
|
|
let received_at = match received_at() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let acquisition = super::convert_available_fields(
|
|
&request,
|
|
reference,
|
|
fields(&transaction, &meta, &version, &transaction_index, std::option::Option::Some(1_700_000_000)),
|
|
"publicnode",
|
|
"devnet-primary",
|
|
received_at,
|
|
);
|
|
assert!(acquisition.is_ok());
|
|
let acquisition = match acquisition {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let expected = b"{\"transaction\":[\"AQID\",\"base64\"],\"meta\":{\"a\":{\"x\":null,\"y\":true},\"z\":1},\"version\":\"legacy\",\"transactionIndex\":7}";
|
|
assert_eq!(acquisition.transaction().payload().bytes(), expected);
|
|
assert_eq!(acquisition.transaction().payload().byte_len(), 112);
|
|
assert_eq!(acquisition.transaction().payload().format_id().as_str(), crate::RAW_TRANSACTION_FORMAT_ID);
|
|
assert_eq!(acquisition.transaction().payload().format_version(), crate::RAW_TRANSACTION_FORMAT_VERSION);
|
|
assert_eq!(
|
|
acquisition.transaction().payload().content_hash().as_bytes(),
|
|
&[
|
|
34, 7, 146, 210, 177, 93, 38, 47, 218, 36, 44, 178, 32, 119, 78, 233, 221, 239, 254, 191, 4, 220, 250, 218, 188, 248, 239, 118, 169, 177, 167, 195
|
|
]
|
|
);
|
|
assert_eq!(acquisition.transaction().slot(), 123);
|
|
assert_eq!(acquisition.transaction().block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_700_000_000_000));
|
|
assert_eq!(acquisition.transaction().reference().network().as_str(), "devnet");
|
|
assert_eq!(acquisition.transaction().reference().signature().as_bytes(), &[0_u8; 64]);
|
|
let provenance = acquisition.observation().provenance();
|
|
assert_eq!(provenance.provider().as_str(), "publicnode");
|
|
assert_eq!(provenance.protocol().as_str(), "solana.http.json_rpc");
|
|
assert_eq!(provenance.acquisition_method().as_str(), "getTransaction");
|
|
assert_eq!(provenance.endpoint_id().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("devnet-primary"));
|
|
assert_eq!(provenance.commitment().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("finalized"));
|
|
assert_eq!(provenance.capture_session_id().map(ksp_store_lib::RawProvenanceCode::as_str), std::option::Option::Some("backfill:pre006"));
|
|
assert_eq!(provenance.received_at(), received_at);
|
|
assert!(provenance.source_payload_hash().is_none());
|
|
assert!(provenance.source_payload_size_bytes().is_none());
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_wire_omission_and_null_produce_distinct_canonical_bytes() {
|
|
let transaction = ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary {
|
|
data: "AQID".to_owned(),
|
|
encoding: ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64,
|
|
};
|
|
let meta_omitted = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
let meta_null = ksp_onchain_transport_lib::SolanaWireField::Null;
|
|
let version_omitted = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
let version_null = ksp_onchain_transport_lib::SolanaWireField::Null;
|
|
let transaction_index_omitted = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
let transaction_index_null = ksp_onchain_transport_lib::SolanaWireField::Null;
|
|
let omitted = super::canonical_payload_bytes(&fields(&transaction, &meta_omitted, &version_omitted, &transaction_index_omitted, std::option::Option::None));
|
|
assert!(omitted.is_ok());
|
|
let omitted = match omitted {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(omitted, b"{\"transaction\":[\"AQID\",\"base64\"]}");
|
|
let nulls = super::canonical_payload_bytes(&fields(&transaction, &meta_null, &version_null, &transaction_index_null, std::option::Option::None));
|
|
assert!(nulls.is_ok());
|
|
let nulls = match nulls {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(nulls, b"{\"transaction\":[\"AQID\",\"base64\"],\"meta\":null,\"version\":null,\"transactionIndex\":null}");
|
|
assert_ne!(omitted, nulls);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_non_base64_transaction_shapes_are_rejected() {
|
|
let base58 = ksp_onchain_transport_lib::SolanaEncodedTransaction::Binary {
|
|
data: "1111".to_owned(),
|
|
encoding: ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base58,
|
|
};
|
|
let legacy = ksp_onchain_transport_lib::SolanaEncodedTransaction::LegacyBinary("1111".to_owned());
|
|
let json = ksp_onchain_transport_lib::SolanaEncodedTransaction::Json(serde_json::json!({"message": {}}));
|
|
let meta = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
let version = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
let transaction_index = ksp_onchain_transport_lib::SolanaWireField::Omitted;
|
|
for transaction in [&base58, &legacy, &json] {
|
|
let result = super::canonical_payload_bytes(&fields(transaction, &meta, &version, &transaction_index, std::option::Option::None));
|
|
assert!(result.is_err());
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_BACKFILL_RAW_CONVERSION_INVALID);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_negative_and_unrepresentable_block_times_are_terminal_conversion_errors() {
|
|
let negative = super::convert_block_time(std::option::Option::Some(-1));
|
|
assert!(negative.is_err());
|
|
let oversized = super::convert_block_time(std::option::Option::Some(i64::MAX));
|
|
assert!(oversized.is_err());
|
|
let absent = super::convert_block_time(std::option::Option::None);
|
|
assert!(matches!(absent, std::result::Result::Ok(std::option::Option::None)));
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_observation_key_is_deterministic_and_endpoint_specific() {
|
|
let signature = match signature_text() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let request = match request("devnet", signature.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let candidate = match candidate("devnet", signature) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let reference = match super::canonical_reference(&request, &candidate) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let first = super::observation_key(&request, &reference, "provider", "endpoint-a");
|
|
let same = super::observation_key(&request, &reference, "provider", "endpoint-a");
|
|
let other_endpoint = super::observation_key(&request, &reference, "provider", "endpoint-b");
|
|
let other_provider = super::observation_key(&request, &reference, "provider-2", "endpoint-a");
|
|
assert_eq!(first, same);
|
|
assert_ne!(first, other_endpoint);
|
|
assert_ne!(first, other_provider);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_candidate_network_mismatch_is_rejected_before_transport() {
|
|
let signature = match signature_text() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let request = match request("devnet", signature.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let candidate = match candidate("mainnet", signature) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let reference = super::canonical_reference(&request, &candidate);
|
|
assert!(reference.is_err());
|
|
if let std::result::Result::Err(error) = reference {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_BACKFILL_RAW_CONVERSION_INVALID);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_missing_outcome_contains_only_network_scoped_reference() {
|
|
let signature = match signature_text() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let request = match request("devnet", signature.clone()) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let candidate = match candidate("devnet", signature) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let reference = match super::canonical_reference(&request, &candidate) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let outcome = crate::BackfillHydrationOutcome::Missing(reference);
|
|
assert!(outcome.is_missing());
|
|
assert_eq!(outcome.reference().network().as_str(), "devnet");
|
|
assert_eq!(outcome.reference().signature().as_bytes(), &[0_u8; 64]);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_006_fix_001_raw_acquisition_uses_one_private_indirection() {
|
|
assert_eq!(std::mem::size_of::<crate::BackfillRawAcquisition>(), std::mem::size_of::<usize>(),);
|
|
return;
|
|
}
|