v0.3.4-pre.004

This commit is contained in:
2026-08-30 21:57:23 +02:00
parent dad18c16d5
commit fb8b585aa2
11 changed files with 1065 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/src/lib.rs
// version: 16
// version: 17
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -25,7 +25,9 @@
//! tables with canonical state/observation PKs and the observation-state FK.
//! `0.3.4-pre.003` completes V002 with exact physical bounds, one unfiltered
//! navigation index, external-schema compatibility and the bounded prerelease
//! checksum transition from the provisional `pre.002` schema.
//! checksum transition from the provisional `pre.002` schema. `0.3.4-pre.004`
//! adds backend-private RAW account state/observation read mapping and hostile-row
//! guards while keeping all account writes, pagination and trait implementations closed.
//!
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`. The
//! common facade consumes only this crate's narrow backend bridge and never
@@ -35,6 +37,7 @@ mod constants;
mod error;
mod health;
mod migration;
mod raw_account;
mod raw_transaction;
mod runtime;
mod schema;
@@ -64,6 +67,10 @@ pub(crate) use self::health::probe_health;
pub(crate) use self::migration::bootstrap;
/// Current embedded migration version consumed by the private health probe.
pub(crate) use self::migration::current_migration_version;
/// Private RAW account observation reader consumed by the physical backend runtime.
pub(crate) use self::raw_account::get_raw_account_observation;
/// Private RAW account state reader consumed by the physical backend runtime.
pub(crate) use self::raw_account::get_raw_account_state;
/// Private RAW transaction cursor decoder consumed by the physical RAW module.
pub(crate) use self::raw_transaction::cursor::decode_raw_transaction_cursor;
/// Private RAW transaction cursor encoder consumed by the physical RAW module.

View File

@@ -0,0 +1,403 @@
// file: crates/ksp-store-postgres-lib/src/raw_account.rs
// version: 1
const GET_ACCOUNT_OBSERVATION_SQL: &str = "SELECT observation_key, account_pubkey, account_slot::text AS account_slot_text, account_state_hash, provider, protocol, acquisition_method, origin, received_at_unix_millis, capture_session_id, commitment, endpoint_id, filter_id, observed_at_unix_millis, source_payload_hash, source_payload_size_bytes, is_startup, transaction_signature, write_version::text AS write_version_text FROM ksp_raw_account_observations WHERE observation_key = $1";
const GET_ACCOUNT_STATE_SQL: &str = "SELECT pubkey, slot::text AS slot_text, state_hash, lamports::text AS lamports_text, owner, executable, rent_epoch::text AS rent_epoch_text, data FROM ksp_raw_account_states WHERE pubkey = $1 AND slot = $2::TEXT::NUMERIC AND state_hash = $3";
struct RawAccountObservationDbRow {
account_pubkey: std::vec::Vec<u8>,
account_slot_text: std::string::String,
account_state_hash: std::vec::Vec<u8>,
acquisition_method: std::string::String,
capture_session_id: std::option::Option<std::string::String>,
commitment: std::option::Option<std::string::String>,
endpoint_id: std::option::Option<std::string::String>,
filter_id: std::option::Option<std::string::String>,
is_startup: std::option::Option<bool>,
observation_key: std::vec::Vec<u8>,
observed_at_unix_millis: std::option::Option<i64>,
origin: std::string::String,
protocol: std::string::String,
provider: std::string::String,
received_at_unix_millis: i64,
source_payload_hash: std::option::Option<std::vec::Vec<u8>>,
source_payload_size_bytes: std::option::Option<i64>,
transaction_signature: std::option::Option<std::vec::Vec<u8>>,
write_version_text: std::option::Option<std::string::String>,
}
struct RawAccountStateDbRow {
data: std::vec::Vec<u8>,
executable: bool,
lamports_text: std::string::String,
owner: std::vec::Vec<u8>,
pubkey: std::vec::Vec<u8>,
rent_epoch_text: std::string::String,
slot_text: std::string::String,
state_hash: std::vec::Vec<u8>,
}
/// Reads one complete canonical RAW account state from the physical PostgreSQL backend.
pub(crate) async fn get_raw_account_state(
pool: &deadpool_postgres::Pool,
network: &ksp_store_api::RawNetworkId,
reference: &ksp_store_api::RawAccountStateReference,
) -> std::result::Result<std::option::Option<ksp_store_api::RawAccountState>, crate::PostgresBackendError> {
let network_result = ensure_network(network, reference, "raw_account_state_network");
if let std::result::Result::Err(error) = network_result {
return std::result::Result::Err(error);
}
let client_result = pool.get().await;
let client = match client_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(crate::map_pool_error(error)),
};
let pubkey_bytes: &[u8] = reference.pubkey().as_ref();
let slot_text = reference.slot().to_string();
let state_hash = reference.state_hash();
let state_hash_bytes: &[u8] = state_hash.as_bytes();
let rows_result = client.query(GET_ACCOUNT_STATE_SQL, &[&pubkey_bytes, &slot_text, &state_hash_bytes]).await;
let rows = match rows_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ReadFailed, "raw_account_state_query"));
},
};
if rows.is_empty() {
return std::result::Result::Ok(std::option::Option::None);
}
if rows.len() != 1 {
return std::result::Result::Err(data_invalid("raw_account_state_cardinality"));
}
let row = match rows.first() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(data_invalid("raw_account_state_cardinality")),
};
let physical = match raw_account_state_db_row(row) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decoded = match decode_raw_account_state_row(network, physical) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if decoded.reference() != reference {
return std::result::Result::Err(data_invalid("raw_account_state_reference"));
}
return std::result::Result::Ok(std::option::Option::Some(decoded));
}
/// Reads one persisted RAW account observation by producer-owned idempotence key.
pub(crate) async fn get_raw_account_observation(
pool: &deadpool_postgres::Pool,
network: &ksp_store_api::RawNetworkId,
observation_key: &ksp_store_api::RawObservationKey,
) -> std::result::Result<std::option::Option<ksp_store_api::RawAccountObservation>, crate::PostgresBackendError> {
let client_result = pool.get().await;
let client = match client_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(crate::map_pool_error(error)),
};
let observation_key_bytes: &[u8] = observation_key.as_bytes();
let rows_result = client.query(GET_ACCOUNT_OBSERVATION_SQL, &[&observation_key_bytes]).await;
let rows = match rows_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ReadFailed, "raw_account_observation_query"));
},
};
if rows.is_empty() {
return std::result::Result::Ok(std::option::Option::None);
}
if rows.len() != 1 {
return std::result::Result::Err(data_invalid("raw_account_observation_cardinality"));
}
let row = match rows.first() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(data_invalid("raw_account_observation_cardinality")),
};
let physical = match raw_account_observation_db_row(row) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decoded = match decode_raw_account_observation_row(network, physical) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if decoded.observation_key() != *observation_key {
return std::result::Result::Err(data_invalid("raw_account_observation_key"));
}
return std::result::Result::Ok(std::option::Option::Some(decoded));
}
fn raw_account_observation_db_row(row: &tokio_postgres::Row) -> std::result::Result<RawAccountObservationDbRow, crate::PostgresBackendError> {
return std::result::Result::Ok(RawAccountObservationDbRow {
account_pubkey: row.try_get("account_pubkey").map_err(|_| data_invalid("raw_account_observation_decode"))?,
account_slot_text: row.try_get("account_slot_text").map_err(|_| data_invalid("raw_account_observation_decode"))?,
account_state_hash: row.try_get("account_state_hash").map_err(|_| data_invalid("raw_account_observation_decode"))?,
acquisition_method: row.try_get("acquisition_method").map_err(|_| data_invalid("raw_account_observation_decode"))?,
capture_session_id: row.try_get("capture_session_id").map_err(|_| data_invalid("raw_account_observation_decode"))?,
commitment: row.try_get("commitment").map_err(|_| data_invalid("raw_account_observation_decode"))?,
endpoint_id: row.try_get("endpoint_id").map_err(|_| data_invalid("raw_account_observation_decode"))?,
filter_id: row.try_get("filter_id").map_err(|_| data_invalid("raw_account_observation_decode"))?,
is_startup: row.try_get("is_startup").map_err(|_| data_invalid("raw_account_observation_decode"))?,
observation_key: row.try_get("observation_key").map_err(|_| data_invalid("raw_account_observation_decode"))?,
observed_at_unix_millis: row.try_get("observed_at_unix_millis").map_err(|_| data_invalid("raw_account_observation_decode"))?,
origin: row.try_get("origin").map_err(|_| data_invalid("raw_account_observation_decode"))?,
protocol: row.try_get("protocol").map_err(|_| data_invalid("raw_account_observation_decode"))?,
provider: row.try_get("provider").map_err(|_| data_invalid("raw_account_observation_decode"))?,
received_at_unix_millis: row.try_get("received_at_unix_millis").map_err(|_| data_invalid("raw_account_observation_decode"))?,
source_payload_hash: row.try_get("source_payload_hash").map_err(|_| data_invalid("raw_account_observation_decode"))?,
source_payload_size_bytes: row.try_get("source_payload_size_bytes").map_err(|_| data_invalid("raw_account_observation_decode"))?,
transaction_signature: row.try_get("transaction_signature").map_err(|_| data_invalid("raw_account_observation_decode"))?,
write_version_text: row.try_get("write_version_text").map_err(|_| data_invalid("raw_account_observation_decode"))?,
});
}
fn raw_account_state_db_row(row: &tokio_postgres::Row) -> std::result::Result<RawAccountStateDbRow, crate::PostgresBackendError> {
return std::result::Result::Ok(RawAccountStateDbRow {
data: row.try_get("data").map_err(|_| data_invalid("raw_account_state_decode"))?,
executable: row.try_get("executable").map_err(|_| data_invalid("raw_account_state_decode"))?,
lamports_text: row.try_get("lamports_text").map_err(|_| data_invalid("raw_account_state_decode"))?,
owner: row.try_get("owner").map_err(|_| data_invalid("raw_account_state_decode"))?,
pubkey: row.try_get("pubkey").map_err(|_| data_invalid("raw_account_state_decode"))?,
rent_epoch_text: row.try_get("rent_epoch_text").map_err(|_| data_invalid("raw_account_state_decode"))?,
slot_text: row.try_get("slot_text").map_err(|_| data_invalid("raw_account_state_decode"))?,
state_hash: row.try_get("state_hash").map_err(|_| data_invalid("raw_account_state_decode"))?,
});
}
fn decode_raw_account_observation_row(
network: &ksp_store_api::RawNetworkId,
row: RawAccountObservationDbRow,
) -> std::result::Result<ksp_store_api::RawAccountObservation, crate::PostgresBackendError> {
let observation_key = match fixed_bytes::<32>(row.observation_key, "raw_account_observation_key") {
std::result::Result::Ok(value) => ksp_store_api::RawObservationKey::new(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pubkey = match fixed_bytes::<32>(row.account_pubkey, "raw_account_observation_pubkey") {
std::result::Result::Ok(value) => ksp_store_api::Pubkey::new_from_array(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let slot = match decode_u64_decimal(row.account_slot_text.as_str(), "raw_account_observation_slot") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let state_hash = match fixed_bytes::<32>(row.account_state_hash, "raw_account_observation_state_hash") {
std::result::Result::Ok(value) => ksp_store_api::RawContentHash::new(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let provider = match decode_provenance_code(row.provider) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let protocol = match decode_provenance_code(row.protocol) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let acquisition_method = match decode_provenance_code(row.acquisition_method) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let origin = match decode_origin(row.origin.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let received_at = match decode_timestamp_i64(row.received_at_unix_millis, "raw_account_observation_received_at") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut provenance = ksp_store_api::RawAcquisitionProvenance::new(provider, protocol, acquisition_method, origin, received_at);
provenance = match row.capture_session_id {
std::option::Option::Some(value) => match decode_provenance_code(value) {
std::result::Result::Ok(code) => provenance.with_capture_session_id(code),
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
std::option::Option::None => provenance,
};
provenance = match row.commitment {
std::option::Option::Some(value) => match decode_provenance_code(value) {
std::result::Result::Ok(code) => provenance.with_commitment(code),
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
std::option::Option::None => provenance,
};
provenance = match row.endpoint_id {
std::option::Option::Some(value) => match decode_provenance_code(value) {
std::result::Result::Ok(code) => provenance.with_endpoint_id(code),
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
std::option::Option::None => provenance,
};
provenance = match row.filter_id {
std::option::Option::Some(value) => match decode_provenance_code(value) {
std::result::Result::Ok(code) => provenance.with_filter_id(code),
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
std::option::Option::None => provenance,
};
provenance = match row.observed_at_unix_millis {
std::option::Option::Some(value) => {
let timestamp = match decode_timestamp_i64(value, "raw_account_observation_observed_at") {
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
match provenance.try_with_observed_at(timestamp) {
std::result::Result::Ok(updated) => updated,
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_observation_time_order")),
}
},
std::option::Option::None => provenance,
};
provenance = match row.source_payload_hash {
std::option::Option::Some(value) => {
let hash = match fixed_bytes::<32>(value, "raw_account_observation_source_hash") {
std::result::Result::Ok(decoded) => ksp_store_api::RawContentHash::new(decoded),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
provenance.with_source_payload_hash(hash)
},
std::option::Option::None => provenance,
};
provenance = match row.source_payload_size_bytes {
std::option::Option::Some(value) => {
let size = match decode_u64_i64(value, "raw_account_observation_source_size") {
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
match provenance.try_with_source_payload_size_bytes(size) {
std::result::Result::Ok(updated) => updated,
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_observation_source_size")),
}
},
std::option::Option::None => provenance,
};
let reference = ksp_store_api::RawAccountStateReference::new(network.clone(), pubkey, slot, state_hash);
let mut observation = ksp_store_api::RawAccountObservation::new(observation_key, reference, provenance);
observation = match row.is_startup {
std::option::Option::Some(value) => observation.with_is_startup(value),
std::option::Option::None => observation,
};
observation = match row.transaction_signature {
std::option::Option::Some(value) => {
let signature = match fixed_bytes::<64>(value, "raw_account_observation_transaction_signature") {
std::result::Result::Ok(decoded) => ksp_store_api::RawTransactionSignature::new(decoded),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
observation.with_transaction_signature(signature)
},
std::option::Option::None => observation,
};
observation = match row.write_version_text {
std::option::Option::Some(value) => match decode_u64_decimal(value.as_str(), "raw_account_observation_write_version") {
std::result::Result::Ok(decoded) => observation.with_write_version(decoded),
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
std::option::Option::None => observation,
};
return std::result::Result::Ok(observation);
}
fn decode_raw_account_state_row(
network: &ksp_store_api::RawNetworkId,
row: RawAccountStateDbRow,
) -> std::result::Result<ksp_store_api::RawAccountState, crate::PostgresBackendError> {
let pubkey = match fixed_bytes::<32>(row.pubkey, "raw_account_state_pubkey") {
std::result::Result::Ok(value) => ksp_store_api::Pubkey::new_from_array(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let slot = match decode_u64_decimal(row.slot_text.as_str(), "raw_account_state_slot") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let state_hash = match fixed_bytes::<32>(row.state_hash, "raw_account_state_hash") {
std::result::Result::Ok(value) => ksp_store_api::RawContentHash::new(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let lamports = match decode_u64_decimal(row.lamports_text.as_str(), "raw_account_state_lamports") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner = match fixed_bytes::<32>(row.owner, "raw_account_state_owner") {
std::result::Result::Ok(value) => ksp_store_api::Pubkey::new_from_array(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rent_epoch = match decode_u64_decimal(row.rent_epoch_text.as_str(), "raw_account_state_rent_epoch") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let reference = ksp_store_api::RawAccountStateReference::new(network.clone(), pubkey, slot, state_hash);
return match ksp_store_api::RawAccountState::try_new(reference, lamports, owner, row.executable, rent_epoch, row.data.into_boxed_slice()) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid("raw_account_state_model")),
};
}
fn decode_origin(value: &str) -> std::result::Result<ksp_store_api::RawAcquisitionOrigin, crate::PostgresBackendError> {
return match value {
"backfill" => std::result::Result::Ok(ksp_store_api::RawAcquisitionOrigin::Backfill),
"import" => std::result::Result::Ok(ksp_store_api::RawAcquisitionOrigin::Import),
"live" => std::result::Result::Ok(ksp_store_api::RawAcquisitionOrigin::Live),
"repair" => std::result::Result::Ok(ksp_store_api::RawAcquisitionOrigin::Repair),
"replay" => std::result::Result::Ok(ksp_store_api::RawAcquisitionOrigin::Replay),
_ => std::result::Result::Err(data_invalid("raw_account_observation_origin")),
};
}
fn decode_provenance_code(value: std::string::String) -> std::result::Result<ksp_store_api::RawProvenanceCode, crate::PostgresBackendError> {
return match ksp_store_api::RawProvenanceCode::new(value) {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid("raw_account_provenance_code")),
};
}
fn decode_timestamp_i64(value: i64, phase: &'static str) -> std::result::Result<ksp_store_api::RawTimestamp, crate::PostgresBackendError> {
let unsigned = match u64::try_from(value) {
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid(phase)),
};
return match ksp_store_api::RawTimestamp::from_unix_millis(unsigned) {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid(phase)),
};
}
fn decode_u64_decimal(value: &str, phase: &'static str) -> std::result::Result<u64, crate::PostgresBackendError> {
return match value.parse::<u64>() {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid(phase)),
};
}
fn decode_u64_i64(value: i64, phase: &'static str) -> std::result::Result<u64, crate::PostgresBackendError> {
return match u64::try_from(value) {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid(phase)),
};
}
fn fixed_bytes<const N: usize>(value: std::vec::Vec<u8>, phase: &'static str) -> std::result::Result<[u8; N], crate::PostgresBackendError> {
return match <[u8; N]>::try_from(value.as_slice()) {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(_) => std::result::Result::Err(data_invalid(phase)),
};
}
fn ensure_network(
network: &ksp_store_api::RawNetworkId,
reference: &ksp_store_api::RawAccountStateReference,
phase: &'static str,
) -> std::result::Result<(), crate::PostgresBackendError> {
if reference.network() != network {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::WrongNetwork, phase));
}
return std::result::Result::Ok(());
}
fn data_invalid(phase: &'static str) -> crate::PostgresBackendError {
return crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::DataInvalid, phase);
}
#[cfg(test)]
#[path = "../unit_tests/raw_account.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/src/runtime.rs
// version: 10
// version: 11
const APPLICATION_NAME: &str = "ksp-store";
const MAX_CONNECTION_URI_BYTES: usize = 4_096;
@@ -332,6 +332,22 @@ impl PostgresBackend {
return crate::probe_health(&self.pool).await;
}
/// Reads one complete canonical RAW account state without exposing physical PostgreSQL row types.
pub async fn get_raw_account_state(
&self,
reference: &ksp_store_api::RawAccountStateReference,
) -> std::result::Result<std::option::Option<ksp_store_api::RawAccountState>, crate::PostgresBackendError> {
return crate::get_raw_account_state(&self.pool, &self.network, reference).await;
}
/// Reads one persisted RAW account observation by producer-owned idempotence key.
pub async fn get_raw_account_observation(
&self,
observation_key: &ksp_store_api::RawObservationKey,
) -> std::result::Result<std::option::Option<ksp_store_api::RawAccountObservation>, crate::PostgresBackendError> {
return crate::get_raw_account_observation(&self.pool, &self.network, observation_key).await;
}
/// Reads one canonical RAW transaction without exposing physical PostgreSQL row types.
pub async fn get_raw_transaction(
&self,