v0.3.10-pre.002

This commit is contained in:
2026-09-06 09:58:31 +02:00
parent 6b434750c5
commit d0da907c00
17 changed files with 1453 additions and 29 deletions

View File

@@ -0,0 +1,17 @@
# file: crates/ksp-raw-transaction-lib/Cargo.toml
# version: 1
[package]
name = "ksp-raw-transaction-lib"
version.workspace = true
edition.workspace = true
repository.workspace = true
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
ksp-store-api = { path = "../ksp-store-api" }
serde_json = { workspace = true }
sha2 = { workspace = true }
[lints]
workspace = true

View File

@@ -0,0 +1,49 @@
// file: crates/ksp-raw-transaction-lib/src/acquisition.rs
// version: 1
/// Complete source-neutral RAW transaction acquisition containing one canonical entity and one producer-owned observation.
pub struct RawTransactionAcquisition {
observation: ksp_store_api::RawTransactionObservation,
transaction: ksp_store_api::RawTransaction,
}
impl crate::RawTransactionAcquisition {
/// Returns the producer-owned acquisition observation linked to the canonical transaction.
#[must_use]
pub const fn observation(&self) -> &ksp_store_api::RawTransactionObservation {
return &self.observation;
}
/// Returns the canonical source-independent RAW transaction.
#[must_use]
pub const fn transaction(&self) -> &ksp_store_api::RawTransaction {
return &self.transaction;
}
/// Consumes this acquisition into its canonical transaction and observation.
#[must_use]
pub fn into_parts(self) -> (ksp_store_api::RawTransaction, ksp_store_api::RawTransactionObservation) {
return (self.transaction, self.observation);
}
}
impl std::fmt::Debug for crate::RawTransactionAcquisition {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("RawTransactionAcquisition(..)");
}
}
/// Assembles one canonical transaction with a producer-owned observation key and safe acquisition provenance.
#[must_use]
pub fn assemble_raw_transaction_acquisition(
transaction: ksp_store_api::RawTransaction,
observation_key: ksp_store_api::RawObservationKey,
provenance: ksp_store_api::RawAcquisitionProvenance,
) -> crate::RawTransactionAcquisition {
let observation = ksp_store_api::RawTransactionObservation::new(observation_key, transaction.reference().clone(), provenance);
return crate::RawTransactionAcquisition { observation, transaction };
}
#[cfg(test)]
#[path = "../unit_tests/acquisition.rs"]
mod tests;

View File

@@ -0,0 +1,305 @@
// file: crates/ksp-raw-transaction-lib/src/canonical.rs
// version: 1
use sha2::Digest; // rust-rules: trait-import
/// KSP-owned source-independent RAW transaction format identifier.
pub const RAW_TRANSACTION_FORMAT_ID: &str = "ksp.solana.raw_transaction";
/// Frozen KSP-owned RAW transaction format version.
pub const RAW_TRANSACTION_FORMAT_VERSION: u32 = 1;
/// Source-wire presence state preserving omitted, explicit null and concrete values independently.
#[non_exhaustive]
pub enum RawTransactionWireField<T> {
/// The source field was omitted entirely.
Omitted,
/// The source field was explicitly present as JSON/protocol null.
Null,
/// The source field carried one concrete value.
Value(T),
}
impl<T> std::fmt::Debug for crate::RawTransactionWireField<T> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return match self {
Self::Omitted => formatter.write_str("Omitted"),
Self::Null => formatter.write_str("Null"),
Self::Value(_) => formatter.write_str("Value(..)"),
};
}
}
/// Canonical Solana transaction version admitted by RAW transaction format v1.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RawTransactionVersion {
/// Legacy unversioned transaction representation.
Legacy,
/// Explicit versioned transaction number admitted by the current Solana wire contract.
Number(u8),
}
/// Complete source-neutral material required to construct one canonical RAW transaction.
pub struct RawTransactionMaterial {
block_time: std::option::Option<i64>,
meta: crate::RawTransactionWireField<serde_json::Value>,
network: ksp_store_api::RawNetworkId,
signature: ksp_store_api::RawTransactionSignature,
slot: u64,
transaction_data: std::string::String,
transaction_index: crate::RawTransactionWireField<u32>,
version: crate::RawTransactionWireField<crate::RawTransactionVersion>,
}
impl crate::RawTransactionMaterial {
/// Creates complete source-neutral material for the frozen RAW v1 binary Base64 body path.
#[must_use]
pub fn binary_base64(
network: ksp_store_api::RawNetworkId,
signature: ksp_store_api::RawTransactionSignature,
slot: u64,
block_time: std::option::Option<i64>,
transaction_data: impl std::convert::Into<std::string::String>,
meta: crate::RawTransactionWireField<serde_json::Value>,
version: crate::RawTransactionWireField<crate::RawTransactionVersion>,
transaction_index: crate::RawTransactionWireField<u32>,
) -> Self {
return Self {
block_time,
meta,
network,
signature,
slot,
transaction_data: transaction_data.into(),
transaction_index,
version,
};
}
}
impl std::fmt::Debug for crate::RawTransactionMaterial {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("RawTransactionMaterial")
.field("network", &self.network)
.field("slot", &self.slot)
.field("block_time", &self.block_time)
.field("transaction_data_len", &self.transaction_data.len())
.field("meta", &wire_field_state(&self.meta))
.field("version", &wire_field_state(&self.version))
.field("transaction_index", &wire_field_state(&self.transaction_index))
.finish_non_exhaustive();
}
}
/// Canonicalizes complete source-neutral transaction material into the frozen KSP RAW transaction v1 representation.
pub fn canonicalize_raw_transaction(material: crate::RawTransactionMaterial) -> ksp_core_lib::Result<ksp_store_api::RawTransaction> {
let block_time = convert_block_time(material.block_time);
let block_time = match block_time {
std::result::Result::Ok(block_time) => block_time,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let bytes = canonical_payload_bytes(&material);
let bytes = match bytes {
std::result::Result::Ok(bytes) => bytes,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if bytes.is_empty() || bytes.len() > ksp_store_api::MAX_RAW_PAYLOAD_BYTES {
return std::result::Result::Err(crate::canonicalization_error("payload.bytes"));
}
let hash: [u8; 32] = sha2::Sha256::digest(bytes.as_slice()).into();
let format_id = ksp_store_api::RawFormatId::new(crate::RAW_TRANSACTION_FORMAT_ID);
let format_id = match format_id {
std::result::Result::Ok(format_id) => format_id,
std::result::Result::Err(_) => return std::result::Result::Err(crate::canonicalization_error("payload.format_id")),
};
let payload = ksp_store_api::RawPayload::try_new(
format_id,
crate::RAW_TRANSACTION_FORMAT_VERSION,
bytes.into_boxed_slice(),
ksp_store_api::RawContentHash::new(hash),
);
let payload = match payload {
std::result::Result::Ok(payload) => payload,
std::result::Result::Err(_) => return std::result::Result::Err(crate::canonicalization_error("payload")),
};
let reference = ksp_store_api::RawTransactionReference::new(material.network, material.signature);
return std::result::Result::Ok(ksp_store_api::RawTransaction::new(reference, material.slot, block_time, payload));
}
fn append_canonical_json(output: &mut std::vec::Vec<u8>, value: &serde_json::Value) -> ksp_core_lib::Result<()> {
return match value {
serde_json::Value::Null => {
output.extend_from_slice(b"null");
std::result::Result::Ok(())
},
serde_json::Value::Bool(value) => {
if *value {
output.extend_from_slice(b"true");
} else {
output.extend_from_slice(b"false");
}
std::result::Result::Ok(())
},
serde_json::Value::Number(value) => {
output.extend_from_slice(value.to_string().as_bytes());
std::result::Result::Ok(())
},
serde_json::Value::String(value) => append_json_string(output, value.as_str()),
serde_json::Value::Array(values) => {
output.push(b'[');
for (index, item) in values.iter().enumerate() {
if index != 0 {
output.push(b',');
}
let item_result = append_canonical_json(output, item);
if let std::result::Result::Err(error) = item_result {
return std::result::Result::Err(error);
}
}
output.push(b']');
std::result::Result::Ok(())
},
serde_json::Value::Object(values) => {
output.push(b'{');
let mut keys = values.keys().collect::<std::vec::Vec<_>>();
keys.sort_unstable();
for (index, key) in keys.iter().enumerate() {
if index != 0 {
output.push(b',');
}
let key_result = append_json_string(output, key.as_str());
if let std::result::Result::Err(error) = key_result {
return std::result::Result::Err(error);
}
output.push(b':');
let item = values.get(key.as_str());
let item = match item {
std::option::Option::Some(item) => item,
std::option::Option::None => return std::result::Result::Err(crate::canonicalization_error("payload.meta")),
};
let item_result = append_canonical_json(output, item);
if let std::result::Result::Err(error) = item_result {
return std::result::Result::Err(error);
}
}
output.push(b'}');
std::result::Result::Ok(())
},
};
}
fn append_json_string(output: &mut std::vec::Vec<u8>, value: &str) -> ksp_core_lib::Result<()> {
let encoded = serde_json::to_vec(value);
return match encoded {
std::result::Result::Ok(encoded) => {
output.extend_from_slice(encoded.as_slice());
std::result::Result::Ok(())
},
std::result::Result::Err(_) => std::result::Result::Err(crate::canonicalization_error("payload.json")),
};
}
fn append_wire_value<T, F>(
output: &mut std::vec::Vec<u8>,
key: &str,
field: &crate::RawTransactionWireField<T>,
mut append_value: F,
) -> ksp_core_lib::Result<()>
where
F: FnMut(&mut std::vec::Vec<u8>, &T) -> ksp_core_lib::Result<()>,
{
return match field {
crate::RawTransactionWireField::Omitted => std::result::Result::Ok(()),
crate::RawTransactionWireField::Null => {
output.push(b',');
let key_result = append_json_string(output, key);
if let std::result::Result::Err(error) = key_result {
return std::result::Result::Err(error);
}
output.extend_from_slice(b":null");
std::result::Result::Ok(())
},
crate::RawTransactionWireField::Value(value) => {
output.push(b',');
let key_result = append_json_string(output, key);
if let std::result::Result::Err(error) = key_result {
return std::result::Result::Err(error);
}
output.push(b':');
append_value(output, value)
},
};
}
fn canonical_payload_bytes(material: &crate::RawTransactionMaterial) -> ksp_core_lib::Result<std::vec::Vec<u8>> {
let mut bytes = std::vec::Vec::new();
bytes.extend_from_slice(b"{\"transaction\":[");
let transaction_result = append_json_string(&mut bytes, material.transaction_data.as_str());
if let std::result::Result::Err(error) = transaction_result {
return std::result::Result::Err(error);
}
bytes.push(b',');
let encoding_result = append_json_string(&mut bytes, "base64");
if let std::result::Result::Err(error) = encoding_result {
return std::result::Result::Err(error);
}
bytes.push(b']');
let meta_result = append_wire_value(&mut bytes, "meta", &material.meta, |output, value| return append_canonical_json(output, value));
if let std::result::Result::Err(error) = meta_result {
return std::result::Result::Err(error);
}
let version_result = append_wire_value(&mut bytes, "version", &material.version, |output, value| {
return match value {
crate::RawTransactionVersion::Legacy => append_json_string(output, "legacy"),
crate::RawTransactionVersion::Number(number) => {
output.extend_from_slice(number.to_string().as_bytes());
std::result::Result::Ok(())
},
};
});
if let std::result::Result::Err(error) = version_result {
return std::result::Result::Err(error);
}
let transaction_index_result = append_wire_value(&mut bytes, "transactionIndex", &material.transaction_index, |output, value| {
output.extend_from_slice(value.to_string().as_bytes());
return std::result::Result::Ok(());
});
if let std::result::Result::Err(error) = transaction_index_result {
return std::result::Result::Err(error);
}
bytes.push(b'}');
return std::result::Result::Ok(bytes);
}
fn convert_block_time(value: std::option::Option<i64>) -> ksp_core_lib::Result<std::option::Option<ksp_store_api::RawTimestamp>> {
let seconds = match value {
std::option::Option::Some(seconds) => seconds,
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
let seconds = match u64::try_from(seconds) {
std::result::Result::Ok(seconds) => seconds,
std::result::Result::Err(_) => return std::result::Result::Err(crate::material_error("block_time")),
};
let millis = match seconds.checked_mul(1_000) {
std::option::Option::Some(millis) => millis,
std::option::Option::None => return std::result::Result::Err(crate::material_error("block_time")),
};
let timestamp = ksp_store_api::RawTimestamp::from_unix_millis(millis);
return match timestamp {
std::result::Result::Ok(timestamp) => std::result::Result::Ok(std::option::Option::Some(timestamp)),
std::result::Result::Err(_) => std::result::Result::Err(crate::material_error("block_time")),
};
}
fn wire_field_state<T>(field: &crate::RawTransactionWireField<T>) -> &'static str {
return match field {
crate::RawTransactionWireField::Omitted => "omitted",
crate::RawTransactionWireField::Null => "null",
crate::RawTransactionWireField::Value(_) => "value",
};
}
#[cfg(test)]
#[path = "../unit_tests/canonical.rs"]
mod tests;

View File

@@ -0,0 +1,27 @@
// file: crates/ksp-raw-transaction-lib/src/error.rs
// version: 1
/// Error code used when RAW v1 canonical payload construction cannot preserve the frozen format contract.
pub const ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction", "canonicalization_invalid");
/// Error code used when source-neutral RAW transaction material violates a bounded semantic invariant.
pub const ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("raw_transaction", "material_invalid");
/// Error code used when textual Solana transaction signature material cannot decode to exactly 64 canonical bytes.
pub const ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("raw_transaction", "signature_invalid");
/// Creates a safe canonicalization error without copying source payload material.
pub(crate) fn canonicalization_error(field: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID, "invalid canonical RAW transaction representation")
.with_context("field", field);
}
/// Creates a safe source-material error without copying source payload material.
pub(crate) fn material_error(field: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID, "invalid source-neutral RAW transaction material")
.with_context("field", field);
}
/// Creates a safe signature error without copying textual signature material.
pub(crate) fn signature_error() -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID, "invalid canonical Solana transaction signature");
}

View File

@@ -0,0 +1,54 @@
// file: crates/ksp-raw-transaction-lib/src/lib.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Source-neutral canonicalization of complete Solana transactions into KSP RAW transaction v1.
//!
//! This lower layer owns the frozen RAW transaction format, textual signature parsing, canonical
//! JSON construction, content hashing and assembly of one canonical transaction with one
//! producer-owned observation. It owns no Transport, Config, Job, Worker, async runtime or Store
//! backend behavior.
mod acquisition;
mod canonical;
mod error;
mod signature;
/// Complete source-neutral RAW transaction acquisition containing one canonical entity and one producer-owned observation.
pub use self::acquisition::RawTransactionAcquisition;
/// Assembles one canonical transaction with a producer-owned observation key and safe acquisition provenance.
pub use self::acquisition::assemble_raw_transaction_acquisition;
/// KSP-owned source-independent RAW transaction format identifier.
pub use self::canonical::RAW_TRANSACTION_FORMAT_ID;
/// Frozen KSP-owned RAW transaction format version.
pub use self::canonical::RAW_TRANSACTION_FORMAT_VERSION;
/// Complete source-neutral material required to construct one canonical RAW transaction.
pub use self::canonical::RawTransactionMaterial;
/// Canonical Solana transaction version admitted by RAW transaction format v1.
pub use self::canonical::RawTransactionVersion;
/// Source-wire presence state preserving omitted, explicit null and concrete values independently.
pub use self::canonical::RawTransactionWireField;
/// Canonicalizes complete source-neutral transaction material into the frozen KSP RAW transaction v1 representation.
pub use self::canonical::canonicalize_raw_transaction;
/// Error code used when RAW v1 canonical payload construction cannot preserve the frozen format contract.
pub use self::error::ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID;
/// Error code used when source-neutral RAW transaction material violates a bounded semantic invariant.
pub use self::error::ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID;
/// Error code used when textual Solana transaction signature material cannot decode to exactly 64 canonical bytes.
pub use self::error::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID;
/// Maximum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub use self::signature::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES;
/// Minimum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub use self::signature::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES;
/// Parses one bounded Base58 Solana transaction signature to exactly 64 canonical bytes.
pub use self::signature::parse_raw_transaction_signature;
/// Creates a safe canonicalization error without copying source payload material.
pub(crate) use self::error::canonicalization_error;
/// Creates a safe source-material error without copying source payload material.
pub(crate) use self::error::material_error;
/// Creates a safe signature error without copying textual signature material.
pub(crate) use self::error::signature_error;

View File

@@ -0,0 +1,67 @@
// file: crates/ksp-raw-transaction-lib/src/signature.rs
// version: 1
/// Maximum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub const MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 88;
/// Minimum UTF-8 byte length admitted for one textual Base58 Solana transaction signature.
pub const MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES: usize = 64;
/// Parses one bounded Base58 Solana transaction signature to exactly 64 canonical bytes.
pub fn parse_raw_transaction_signature(value: &str) -> ksp_core_lib::Result<ksp_store_api::RawTransactionSignature> {
let text = value.as_bytes();
if text.len() < crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES || text.len() > crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES {
return std::result::Result::Err(
crate::signature_error()
.with_context("actual_len", text.len().to_string())
.with_context("minimum_len", crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES.to_string())
.with_context("maximum_len", crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES.to_string()),
);
}
let mut decoded = [0_u8; 64];
let mut leading_zeroes = 0_usize;
for byte in text {
if *byte != b'1' {
break;
}
leading_zeroes += 1;
}
for byte in text {
let digit = match base58_digit(*byte) {
std::option::Option::Some(digit) => digit,
std::option::Option::None => return std::result::Result::Err(crate::signature_error()),
};
let mut carry = u32::from(digit);
for output in decoded.iter_mut().rev() {
let expanded = (u32::from(*output) * 58) + carry;
*output = (expanded & 0xff) as u8;
carry = expanded >> 8;
}
if carry != 0 {
return std::result::Result::Err(crate::signature_error());
}
}
let significant_len = match decoded.iter().position(|byte| return *byte != 0) {
std::option::Option::Some(index) => decoded.len() - index,
std::option::Option::None => 0,
};
if leading_zeroes + significant_len != decoded.len() {
return std::result::Result::Err(crate::signature_error());
}
return std::result::Result::Ok(ksp_store_api::RawTransactionSignature::new(decoded));
}
fn base58_digit(byte: u8) -> std::option::Option<u8> {
return match byte {
b'1'..=b'9' => std::option::Option::Some(byte - b'1'),
b'A'..=b'H' => std::option::Option::Some((byte - b'A') + 9),
b'J'..=b'N' => std::option::Option::Some((byte - b'J') + 17),
b'P'..=b'Z' => std::option::Option::Some((byte - b'P') + 22),
b'a'..=b'k' => std::option::Option::Some((byte - b'a') + 33),
b'm'..=b'z' => std::option::Option::Some((byte - b'm') + 44),
_ => std::option::Option::None,
};
}
#[cfg(test)]
#[path = "../unit_tests/signature.rs"]
mod tests;

View File

@@ -0,0 +1,52 @@
// file: crates/ksp-raw-transaction-lib/tests/dependency_boundary.rs
// version: 1
fn manifest() -> std::string::String {
return std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")).unwrap_or_default();
}
fn production_sources() -> std::string::String {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut combined = std::string::String::new();
for name in ["acquisition.rs", "canonical.rs", "error.rs", "lib.rs", "signature.rs"] {
let source = std::fs::read_to_string(root.join(name)).unwrap_or_default();
combined.push_str(source.as_str());
}
return combined;
}
#[test]
fn pre_002_manifest_uses_api_models_and_exact_common_dependencies() {
let manifest = manifest();
assert!(manifest.contains("ksp-core-lib = { path = \"../ksp-core-lib\" }"));
assert!(manifest.contains("ksp-store-api = { path = \"../ksp-store-api\" }"));
assert!(manifest.contains("serde_json = { workspace = true }"));
assert!(manifest.contains("sha2 = { workspace = true }"));
for forbidden in
["ksp-store-lib", "ksp-onchain-transport-lib", "ksp-config-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-worker-api", "tokio", "futures"]
{
assert!(!manifest.contains(forbidden));
}
return;
}
#[test]
fn pre_002_production_sources_keep_transport_runtime_job_worker_and_backend_out() {
let sources = production_sources();
for forbidden in [
"ksp_store_lib",
"ksp_onchain_transport_lib",
"ksp_config_lib",
"ksp_job_api",
"ksp_job_backfill_lib",
"ksp_worker_api",
"ksp_store_postgres_lib",
"tokio::",
"futures::",
"reqwest::",
"tonic::",
] {
assert!(!sources.contains(forbidden));
}
return;
}

View File

@@ -0,0 +1,77 @@
// file: crates/ksp-raw-transaction-lib/tests/public_api.rs
// version: 1
#[test]
fn pre_002_common_raw_contract_is_consumable_from_crate_root() {
assert_eq!(ksp_raw_transaction_lib::RAW_TRANSACTION_FORMAT_ID, "ksp.solana.raw_transaction");
assert_eq!(ksp_raw_transaction_lib::RAW_TRANSACTION_FORMAT_VERSION, 1);
assert_eq!(ksp_raw_transaction_lib::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES, 64);
assert_eq!(ksp_raw_transaction_lib::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES, 88);
let network = match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let signature = match ksp_raw_transaction_lib::parse_raw_transaction_signature("1".repeat(64).as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
network,
signature,
5,
std::option::Option::None,
"AQID",
ksp_raw_transaction_lib::RawTransactionWireField::Null,
ksp_raw_transaction_lib::RawTransactionWireField::Value(ksp_raw_transaction_lib::RawTransactionVersion::Legacy),
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
);
let transaction = ksp_raw_transaction_lib::canonicalize_raw_transaction(material);
assert!(transaction.is_ok());
return;
}
#[test]
fn pre_002_acquisition_assembly_accepts_store_api_producer_owned_metadata() {
let network = match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let signature = match ksp_raw_transaction_lib::parse_raw_transaction_signature("1".repeat(64).as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
network,
signature,
5,
std::option::Option::None,
"AQID",
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
);
let transaction = match ksp_raw_transaction_lib::canonicalize_raw_transaction(material) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let provider = match ksp_store_api::RawProvenanceCode::new("fixture") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let protocol = match ksp_store_api::RawProvenanceCode::new("fixture.protocol") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let method = match ksp_store_api::RawProvenanceCode::new("fixture.method") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let received_at = match ksp_store_api::RawTimestamp::from_unix_millis(1) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let provenance = ksp_store_api::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_api::RawAcquisitionOrigin::Import, received_at);
let acquisition = ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(transaction, ksp_store_api::RawObservationKey::new([9_u8; 32]), provenance);
assert_eq!(acquisition.transaction().reference(), acquisition.observation().transaction());
return;
}

View File

@@ -0,0 +1,37 @@
// file: crates/ksp-raw-transaction-lib/tests/release_completeness.rs
// version: 1
#[test]
fn pre_002_production_module_inventory_is_exact() {
let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let entries = match std::fs::read_dir(source_root) {
std::result::Result::Ok(entries) => entries,
std::result::Result::Err(_) => return,
};
let mut names = std::vec::Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(std::ffi::OsStr::to_str) == std::option::Option::Some("rs") {
if let std::option::Option::Some(name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
names.push(name.to_owned());
}
}
}
names.sort();
let expected = ["acquisition.rs", "canonical.rs", "error.rs", "lib.rs", "signature.rs"];
assert_eq!(names.len(), expected.len());
for (actual, expected_name) in names.iter().zip(expected.iter()) {
assert_eq!(actual.as_str(), *expected_name);
}
return;
}
#[test]
fn pre_002_common_surface_contains_no_worker_job_or_transport_dependency_or_public_module() {
let lib = std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/lib.rs")).unwrap_or_default();
assert!(!lib.contains("pub mod "));
for forbidden in ["ksp_worker", "ksp_job", "ksp_onchain_transport", "ksp_config", "ksp_store_lib", "ksp_store_postgres"] {
assert!(!lib.contains(forbidden));
}
return;
}

View File

@@ -0,0 +1,26 @@
// file: crates/ksp-raw-transaction-lib/tests/security_hardening.rs
// version: 1
#[test]
fn pre_002_signature_errors_never_echo_hostile_input() {
let hostile = format!("{}0", "A".repeat(63));
let result = ksp_raw_transaction_lib::parse_raw_transaction_signature(hostile.as_str());
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID);
assert!(!std::format!("{error:?}").contains(hostile.as_str()));
assert!(!error.to_string().contains(hostile.as_str()));
}
return;
}
#[test]
fn pre_002_public_error_codes_are_stable_and_domain_scoped() {
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID.domain(), "raw_transaction");
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_CANONICALIZATION_INVALID.code(), "canonicalization_invalid");
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID.domain(), "raw_transaction");
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_MATERIAL_INVALID.code(), "material_invalid");
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID.domain(), "raw_transaction");
assert_eq!(ksp_raw_transaction_lib::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID.code(), "signature_invalid");
return;
}

View File

@@ -0,0 +1,73 @@
// file: crates/ksp-raw-transaction-lib/unit_tests/acquisition.rs
// version: 1
fn canonical_transaction() -> std::option::Option<ksp_store_api::RawTransaction> {
let network = match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let signature = match crate::parse_raw_transaction_signature("1".repeat(64).as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let material = crate::RawTransactionMaterial::binary_base64(
network,
signature,
42,
std::option::Option::None,
"AQID",
crate::RawTransactionWireField::Omitted,
crate::RawTransactionWireField::Omitted,
crate::RawTransactionWireField::Omitted,
);
return match crate::canonicalize_raw_transaction(material) {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn provenance() -> std::option::Option<ksp_store_api::RawAcquisitionProvenance> {
let provider = match ksp_store_api::RawProvenanceCode::new("fixture") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let protocol = match ksp_store_api::RawProvenanceCode::new("fixture.protocol") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let method = match ksp_store_api::RawProvenanceCode::new("fixture.method") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let received_at = match ksp_store_api::RawTimestamp::from_unix_millis(1_700_000_000_000) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(ksp_store_api::RawAcquisitionProvenance::new(
provider,
protocol,
method,
ksp_store_api::RawAcquisitionOrigin::Import,
received_at,
));
}
#[test]
fn pre_002_acquisition_preserves_producer_key_and_exact_transaction_reference() {
let transaction = match canonical_transaction() {
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 key = ksp_store_api::RawObservationKey::new([7_u8; 32]);
let acquisition = crate::assemble_raw_transaction_acquisition(transaction, key, provenance);
assert_eq!(acquisition.observation().observation_key(), key);
assert_eq!(acquisition.observation().transaction(), acquisition.transaction().reference());
assert_eq!(std::format!("{acquisition:?}"), "RawTransactionAcquisition(..)");
let (transaction, observation) = acquisition.into_parts();
assert_eq!(observation.transaction(), transaction.reference());
return;
}

View File

@@ -0,0 +1,219 @@
// file: crates/ksp-raw-transaction-lib/unit_tests/canonical.rs
// version: 1
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;
}

View File

@@ -0,0 +1,33 @@
// file: crates/ksp-raw-transaction-lib/unit_tests/signature.rs
// version: 1
#[test]
fn pre_002_signature_parser_accepts_exact_sixty_four_zero_bytes() {
let text = "1".repeat(crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES);
let signature = crate::parse_raw_transaction_signature(text.as_str());
assert!(signature.is_ok());
if let std::result::Result::Ok(signature) = signature {
assert_eq!(signature.as_bytes(), &[0_u8; 64]);
}
return;
}
#[test]
fn pre_002_signature_parser_rejects_text_bounds_invalid_base58_and_noncanonical_lengths() {
for text in [
"1".repeat(crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES - 1),
"1".repeat(crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES + 1),
format!("{}0", "1".repeat(crate::MIN_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES - 1)),
"1".repeat(65),
"z".repeat(crate::MAX_RAW_TRANSACTION_SIGNATURE_TEXT_BYTES),
] {
let result = crate::parse_raw_transaction_signature(text.as_str());
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_SIGNATURE_INVALID);
let debug = std::format!("{error:?}");
assert!(!debug.contains(text.as_str()));
}
}
return;
}