248 lines
10 KiB
Rust
248 lines
10 KiB
Rust
// file: crates/ksp-raw-transaction-lib/unit_tests/canonical.rs
|
|
// version: 2
|
|
|
|
fn network() -> std::option::Option<ksp_store_api::RawNetworkId> {
|
|
return match ksp_store_api::RawNetworkId::new("devnet") {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn signature() -> std::option::Option<ksp_store_api::RawTransactionSignature> {
|
|
let result = crate::parse_raw_transaction_signature("1".repeat(64).as_str());
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn material(
|
|
block_time: std::option::Option<i64>,
|
|
transaction_data: std::string::String,
|
|
meta: crate::RawTransactionWireField<serde_json::Value>,
|
|
version: crate::RawTransactionWireField<crate::RawTransactionVersion>,
|
|
transaction_index: crate::RawTransactionWireField<u32>,
|
|
) -> std::option::Option<crate::RawTransactionMaterial> {
|
|
let network = match network() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
let signature = match signature() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::option::Option::None,
|
|
};
|
|
return std::option::Option::Some(crate::RawTransactionMaterial::binary_base64(
|
|
network,
|
|
signature,
|
|
123,
|
|
block_time,
|
|
transaction_data,
|
|
meta,
|
|
version,
|
|
transaction_index,
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_canonical_raw_v1_golden_bytes_and_hash_are_exact() {
|
|
let material = match material(
|
|
std::option::Option::Some(1_700_000_000),
|
|
"AQID".to_owned(),
|
|
crate::RawTransactionWireField::Value(serde_json::json!({"z": 1, "a": {"y": true, "x": null}})),
|
|
crate::RawTransactionWireField::Value(crate::RawTransactionVersion::Legacy),
|
|
crate::RawTransactionWireField::Value(7_u32),
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let transaction = crate::canonicalize_raw_transaction(material);
|
|
assert!(transaction.is_ok());
|
|
let transaction = match transaction {
|
|
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!(transaction.payload().bytes(), expected);
|
|
assert_eq!(transaction.payload().byte_len(), 112);
|
|
assert_eq!(transaction.payload().format_id().as_str(), crate::RAW_TRANSACTION_FORMAT_ID);
|
|
assert_eq!(transaction.payload().format_version(), crate::RAW_TRANSACTION_FORMAT_VERSION);
|
|
assert_eq!(
|
|
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!(transaction.slot(), 123);
|
|
assert_eq!(transaction.block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_700_000_000_000));
|
|
assert_eq!(transaction.reference().network().as_str(), "devnet");
|
|
assert_eq!(transaction.reference().signature().as_bytes(), &[0_u8; 64]);
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_wire_omitted_null_and_value_states_remain_distinct() {
|
|
let omitted = match material(
|
|
std::option::Option::None,
|
|
"AQID".to_owned(),
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let omitted = super::canonical_payload_bytes(&omitted);
|
|
assert!(omitted.is_ok());
|
|
if let std::result::Result::Ok(omitted) = omitted {
|
|
assert_eq!(omitted, b"{\"transaction\":[\"AQID\",\"base64\"]}");
|
|
}
|
|
let nulls = match material(
|
|
std::option::Option::None,
|
|
"AQID".to_owned(),
|
|
crate::RawTransactionWireField::Null,
|
|
crate::RawTransactionWireField::Null,
|
|
crate::RawTransactionWireField::Null,
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let nulls = super::canonical_payload_bytes(&nulls);
|
|
assert!(nulls.is_ok());
|
|
if let std::result::Result::Ok(nulls) = nulls {
|
|
assert_eq!(nulls, b"{\"transaction\":[\"AQID\",\"base64\"],\"meta\":null,\"version\":null,\"transactionIndex\":null}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_canonical_json_sorts_objects_preserves_arrays_and_escapes_strings() {
|
|
let meta = serde_json::json!({
|
|
"z": [3, 2, 1],
|
|
"quote": "a\"b\\c\n",
|
|
"a": {"n": -12.5, "b": false, "a": true}
|
|
});
|
|
let material = match material(
|
|
std::option::Option::None,
|
|
"A\"B".to_owned(),
|
|
crate::RawTransactionWireField::Value(meta),
|
|
crate::RawTransactionWireField::Value(crate::RawTransactionVersion::Number(0)),
|
|
crate::RawTransactionWireField::Value(u32::MAX),
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let bytes = super::canonical_payload_bytes(&material);
|
|
assert!(bytes.is_ok());
|
|
if let std::result::Result::Ok(bytes) = bytes {
|
|
let expected = b"{\"transaction\":[\"A\\\"B\",\"base64\"],\"meta\":{\"a\":{\"a\":true,\"b\":false,\"n\":-12.5},\"quote\":\"a\\\"b\\\\c\\n\",\"z\":[3,2,1]},\"version\":0,\"transactionIndex\":4294967295}";
|
|
assert_eq!(bytes, expected);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_block_time_rejects_negative_overflow_and_store_maximum_overrun() {
|
|
for block_time in [-1_i64, i64::MAX, 253_402_300_800_i64] {
|
|
let material = match material(
|
|
std::option::Option::Some(block_time),
|
|
"AQID".to_owned(),
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::canonicalize_raw_transaction(material);
|
|
assert!(result.is_err());
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID);
|
|
}
|
|
}
|
|
let maximum = match material(
|
|
std::option::Option::Some(253_402_300_799_i64),
|
|
"AQID".to_owned(),
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let maximum = crate::canonicalize_raw_transaction(maximum);
|
|
assert!(maximum.is_ok());
|
|
if let std::result::Result::Ok(maximum) = maximum {
|
|
assert_eq!(maximum.block_time().map(|value| return value.unix_millis()), std::option::Option::Some(253_402_300_799_000));
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_payload_bound_is_enforced_by_common_before_store_construction() {
|
|
let material = match material(
|
|
std::option::Option::None,
|
|
"A".repeat(ksp_store_api::MAX_RAW_PAYLOAD_BYTES),
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
crate::RawTransactionWireField::Omitted,
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let result = crate::canonicalize_raw_transaction(material);
|
|
assert!(result.is_err());
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_002_material_and_wire_debug_do_not_render_transaction_or_meta_material() {
|
|
let secret_transaction = "SECRET_TRANSACTION_BODY";
|
|
let secret_meta = "SECRET_META_VALUE";
|
|
let material = match material(
|
|
std::option::Option::None,
|
|
secret_transaction.to_owned(),
|
|
crate::RawTransactionWireField::Value(serde_json::json!({"secret": secret_meta})),
|
|
crate::RawTransactionWireField::Value(crate::RawTransactionVersion::Legacy),
|
|
crate::RawTransactionWireField::Value(1_u32),
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return,
|
|
};
|
|
let debug = std::format!("{material:?}");
|
|
assert!(!debug.contains(secret_transaction));
|
|
assert!(!debug.contains(secret_meta));
|
|
assert_eq!(std::format!("{:?}", crate::RawTransactionWireField::Value(secret_meta)), "Value(..)");
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_004_embedded_signature_material_preserves_block_fields_and_canonicalizes() {
|
|
let network = match ksp_store_api::RawNetworkId::new("devnet") {
|
|
std::result::Result::Ok(network) => network,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let material = crate::RawTransactionMaterial::binary_base64_with_embedded_signature(
|
|
network,
|
|
430_000_123,
|
|
std::option::Option::Some(1_787_072_400),
|
|
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
crate::RawTransactionWireField::Value(serde_json::json!({"err":null,"fee":5000})),
|
|
crate::RawTransactionWireField::Value(crate::RawTransactionVersion::Legacy),
|
|
crate::RawTransactionWireField::Value(0_u32),
|
|
);
|
|
assert!(material.is_ok());
|
|
let transaction = match material.and_then(crate::canonicalize_raw_transaction) {
|
|
std::result::Result::Ok(transaction) => transaction,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(transaction.reference().signature().as_bytes(), &[0_u8; 64]);
|
|
assert_eq!(transaction.slot(), 430_000_123);
|
|
assert_eq!(transaction.block_time().map(|value| return value.unix_millis()), std::option::Option::Some(1_787_072_400_000));
|
|
assert!(transaction.payload().bytes().starts_with(b"{\"transaction\":[\"AQAAAA"));
|
|
assert!(transaction.payload().bytes().ends_with(b"\"version\":\"legacy\",\"transactionIndex\":0}"));
|
|
return;
|
|
}
|