v0.3.8-pre.005
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 451
|
||||
# version: 452
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.8-pre.4.fix.1"
|
||||
version = "0.3.8-pre.5"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/src/lib.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -12,7 +12,8 @@
|
||||
//! `RawTransaction` capabilities. `0.3.4-pre.008` adds the four `RawAccount*` capabilities
|
||||
//! on both the physical backend and this common facade, completing the canonical RAW runtime inventory at
|
||||
//! ten capabilities without exposing physical types. The `0.3.8` inspection
|
||||
//! contracts are reexported here before backend/facade dispatch is opened.
|
||||
//! contracts are reexported here. `0.3.8-pre.004` dispatches transaction inspection
|
||||
//! and `0.3.8-pre.005` completes inspection dispatch with RAW account states.
|
||||
//!
|
||||
//! The default `postgres` feature compiles the official PostgreSQL backend as
|
||||
//! an optional implementation dependency. No backend implementation type is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/src/store.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
/// Opaque common Store runtime facade.
|
||||
///
|
||||
@@ -228,6 +228,36 @@ impl ksp_store_api::RawAccountStateRead for Store {
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawAccountStateInspectionRead for Store {
|
||||
fn inspect_raw_account_states<'a>(
|
||||
&'a self,
|
||||
query: &'a ksp_store_api::RawAccountStateInspectionQuery,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawAccountStateSummary>>> {
|
||||
let network_check = validate_operation_network(&self.network, query.network(), self.backend_kind);
|
||||
if let std::result::Result::Err(error) = network_check {
|
||||
return std::boxed::Box::pin(async move {
|
||||
return std::result::Result::Err(error);
|
||||
});
|
||||
}
|
||||
return std::boxed::Box::pin(async move {
|
||||
#[cfg(feature = "postgres")]
|
||||
{
|
||||
return match &self.runtime {
|
||||
StoreRuntime::Postgres(backend) => {
|
||||
let result = backend.inspect_raw_account_states(query).await;
|
||||
result.map_err(|error| return map_postgres_error(error, self.backend_kind, self.network.as_str()))
|
||||
},
|
||||
};
|
||||
}
|
||||
#[cfg(not(feature = "postgres"))]
|
||||
{
|
||||
let _ = query;
|
||||
return std::result::Result::Err(unavailable_runtime_error(self.backend_kind));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawAccountStateWrite for Store {
|
||||
fn persist_raw_account_acquisition<'a>(
|
||||
&'a self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/dependency_boundary.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -60,11 +60,12 @@ fn pre_005_facade_exposes_no_physical_postgres_types_or_environment_bypass() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_facade_dispatches_eleven_raw_capabilities_without_physical_leak() {
|
||||
fn v0_3_8_pre_005_facade_dispatches_twelve_raw_capabilities_without_physical_leak() {
|
||||
let store = include_str!("../src/store.rs");
|
||||
for required in [
|
||||
"impl ksp_store_api::RawAccountObservationRead for Store",
|
||||
"impl ksp_store_api::RawAccountObservationWrite for Store",
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for Store",
|
||||
"impl ksp_store_api::RawAccountStateRead for Store",
|
||||
"impl ksp_store_api::RawAccountStateWrite for Store",
|
||||
"impl ksp_store_api::RawTransactionInspectionRead for Store",
|
||||
@@ -80,7 +81,7 @@ fn v0_3_8_pre_004_facade_dispatches_eleven_raw_capabilities_without_physical_lea
|
||||
] {
|
||||
assert!(store.contains(required), "missing pre.008 Store capability dispatch contract: {required}");
|
||||
}
|
||||
assert_eq!(store.matches("impl ksp_store_api::Raw").count(), 11);
|
||||
assert_eq!(store.matches("impl ksp_store_api::Raw").count(), 12);
|
||||
for forbidden in ["tokio_postgres::", "deadpool_postgres::", "CREATE TABLE", "INSERT INTO", "UPDATE ksp_", "DELETE FROM"] {
|
||||
assert!(!store.contains(forbidden), "pre.008 facade leaked physical backend material: {forbidden}");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -292,11 +292,12 @@ fn pre_009_facade_production_sources_keep_config_env_physical_sql_and_backend_ha
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_facade_raw_capability_inventory_is_exactly_eleven() {
|
||||
fn v0_3_8_pre_005_facade_raw_capability_inventory_is_exactly_twelve() {
|
||||
let store = include_str!("../src/store.rs");
|
||||
let capability_impls = [
|
||||
"impl ksp_store_api::RawAccountObservationRead for Store",
|
||||
"impl ksp_store_api::RawAccountObservationWrite for Store",
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for Store",
|
||||
"impl ksp_store_api::RawAccountStateRead for Store",
|
||||
"impl ksp_store_api::RawAccountStateWrite for Store",
|
||||
"impl ksp_store_api::RawTransactionInspectionRead for Store",
|
||||
@@ -310,22 +311,22 @@ fn v0_3_8_pre_004_facade_raw_capability_inventory_is_exactly_eleven() {
|
||||
for implementation in capability_impls {
|
||||
assert_eq!(store.matches(implementation).count(), 1, "unexpected Store capability implementation inventory: {implementation}");
|
||||
}
|
||||
assert_eq!(store.matches("impl ksp_store_api::Raw").count(), 11);
|
||||
assert_eq!(store.matches("validate_operation_network(").count(), 15);
|
||||
assert_eq!(store.matches("impl ksp_store_api::Raw").count(), 12);
|
||||
assert_eq!(store.matches("validate_operation_network(").count(), 16);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_facade_and_backend_capability_sets_match_with_transaction_inspection_only() {
|
||||
fn v0_3_8_pre_005_facade_and_backend_capability_sets_match_with_both_inspection_capabilities() {
|
||||
let store = include_str!("../src/store.rs");
|
||||
let backend = include_str!("../../ksp-store-postgres-lib/src/runtime.rs");
|
||||
let store_traits = raw_capability_trait_names(store, " for Store");
|
||||
let backend_traits = raw_capability_trait_names(backend, " for PostgresBackend");
|
||||
assert_eq!(store_traits.len(), 11);
|
||||
assert_eq!(backend_traits.len(), 11);
|
||||
assert_eq!(store_traits.len(), 12);
|
||||
assert_eq!(backend_traits.len(), 12);
|
||||
assert_eq!(store_traits, backend_traits);
|
||||
assert!(store_traits.contains(&"RawTransactionInspectionRead"));
|
||||
assert!(!store_traits.contains(&"RawAccountStateInspectionRead"));
|
||||
assert!(store_traits.contains(&"RawAccountStateInspectionRead"));
|
||||
for forbidden in ["RawAccountRetentionRead", "RawAccountRetentionWrite", "RawAccountDelete", "RawAccountCompaction"] {
|
||||
assert!(!store_traits.contains(&forbidden), "unexpected account capability added to Store: {forbidden}");
|
||||
assert!(!backend_traits.contains(&forbidden), "unexpected account capability added to PostgreSQL backend: {forbidden}");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/public_api.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -81,6 +81,7 @@ fn assert_raw_capabilities<T>()
|
||||
where
|
||||
T: ksp_store_lib::RawAccountObservationRead
|
||||
+ ksp_store_lib::RawAccountObservationWrite
|
||||
+ ksp_store_lib::RawAccountStateInspectionRead
|
||||
+ ksp_store_lib::RawAccountStateRead
|
||||
+ ksp_store_lib::RawAccountStateWrite
|
||||
+ ksp_store_lib::RawTransactionInspectionRead
|
||||
@@ -96,7 +97,7 @@ where
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_store_facade_implements_raw_transaction_inspection_as_capability_11_of_11() {
|
||||
fn v0_3_8_pre_005_store_facade_implements_both_inspection_capabilities_as_12_of_12() {
|
||||
assert_raw_capabilities::<ksp_store_lib::Store>();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
||||
// version: 22
|
||||
// version: 23
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -33,8 +33,9 @@
|
||||
//! deterministic account keyset pagination with the fixed `KSPA` cursor. `0.3.4-pre.008`
|
||||
//! implements the four `RawAccount*` capabilities directly on `PostgresBackend`, completing
|
||||
//! the backend RAW capability inventory at ten without exposing physical PostgreSQL types.
|
||||
//! `0.3.8-pre.004` adds the payload-free random-access RawTransaction inspection
|
||||
//! capability with exact counts while preserving keyset traversal unchanged.
|
||||
//! `0.3.8-pre.004` adds payload-free random-access RawTransaction inspection;
|
||||
//! `0.3.8-pre.005` adds the corresponding data-free RawAccountState inspection
|
||||
//! with exact counts while preserving both keyset traversal families unchanged.
|
||||
//!
|
||||
//! 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
|
||||
@@ -86,6 +87,8 @@ pub(crate) use self::raw_account::get_raw_account_observation;
|
||||
pub(crate) use self::raw_account::get_raw_account_state;
|
||||
/// Private RAW account-state list reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_account::list_raw_account_states;
|
||||
/// Private data-free RAW account-state inspection reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_account::inspect_raw_account_states;
|
||||
/// Private atomic RAW account acquisition writer consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_account::persist_raw_account_acquisition;
|
||||
/// Private additional RAW account observation writer consumed by the physical backend runtime.
|
||||
@@ -104,10 +107,10 @@ pub(crate) use self::raw_transaction::get_raw_transaction_observation;
|
||||
pub(crate) use self::raw_transaction::get_raw_transaction_retention_state;
|
||||
/// Private RAW transaction tombstone reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_transaction::get_raw_transaction_tombstone;
|
||||
/// Private payload-free RAW transaction inspection reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_transaction::inspect_raw_transactions;
|
||||
/// Private RAW transaction list reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_transaction::list_raw_transactions;
|
||||
/// Private payload-free RAW transaction inspection reader consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_transaction::inspect_raw_transactions;
|
||||
/// Private atomic RAW transaction acquisition writer consumed by the physical backend runtime.
|
||||
pub(crate) use self::raw_transaction::persist_raw_transaction_acquisition;
|
||||
/// Private additional RAW transaction observation writer consumed by the physical backend runtime.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/raw_account.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
pub(crate) mod cursor;
|
||||
|
||||
@@ -7,6 +7,8 @@ const GET_ACCOUNT_OBSERVATION_SQL: &str = "SELECT observation_key, account_pubke
|
||||
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";
|
||||
const INSERT_ACCOUNT_OBSERVATION_SQL: &str = "INSERT INTO ksp_raw_account_observations (observation_key, account_pubkey, account_slot, 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) VALUES ($1, $2, $3::TEXT::NUMERIC, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19::TEXT::NUMERIC) ON CONFLICT (observation_key) DO NOTHING RETURNING observation_key";
|
||||
const INSERT_ACCOUNT_STATE_SQL: &str = "INSERT INTO ksp_raw_account_states (pubkey, slot, state_hash, lamports, owner, executable, rent_epoch, data) VALUES ($1, $2::TEXT::NUMERIC, $3, $4::TEXT::NUMERIC, $5, $6, $7::TEXT::NUMERIC, $8) ON CONFLICT (pubkey, slot, state_hash) DO NOTHING RETURNING pubkey";
|
||||
const INSPECT_ACCOUNT_STATES_ASC_SQL: &str = "WITH filtered_count AS (SELECT COUNT(*)::TEXT AS filtered_count_text FROM ksp_raw_account_states WHERE ($1::BYTEA IS NULL OR pubkey = $1::BYTEA) AND ($2::TEXT IS NULL OR slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR slot <= $3::TEXT::NUMERIC)), counts AS (SELECT filtered_count_text, CASE WHEN $1::BYTEA IS NULL AND $2::TEXT IS NULL AND $3::TEXT IS NULL THEN filtered_count_text ELSE (SELECT COUNT(*)::TEXT FROM ksp_raw_account_states) END AS total_count_text FROM filtered_count) SELECT counts.total_count_text, counts.filtered_count_text, page.pubkey IS NOT NULL AS page_present, page.pubkey, page.slot_text, page.state_hash, page.lamports_text, page.owner, page.executable, page.rent_epoch_text, page.data_length_bytes FROM counts LEFT JOIN LATERAL (SELECT account_row.pubkey, account_row.slot::TEXT AS slot_text, account_row.state_hash, account_row.lamports::TEXT AS lamports_text, account_row.owner, account_row.executable, account_row.rent_epoch::TEXT AS rent_epoch_text, OCTET_LENGTH(account_row.data)::BIGINT AS data_length_bytes FROM ksp_raw_account_states AS account_row WHERE ($1::BYTEA IS NULL OR account_row.pubkey = $1::BYTEA) AND ($2::TEXT IS NULL OR account_row.slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR account_row.slot <= $3::TEXT::NUMERIC) ORDER BY account_row.slot ASC, account_row.pubkey ASC, account_row.state_hash ASC LIMIT $4 OFFSET $5) AS page ON TRUE";
|
||||
const INSPECT_ACCOUNT_STATES_DESC_SQL: &str = "WITH filtered_count AS (SELECT COUNT(*)::TEXT AS filtered_count_text FROM ksp_raw_account_states WHERE ($1::BYTEA IS NULL OR pubkey = $1::BYTEA) AND ($2::TEXT IS NULL OR slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR slot <= $3::TEXT::NUMERIC)), counts AS (SELECT filtered_count_text, CASE WHEN $1::BYTEA IS NULL AND $2::TEXT IS NULL AND $3::TEXT IS NULL THEN filtered_count_text ELSE (SELECT COUNT(*)::TEXT FROM ksp_raw_account_states) END AS total_count_text FROM filtered_count) SELECT counts.total_count_text, counts.filtered_count_text, page.pubkey IS NOT NULL AS page_present, page.pubkey, page.slot_text, page.state_hash, page.lamports_text, page.owner, page.executable, page.rent_epoch_text, page.data_length_bytes FROM counts LEFT JOIN LATERAL (SELECT account_row.pubkey, account_row.slot::TEXT AS slot_text, account_row.state_hash, account_row.lamports::TEXT AS lamports_text, account_row.owner, account_row.executable, account_row.rent_epoch::TEXT AS rent_epoch_text, OCTET_LENGTH(account_row.data)::BIGINT AS data_length_bytes FROM ksp_raw_account_states AS account_row WHERE ($1::BYTEA IS NULL OR account_row.pubkey = $1::BYTEA) AND ($2::TEXT IS NULL OR account_row.slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR account_row.slot <= $3::TEXT::NUMERIC) ORDER BY account_row.slot DESC, account_row.pubkey DESC, account_row.state_hash DESC LIMIT $4 OFFSET $5) AS page ON TRUE";
|
||||
const LIST_ACCOUNT_STATES_ASC_SQL: &str = "SELECT pubkey, slot::text AS slot_text, state_hash FROM ksp_raw_account_states WHERE ($1::TEXT IS NULL OR slot >= $1::TEXT::NUMERIC) AND ($2::TEXT IS NULL OR slot <= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR (slot, pubkey, state_hash) > ($3::TEXT::NUMERIC, $4::BYTEA, $5::BYTEA)) ORDER BY slot ASC, pubkey ASC, state_hash ASC LIMIT $6";
|
||||
const LIST_ACCOUNT_STATES_BY_PUBKEY_ASC_SQL: &str = "SELECT pubkey, slot::text AS slot_text, state_hash FROM ksp_raw_account_states WHERE pubkey = $1 AND ($2::TEXT IS NULL OR slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR slot <= $3::TEXT::NUMERIC) AND ($4::TEXT IS NULL OR (slot, pubkey, state_hash) > ($4::TEXT::NUMERIC, $5::BYTEA, $6::BYTEA)) ORDER BY slot ASC, pubkey ASC, state_hash ASC LIMIT $7";
|
||||
const LIST_ACCOUNT_STATES_BY_PUBKEY_DESC_SQL: &str = "SELECT pubkey, slot::text AS slot_text, state_hash FROM ksp_raw_account_states WHERE pubkey = $1 AND ($2::TEXT IS NULL OR slot >= $2::TEXT::NUMERIC) AND ($3::TEXT IS NULL OR slot <= $3::TEXT::NUMERIC) AND ($4::TEXT IS NULL OR (slot, pubkey, state_hash) < ($4::TEXT::NUMERIC, $5::BYTEA, $6::BYTEA)) ORDER BY slot DESC, pubkey DESC, state_hash DESC LIMIT $7";
|
||||
@@ -22,6 +24,20 @@ struct RawAccountListDbRow {
|
||||
state_hash: std::vec::Vec<u8>,
|
||||
}
|
||||
|
||||
struct RawAccountInspectionDbRow {
|
||||
data_length_bytes: std::option::Option<i64>,
|
||||
executable: std::option::Option<bool>,
|
||||
filtered_count_text: std::string::String,
|
||||
lamports_text: std::option::Option<std::string::String>,
|
||||
owner: std::option::Option<std::vec::Vec<u8>>,
|
||||
page_present: bool,
|
||||
pubkey: std::option::Option<std::vec::Vec<u8>>,
|
||||
rent_epoch_text: std::option::Option<std::string::String>,
|
||||
slot_text: std::option::Option<std::string::String>,
|
||||
state_hash: std::option::Option<std::vec::Vec<u8>>,
|
||||
total_count_text: std::string::String,
|
||||
}
|
||||
|
||||
struct RawAccountObservationDbRow {
|
||||
account_pubkey: std::vec::Vec<u8>,
|
||||
account_slot_text: std::string::String,
|
||||
@@ -203,6 +219,107 @@ pub(crate) async fn list_raw_account_states(
|
||||
return std::result::Result::Ok(ksp_store_api::RawPage::new(items, next_cursor));
|
||||
}
|
||||
|
||||
/// Inspects one data-free random-access RAW account-state window with exact logical counts.
|
||||
pub(crate) async fn inspect_raw_account_states(
|
||||
pool: &deadpool_postgres::Pool,
|
||||
network: &ksp_store_api::RawNetworkId,
|
||||
query: &ksp_store_api::RawAccountStateInspectionQuery,
|
||||
) -> std::result::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawAccountStateSummary>, crate::PostgresBackendError> {
|
||||
if query.network() != network {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||
crate::PostgresBackendErrorKind::WrongNetwork,
|
||||
"raw_account_inspection_network",
|
||||
));
|
||||
}
|
||||
let sql = match query.direction() {
|
||||
ksp_store_api::RawSortDirection::Ascending => INSPECT_ACCOUNT_STATES_ASC_SQL,
|
||||
ksp_store_api::RawSortDirection::Descending => INSPECT_ACCOUNT_STATES_DESC_SQL,
|
||||
_ => {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||
crate::PostgresBackendErrorKind::QueryInvalid,
|
||||
"raw_account_inspection_direction",
|
||||
));
|
||||
},
|
||||
};
|
||||
let (sql_limit, sql_offset) = match raw_account_inspection_sql_window(query.page()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => 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 = query.pubkey().map(|value| return value.to_bytes().to_vec());
|
||||
let slots = query.slots();
|
||||
let start_text = slots.start_inclusive().map(|value| return value.to_string());
|
||||
let end_text = slots.end_inclusive().map(|value| return value.to_string());
|
||||
let rows_result = client.query(sql, &[&pubkey_bytes, &start_text, &end_text, &sql_limit, &sql_offset]).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_inspection_query"));
|
||||
},
|
||||
};
|
||||
if rows.is_empty() {
|
||||
return std::result::Result::Err(data_invalid("raw_account_inspection_cardinality"));
|
||||
}
|
||||
let row_count = rows.len();
|
||||
let mut total_items = std::option::Option::None;
|
||||
let mut filtered_items = std::option::Option::None;
|
||||
let mut items = std::vec::Vec::new();
|
||||
for row in rows {
|
||||
let physical = match raw_account_inspection_db_row(&row) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let row_total = match decode_u64_decimal(physical.total_count_text.as_str(), "raw_account_inspection_total") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let row_filtered = match decode_u64_decimal(physical.filtered_count_text.as_str(), "raw_account_inspection_filtered") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
match (total_items, filtered_items) {
|
||||
(std::option::Option::None, std::option::Option::None) => {
|
||||
total_items = std::option::Option::Some(row_total);
|
||||
filtered_items = std::option::Option::Some(row_filtered);
|
||||
},
|
||||
(std::option::Option::Some(total), std::option::Option::Some(filtered)) if total == row_total && filtered == row_filtered => {},
|
||||
_ => return std::result::Result::Err(data_invalid("raw_account_inspection_counts")),
|
||||
}
|
||||
if physical.page_present {
|
||||
let summary = match decode_raw_account_inspection_summary(network, physical) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
items.push(summary);
|
||||
} else if row_count != 1 || !raw_account_inspection_empty_page_is_clean(&physical) {
|
||||
return std::result::Result::Err(data_invalid("raw_account_inspection_page"));
|
||||
}
|
||||
}
|
||||
let total_items = match total_items {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(data_invalid("raw_account_inspection_total")),
|
||||
};
|
||||
let filtered_items = match filtered_items {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Err(data_invalid("raw_account_inspection_filtered")),
|
||||
};
|
||||
let item_count = match u64::try_from(items.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_page")),
|
||||
};
|
||||
if item_count > query.page().limit().get() {
|
||||
return std::result::Result::Err(data_invalid("raw_account_inspection_page"));
|
||||
}
|
||||
return match ksp_store_api::RawInspectionPage::try_new(items, total_items, filtered_items) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(data_invalid("raw_account_inspection_page")),
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads one persisted RAW account observation by producer-owned idempotence key.
|
||||
pub(crate) async fn get_raw_account_observation(
|
||||
pool: &deadpool_postgres::Pool,
|
||||
@@ -913,6 +1030,182 @@ fn decode_timestamp_i64(value: i64, phase: &'static str) -> std::result::Result<
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_account_inspection_empty_page_is_clean(row: &RawAccountInspectionDbRow) -> bool {
|
||||
return !row.page_present
|
||||
&& row.data_length_bytes.is_none()
|
||||
&& row.executable.is_none()
|
||||
&& row.lamports_text.is_none()
|
||||
&& row.owner.is_none()
|
||||
&& row.pubkey.is_none()
|
||||
&& row.rent_epoch_text.is_none()
|
||||
&& row.slot_text.is_none()
|
||||
&& row.state_hash.is_none();
|
||||
}
|
||||
|
||||
fn raw_account_inspection_sql_window(
|
||||
page: ksp_store_api::RawInspectionPageRequest,
|
||||
) -> std::result::Result<(i64, i64), crate::PostgresBackendError> {
|
||||
let sql_limit = match i64::try_from(page.limit().get()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||
crate::PostgresBackendErrorKind::PageLimitUnsupported,
|
||||
"raw_account_inspection_limit",
|
||||
));
|
||||
},
|
||||
};
|
||||
let sql_offset = match i64::try_from(page.offset()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||
crate::PostgresBackendErrorKind::QueryInvalid,
|
||||
"raw_account_inspection_offset",
|
||||
));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok((sql_limit, sql_offset));
|
||||
}
|
||||
|
||||
fn raw_account_inspection_db_row(row: &tokio_postgres::Row) -> std::result::Result<RawAccountInspectionDbRow, crate::PostgresBackendError> {
|
||||
let data_length_bytes = match row.try_get::<_, std::option::Option<i64>>("data_length_bytes") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let executable = match row.try_get::<_, std::option::Option<bool>>("executable") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let filtered_count_text = match row.try_get::<_, std::string::String>("filtered_count_text") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let lamports_text = match row.try_get::<_, std::option::Option<std::string::String>>("lamports_text") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let owner = match row.try_get::<_, std::option::Option<std::vec::Vec<u8>>>("owner") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let page_present = match row.try_get::<_, bool>("page_present") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let pubkey = match row.try_get::<_, std::option::Option<std::vec::Vec<u8>>>("pubkey") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let rent_epoch_text = match row.try_get::<_, std::option::Option<std::string::String>>("rent_epoch_text") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let slot_text = match row.try_get::<_, std::option::Option<std::string::String>>("slot_text") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let state_hash = match row.try_get::<_, std::option::Option<std::vec::Vec<u8>>>("state_hash") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
let total_count_text = match row.try_get::<_, std::string::String>("total_count_text") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_decode")),
|
||||
};
|
||||
return std::result::Result::Ok(RawAccountInspectionDbRow {
|
||||
data_length_bytes,
|
||||
executable,
|
||||
filtered_count_text,
|
||||
lamports_text,
|
||||
owner,
|
||||
page_present,
|
||||
pubkey,
|
||||
rent_epoch_text,
|
||||
slot_text,
|
||||
state_hash,
|
||||
total_count_text,
|
||||
});
|
||||
}
|
||||
|
||||
fn decode_raw_account_inspection_summary(
|
||||
network: &ksp_store_api::RawNetworkId,
|
||||
row: RawAccountInspectionDbRow,
|
||||
) -> std::result::Result<ksp_store_api::RawAccountStateSummary, crate::PostgresBackendError> {
|
||||
if !row.page_present {
|
||||
return std::result::Result::Err(data_invalid("raw_account_inspection_page"));
|
||||
}
|
||||
let pubkey = match inspection_required(row.pubkey, "raw_account_inspection_pubkey") {
|
||||
std::result::Result::Ok(value) => match fixed_bytes::<32>(value, "raw_account_inspection_pubkey") {
|
||||
std::result::Result::Ok(bytes) => ksp_store_api::Pubkey::new_from_array(bytes),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let slot_text = match inspection_required(row.slot_text, "raw_account_inspection_slot") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let slot = match decode_u64_decimal(slot_text.as_str(), "raw_account_inspection_slot") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let state_hash_raw = match inspection_required(row.state_hash, "raw_account_inspection_state_hash") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let state_hash = match fixed_bytes::<32>(state_hash_raw, "raw_account_inspection_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_text = match inspection_required(row.lamports_text, "raw_account_inspection_lamports") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let lamports = match decode_u64_decimal(lamports_text.as_str(), "raw_account_inspection_lamports") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let owner_raw = match inspection_required(row.owner, "raw_account_inspection_owner") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let owner = match fixed_bytes::<32>(owner_raw, "raw_account_inspection_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 executable = match inspection_required(row.executable, "raw_account_inspection_executable") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rent_epoch_text = match inspection_required(row.rent_epoch_text, "raw_account_inspection_rent_epoch") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rent_epoch = match decode_u64_decimal(rent_epoch_text.as_str(), "raw_account_inspection_rent_epoch") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let data_length_raw = match inspection_required(row.data_length_bytes, "raw_account_inspection_data_length") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let data_length_bytes = match u64::try_from(data_length_raw) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(data_invalid("raw_account_inspection_data_length")),
|
||||
};
|
||||
let reference = ksp_store_api::RawAccountStateReference::new(network.clone(), pubkey, slot, state_hash);
|
||||
return match ksp_store_api::RawAccountStateSummary::try_new(reference, lamports, owner, executable, rent_epoch, data_length_bytes) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(data_invalid("raw_account_inspection_summary")),
|
||||
};
|
||||
}
|
||||
|
||||
fn inspection_required<T>(value: std::option::Option<T>, phase: &'static str) -> std::result::Result<T, crate::PostgresBackendError> {
|
||||
return match value {
|
||||
std::option::Option::Some(inner) => std::result::Result::Ok(inner),
|
||||
std::option::Option::None => 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),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/runtime.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
|
||||
const APPLICATION_NAME: &str = "ksp-store";
|
||||
const MAX_CONNECTION_URI_BYTES: usize = 4_096;
|
||||
@@ -348,6 +348,14 @@ impl PostgresBackend {
|
||||
return crate::list_raw_account_states(&self.pool, &self.network, query).await;
|
||||
}
|
||||
|
||||
/// Inspects one data-free random-access RAW account-state window with exact logical counts.
|
||||
pub async fn inspect_raw_account_states(
|
||||
&self,
|
||||
query: &ksp_store_api::RawAccountStateInspectionQuery,
|
||||
) -> std::result::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawAccountStateSummary>, crate::PostgresBackendError> {
|
||||
return crate::inspect_raw_account_states(&self.pool, &self.network, query).await;
|
||||
}
|
||||
|
||||
/// Reads one persisted RAW account observation by producer-owned idempotence key.
|
||||
pub async fn get_raw_account_observation(
|
||||
&self,
|
||||
@@ -523,6 +531,18 @@ impl ksp_store_api::RawAccountStateRead for PostgresBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend {
|
||||
fn inspect_raw_account_states<'a>(
|
||||
&'a self,
|
||||
query: &'a ksp_store_api::RawAccountStateInspectionQuery,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawInspectionPage<ksp_store_api::RawAccountStateSummary>>> {
|
||||
return std::boxed::Box::pin(async move {
|
||||
let result = PostgresBackend::inspect_raw_account_states(self, query).await;
|
||||
return result.map_err(map_capability_error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawAccountStateWrite for PostgresBackend {
|
||||
fn persist_raw_account_acquisition<'a>(
|
||||
&'a self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||
// version: 26
|
||||
// version: 27
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -184,6 +184,7 @@ fn pre_005_raw_account_acquisition_is_atomic_idempotent_and_keeps_trait_impls_ou
|
||||
assert!(!raw.contains(forbidden), "pre.005 account module contains later/destructive scope: {forbidden}");
|
||||
}
|
||||
for implementation in [
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
@@ -218,6 +219,7 @@ fn pre_006_raw_account_additional_observation_is_reference_guarded_cancellation_
|
||||
assert!(!raw.contains(forbidden), "pre.006 account module contains later/destructive scope: {forbidden}");
|
||||
}
|
||||
for implementation in [
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
@@ -374,11 +376,36 @@ fn pre_007_raw_account_pagination_is_keyset_cursor_bound_and_policy_free() {
|
||||
assert!(cursor.contains(required), "missing pre.007 account cursor/binding contract: {required}");
|
||||
}
|
||||
assert!(index.contains("ON ksp_raw_account_states (slot, pubkey, state_hash)"));
|
||||
for forbidden in [" OFFSET ", "limit.min(", "clamp(", "500", "1000"] {
|
||||
let ascending = raw.lines().find(|line| return line.starts_with("const LIST_ACCOUNT_STATES_ASC_SQL"));
|
||||
let ascending = match ascending {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing canonical account ascending keyset SQL"),
|
||||
};
|
||||
let descending = raw.lines().find(|line| return line.starts_with("const LIST_ACCOUNT_STATES_DESC_SQL"));
|
||||
let descending = match descending {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing canonical account descending keyset SQL"),
|
||||
};
|
||||
let by_pubkey_ascending = raw.lines().find(|line| return line.starts_with("const LIST_ACCOUNT_STATES_BY_PUBKEY_ASC_SQL"));
|
||||
let by_pubkey_ascending = match by_pubkey_ascending {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing canonical account pubkey ascending keyset SQL"),
|
||||
};
|
||||
let by_pubkey_descending = raw.lines().find(|line| return line.starts_with("const LIST_ACCOUNT_STATES_BY_PUBKEY_DESC_SQL"));
|
||||
let by_pubkey_descending = match by_pubkey_descending {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing canonical account pubkey descending keyset SQL"),
|
||||
};
|
||||
for statement in [ascending, descending, by_pubkey_ascending, by_pubkey_descending] {
|
||||
assert!(!statement.contains(" OFFSET "), "pre.007 canonical account keyset SQL must remain OFFSET-free");
|
||||
}
|
||||
assert!(!cursor.contains(" OFFSET "), "pre.007 account cursor contains forbidden OFFSET material");
|
||||
for forbidden in ["limit.min(", "clamp(", "500", "1000"] {
|
||||
assert!(!raw.contains(forbidden), "pre.007 account source contains forbidden pagination/policy material: {forbidden}");
|
||||
assert!(!cursor.contains(forbidden), "pre.007 account cursor contains forbidden pagination/policy material: {forbidden}");
|
||||
}
|
||||
for forbidden in [
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
@@ -422,11 +449,12 @@ fn pre_007_raw_retention_is_atomic_compare_and_transition_without_fake_compactio
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_backend_trait_implementations_cover_exact_eleven_raw_capabilities_in_runtime_bridge() {
|
||||
fn v0_3_8_pre_005_backend_trait_implementations_cover_exact_twelve_raw_capabilities_in_runtime_bridge() {
|
||||
let runtime = include_str!("../src/runtime.rs");
|
||||
for implementation in [
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionInspectionRead for PostgresBackend",
|
||||
@@ -439,6 +467,6 @@ fn v0_3_8_pre_004_backend_trait_implementations_cover_exact_eleven_raw_capabilit
|
||||
] {
|
||||
assert_eq!(runtime.matches(implementation).count(), 1, "unexpected PostgreSQL RAW capability inventory: {implementation}");
|
||||
}
|
||||
assert_eq!(runtime.matches("impl ksp_store_api::Raw").count(), 11);
|
||||
assert_eq!(runtime.matches("impl ksp_store_api::Raw").count(), 12);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||
// version: 19
|
||||
// version: 20
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -250,6 +250,9 @@ fn pre_009_live_raw_account_proof_is_opt_in_isolated_secret_safe_and_cross_famil
|
||||
"prove_additional_observations",
|
||||
"ReferenceNotFound",
|
||||
"prove_pagination_and_cursors",
|
||||
"prove_account_inspection",
|
||||
"inspect_raw_account_states",
|
||||
"RawAccountStateInspectionQuery",
|
||||
"KSPT",
|
||||
"prove_cross_family_coexistence",
|
||||
"persist_raw_transaction_acquisition",
|
||||
@@ -299,11 +302,12 @@ fn pre_009_live_raw_transaction_proof_is_opt_in_isolated_and_secret_safe() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_raw_capability_implementation_inventory_is_exactly_eleven() {
|
||||
fn v0_3_8_pre_005_raw_capability_implementation_inventory_is_exactly_twelve() {
|
||||
let runtime = include_str!("../src/runtime.rs");
|
||||
let capability_impls = [
|
||||
"impl ksp_store_api::RawAccountObservationRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountObservationWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateInspectionRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateRead for PostgresBackend",
|
||||
"impl ksp_store_api::RawAccountStateWrite for PostgresBackend",
|
||||
"impl ksp_store_api::RawTransactionInspectionRead for PostgresBackend",
|
||||
@@ -317,7 +321,7 @@ fn v0_3_8_pre_004_raw_capability_implementation_inventory_is_exactly_eleven() {
|
||||
for implementation in capability_impls {
|
||||
assert_eq!(runtime.matches(implementation).count(), 1, "unexpected PostgreSQL capability implementation inventory: {implementation}");
|
||||
}
|
||||
assert_eq!(runtime.matches("impl ksp_store_api::Raw").count(), 11);
|
||||
assert_eq!(runtime.matches("impl ksp_store_api::Raw").count(), 12);
|
||||
let migration = include_str!("../src/migration.rs");
|
||||
assert!(migration.contains("raw_account_state"));
|
||||
assert!(migration.contains("crate::V002_RESOURCES"));
|
||||
@@ -466,3 +470,38 @@ fn pre_010_v002_schema_inventory_is_exact_family_local_and_query_justified() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_005_raw_account_keyset_sql_remains_offset_free_while_inspection_is_random_access() {
|
||||
let source = include_str!("../src/raw_account.rs");
|
||||
for name in [
|
||||
"const LIST_ACCOUNT_STATES_ASC_SQL",
|
||||
"const LIST_ACCOUNT_STATES_DESC_SQL",
|
||||
"const LIST_ACCOUNT_STATES_BY_PUBKEY_ASC_SQL",
|
||||
"const LIST_ACCOUNT_STATES_BY_PUBKEY_DESC_SQL",
|
||||
] {
|
||||
let statement = source.lines().find(|line| return line.starts_with(name));
|
||||
let statement = match statement {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing canonical account keyset SQL: {name}"),
|
||||
};
|
||||
assert!(!statement.contains(" OFFSET "));
|
||||
}
|
||||
for name in ["const INSPECT_ACCOUNT_STATES_ASC_SQL", "const INSPECT_ACCOUNT_STATES_DESC_SQL"] {
|
||||
let statement = source.lines().find(|line| return line.starts_with(name));
|
||||
let statement = match statement {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("missing account inspection SQL: {name}"),
|
||||
};
|
||||
for required in ["COUNT(*)", "LEFT JOIN LATERAL", "OCTET_LENGTH(account_row.data)", "LIMIT $4 OFFSET $5"] {
|
||||
assert!(statement.contains(required), "missing account inspection SQL contract: {required}");
|
||||
}
|
||||
assert!(!statement.contains("account_row.data,"));
|
||||
assert!(!statement.contains("SELECT account_row.data"));
|
||||
assert!(statement.contains("ORDER BY account_row.slot"));
|
||||
assert!(statement.contains("account_row.pubkey"));
|
||||
assert!(statement.contains("account_row.state_hash"));
|
||||
}
|
||||
assert_eq!(source.matches(" OFFSET ").count(), 2);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/postgres_raw_account_live.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -68,7 +68,10 @@ enum LiveAccountPersistResult {
|
||||
#[test]
|
||||
#[ignore = "opt-in real PostgreSQL RawAccountState proof; reads one dedicated URI from stdin"]
|
||||
fn pre_009_real_postgres_raw_account_vertical_slice_is_atomic_concurrent_and_recoverable() {
|
||||
eprintln!("KSP Store RawAccountState live proof: reading one dedicated URI from stdin without echoing it from the test.");
|
||||
eprintln!(
|
||||
"KSP Store RawAccountState live proof: waiting for one dedicated PostgreSQL URI line on stdin; the scenario does not start until Enter is pressed."
|
||||
);
|
||||
eprintln!("KSP Store RawAccountState live proof: the test itself never echoes the URI.");
|
||||
let uri_result = read_uri_from_stdin();
|
||||
let uri = match uri_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -179,6 +182,7 @@ async fn run_raw_account_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
||||
prove_atomic_idempotence_and_conflicts(&backend).await,
|
||||
prove_additional_observations(&backend).await,
|
||||
prove_pagination_and_cursors(&backend).await,
|
||||
prove_account_inspection(&backend).await,
|
||||
prove_cross_family_coexistence(&backend, admin).await,
|
||||
] {
|
||||
if let std::result::Result::Err(error) = proof {
|
||||
@@ -561,6 +565,80 @@ async fn prove_pagination_and_cursors(backend: &ksp_store_postgres_lib::Postgres
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn prove_account_inspection(backend: &ksp_store_postgres_lib::PostgresBackend) -> std::result::Result<(), LiveFailure> {
|
||||
let filtered_query = match account_inspection_query(
|
||||
std::option::Option::Some(ksp_store_api::Pubkey::new_from_array([50; 32])),
|
||||
200,
|
||||
202,
|
||||
ksp_store_api::RawSortDirection::Ascending,
|
||||
1,
|
||||
2,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let filtered = match backend.inspect_raw_account_states(&filtered_query).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("inspection_filtered_query")),
|
||||
};
|
||||
if filtered.filtered_items() != 3 || filtered.total_items() < 5 || filtered.items().len() != 2 {
|
||||
return std::result::Result::Err(LiveFailure::new("inspection_filtered_counts"));
|
||||
}
|
||||
let first = &filtered.items()[0];
|
||||
let second = &filtered.items()[1];
|
||||
if first.reference().slot() != 200
|
||||
|| first.reference().pubkey() != &ksp_store_api::Pubkey::new_from_array([50; 32])
|
||||
|| first.reference().state_hash() != ksp_store_api::RawContentHash::new([51; 32])
|
||||
|| first.data_length_bytes() != 2
|
||||
|| second.reference().slot() != 201
|
||||
|| second.reference().state_hash() != ksp_store_api::RawContentHash::new([52; 32])
|
||||
|| second.data_length_bytes() != 2
|
||||
{
|
||||
return std::result::Result::Err(LiveFailure::new("inspection_filtered_order"));
|
||||
}
|
||||
let descending_query = match account_inspection_query(std::option::Option::None, 200, 202, ksp_store_api::RawSortDirection::Descending, 1, 2) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let descending = match backend.inspect_raw_account_states(&descending_query).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("inspection_descending_query")),
|
||||
};
|
||||
if descending.filtered_items() != 5 || descending.items().len() != 2 {
|
||||
return std::result::Result::Err(LiveFailure::new("inspection_descending_counts"));
|
||||
}
|
||||
let descending_first = &descending.items()[0];
|
||||
let descending_second = &descending.items()[1];
|
||||
if descending_first.reference().slot() != 201
|
||||
|| descending_first.reference().pubkey() != &ksp_store_api::Pubkey::new_from_array([51; 32])
|
||||
|| descending_first.reference().state_hash() != ksp_store_api::RawContentHash::new([53; 32])
|
||||
|| descending_second.reference().slot() != 201
|
||||
|| descending_second.reference().pubkey() != &ksp_store_api::Pubkey::new_from_array([50; 32])
|
||||
|| descending_second.reference().state_hash() != ksp_store_api::RawContentHash::new([52; 32])
|
||||
{
|
||||
return std::result::Result::Err(LiveFailure::new("inspection_descending_order"));
|
||||
}
|
||||
let deep_query = match account_inspection_query(
|
||||
std::option::Option::Some(ksp_store_api::Pubkey::new_from_array([50; 32])),
|
||||
200,
|
||||
202,
|
||||
ksp_store_api::RawSortDirection::Ascending,
|
||||
10_000,
|
||||
2,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let deep = match backend.inspect_raw_account_states(&deep_query).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("inspection_deep_query")),
|
||||
};
|
||||
if deep.filtered_items() != 3 || deep.total_items() != filtered.total_items() || !deep.items().is_empty() {
|
||||
return std::result::Result::Err(LiveFailure::new("inspection_deep_page"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn prove_cross_family_coexistence(
|
||||
backend: &ksp_store_postgres_lib::PostgresBackend,
|
||||
admin: &tokio_postgres::Client,
|
||||
@@ -850,6 +928,30 @@ fn account_query(
|
||||
return std::result::Result::Ok(ksp_store_api::RawAccountStateQuery::new(network, pubkey, slots, direction, page));
|
||||
}
|
||||
|
||||
fn account_inspection_query(
|
||||
pubkey: std::option::Option<ksp_store_api::Pubkey>,
|
||||
start: u64,
|
||||
end: u64,
|
||||
direction: ksp_store_api::RawSortDirection,
|
||||
offset: u64,
|
||||
limit: u64,
|
||||
) -> std::result::Result<ksp_store_api::RawAccountStateInspectionQuery, LiveFailure> {
|
||||
let network = match network("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let slots = match ksp_store_api::RawSlotRange::new(std::option::Option::Some(start), std::option::Option::Some(end)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("inspection_model_slot_range")),
|
||||
};
|
||||
let limit = match ksp_store_api::RawPageLimit::new(limit) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("inspection_model_page_limit")),
|
||||
};
|
||||
let page = ksp_store_api::RawInspectionPageRequest::new(offset, limit);
|
||||
return std::result::Result::Ok(ksp_store_api::RawAccountStateInspectionQuery::new(network, pubkey, slots, direction, page));
|
||||
}
|
||||
|
||||
fn account_reference(pubkey_seed: u8, slot: u64, hash_seed: u8) -> std::result::Result<ksp_store_api::RawAccountStateReference, LiveFailure> {
|
||||
let network = match network("devnet") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/public_api.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -117,9 +117,11 @@ fn pre_005_raw_write_bridge_uses_only_backend_independent_models_and_outcomes()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_raw_list_and_inspection_bridges_use_only_backend_independent_models() {
|
||||
let _list = ksp_store_postgres_lib::PostgresBackend::list_raw_transactions;
|
||||
let _inspection = ksp_store_postgres_lib::PostgresBackend::inspect_raw_transactions;
|
||||
fn v0_3_8_pre_005_raw_list_and_inspection_bridges_use_only_backend_independent_models() {
|
||||
let _account_list = ksp_store_postgres_lib::PostgresBackend::list_raw_account_states;
|
||||
let _account_inspection = ksp_store_postgres_lib::PostgresBackend::inspect_raw_account_states;
|
||||
let _transaction_list = ksp_store_postgres_lib::PostgresBackend::list_raw_transactions;
|
||||
let _transaction_inspection = ksp_store_postgres_lib::PostgresBackend::inspect_raw_transactions;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,6 +135,7 @@ fn assert_raw_capabilities<T>()
|
||||
where
|
||||
T: ksp_store_api::RawAccountObservationRead
|
||||
+ ksp_store_api::RawAccountObservationWrite
|
||||
+ ksp_store_api::RawAccountStateInspectionRead
|
||||
+ ksp_store_api::RawAccountStateRead
|
||||
+ ksp_store_api::RawAccountStateWrite
|
||||
+ ksp_store_api::RawTransactionInspectionRead
|
||||
@@ -148,7 +151,7 @@ where
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_004_postgres_backend_implements_transaction_inspection_as_capability_11_of_11() {
|
||||
fn v0_3_8_pre_005_postgres_backend_implements_both_inspection_capabilities_as_12_of_12() {
|
||||
assert_raw_capabilities::<ksp_store_postgres_lib::PostgresBackend>();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/unit_tests/raw_account.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
fn network() -> ksp_store_api::RawNetworkId {
|
||||
return match ksp_store_api::RawNetworkId::new("devnet") {
|
||||
@@ -67,6 +67,95 @@ fn observation_row() -> super::RawAccountObservationDbRow {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return super::RawAccountInspectionDbRow {
|
||||
data_length_bytes: std::option::Option::Some(data_length_bytes),
|
||||
executable: std::option::Option::Some(true),
|
||||
filtered_count_text: "3".to_owned(),
|
||||
lamports_text: std::option::Option::Some(u64::MAX.to_string()),
|
||||
owner: std::option::Option::Some(vec![2; 32]),
|
||||
page_present: true,
|
||||
pubkey: std::option::Option::Some(vec![1; 32]),
|
||||
rent_epoch_text: std::option::Option::Some(u64::MAX.to_string()),
|
||||
slot_text: std::option::Option::Some(u64::MAX.to_string()),
|
||||
state_hash: std::option::Option::Some(vec![3; 32]),
|
||||
total_count_text: "4".to_owned(),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_005_inspection_window_rejects_unrepresentable_limit_and_offset_before_io() {
|
||||
let normal_limit = match ksp_store_api::RawPageLimit::new(100) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid account inspection page limit rejected: {error:?}"),
|
||||
};
|
||||
let normal = super::raw_account_inspection_sql_window(ksp_store_api::RawInspectionPageRequest::new(25, normal_limit));
|
||||
assert_eq!(normal, std::result::Result::Ok((100, 25)));
|
||||
let huge_limit = match ksp_store_api::RawPageLimit::new(u64::MAX) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("API unexpectedly rejected backend-physical account limit fixture: {error:?}"),
|
||||
};
|
||||
let limit_error = super::raw_account_inspection_sql_window(ksp_store_api::RawInspectionPageRequest::new(0, huge_limit));
|
||||
assert_eq!(
|
||||
limit_error.err().map(|value| return (value.kind(), value.phase())),
|
||||
std::option::Option::Some((crate::PostgresBackendErrorKind::PageLimitUnsupported, "raw_account_inspection_limit")),
|
||||
);
|
||||
let offset_error = super::raw_account_inspection_sql_window(ksp_store_api::RawInspectionPageRequest::new(u64::MAX, normal_limit));
|
||||
assert_eq!(
|
||||
offset_error.err().map(|value| return (value.kind(), value.phase())),
|
||||
std::option::Option::Some((crate::PostgresBackendErrorKind::QueryInvalid, "raw_account_inspection_offset")),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_005_inspection_summary_is_data_free_and_preserves_complete_u64_domain() {
|
||||
let network = network();
|
||||
let decoded = super::decode_raw_account_inspection_summary(&network, inspection_row(4));
|
||||
let summary = match decoded {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("valid account inspection summary rejected: {error:?}"),
|
||||
};
|
||||
assert_eq!(summary.reference().network().as_str(), "devnet");
|
||||
assert_eq!(summary.reference().pubkey(), &ksp_store_api::Pubkey::new_from_array([1; 32]));
|
||||
assert_eq!(summary.reference().slot(), u64::MAX);
|
||||
assert_eq!(summary.reference().state_hash(), ksp_store_api::RawContentHash::new([3; 32]));
|
||||
assert_eq!(summary.lamports(), u64::MAX);
|
||||
assert_eq!(summary.owner(), &ksp_store_api::Pubkey::new_from_array([2; 32]));
|
||||
assert!(summary.executable());
|
||||
assert_eq!(summary.rent_epoch(), u64::MAX);
|
||||
assert_eq!(summary.data_length_bytes(), 4);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_8_pre_005_inspection_summary_rejects_hostile_width_decimal_and_data_length() {
|
||||
let network = network();
|
||||
let mut bad_owner = inspection_row(4);
|
||||
bad_owner.owner = std::option::Option::Some(vec![2; 31]);
|
||||
assert_eq!(
|
||||
super::decode_raw_account_inspection_summary(&network, bad_owner).err().map(|value| return value.kind()),
|
||||
std::option::Option::Some(crate::PostgresBackendErrorKind::DataInvalid)
|
||||
);
|
||||
let mut bad_slot = inspection_row(4);
|
||||
bad_slot.slot_text = std::option::Option::Some("18446744073709551616-HOSTILE".to_owned());
|
||||
let error = match super::decode_raw_account_inspection_summary(&network, bad_slot) {
|
||||
std::result::Result::Err(value) => value,
|
||||
std::result::Result::Ok(_) => panic!("hostile account inspection slot unexpectedly decoded"),
|
||||
};
|
||||
assert_eq!(error.kind(), crate::PostgresBackendErrorKind::DataInvalid);
|
||||
assert!(!std::format!("{error:?}").contains("HOSTILE"));
|
||||
let maximum = match i64::try_from(ksp_store_api::MAX_RAW_ACCOUNT_DATA_BYTES) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => panic!("account data bound must fit PostgreSQL BIGINT in test fixture"),
|
||||
};
|
||||
for length in [-1_i64, maximum.saturating_add(1)] {
|
||||
let result = super::decode_raw_account_inspection_summary(&network, inspection_row(length));
|
||||
assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::DataInvalid));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_004_account_state_row_round_trips_complete_u64_domain_and_bytes() {
|
||||
let network = network();
|
||||
|
||||
192
deltas/0.3.8/pre.005.md
Normal file
192
deltas/0.3.8/pre.005.md
Normal file
@@ -0,0 +1,192 @@
|
||||
<!-- file: deltas/0.3.8/pre.005.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.3.8-pre.005` — inspection PostgreSQL RawAccountState et dispatch Store
|
||||
|
||||
## Base requise
|
||||
|
||||
Base directe attendue :
|
||||
|
||||
```text
|
||||
0.3.8-pre.004-fix.001
|
||||
workspace.package.version = 0.3.8-pre.4.fix.1
|
||||
```
|
||||
|
||||
Le gate opérateur de cette base est entièrement vert et le live proof RawTransaction a réellement passé sur PostgreSQL 17.
|
||||
|
||||
La livraison est :
|
||||
|
||||
```text
|
||||
0.3.8-pre.005
|
||||
workspace.package.version = 0.3.8-pre.5
|
||||
commit = v0.3.8-pre.005
|
||||
tag = aucun
|
||||
```
|
||||
|
||||
## Objectif
|
||||
|
||||
Compléter le second vertical slice physique de l'API d'inspection backend-neutral :
|
||||
|
||||
```text
|
||||
RawAccountStateInspectionRead
|
||||
ksp-store-postgres-lib
|
||||
-> ksp-store-lib::Store
|
||||
```
|
||||
|
||||
Les deux familles RAW disposent alors de leur capability d'inspection random-access. Aucun branchement UI/DataTables, aucune migration et aucune capability destructive Account ne sont ajoutés.
|
||||
|
||||
## SQL d'inspection Account
|
||||
|
||||
Deux statements privés existent, un par direction canonique. Chaque appel exécute une seule instruction SQL comprenant :
|
||||
|
||||
```text
|
||||
filtered_count
|
||||
conditional total_count
|
||||
LATERAL page
|
||||
ORDER BY slot, pubkey, state_hash
|
||||
LIMIT/OFFSET
|
||||
```
|
||||
|
||||
`filtered_items` applique le filtre exact `pubkey` optionnel et le range slot inclusif. `total_items` reste le nombre exact de lignes `ksp_raw_account_states`. Lorsqu'aucun filtre n'est actif, le count filtré est réutilisé comme total.
|
||||
|
||||
Counts et page proviennent donc du même snapshot de statement PostgreSQL.
|
||||
|
||||
## Aucun transfert de data bytes
|
||||
|
||||
Le statement ne sélectionne jamais :
|
||||
|
||||
```text
|
||||
account_row.data
|
||||
```
|
||||
|
||||
Il projette uniquement :
|
||||
|
||||
```text
|
||||
OCTET_LENGTH(account_row.data)::BIGINT AS data_length_bytes
|
||||
```
|
||||
|
||||
Le summary reconstruit uniquement l'identité canonique, lamports, owner, executable, rent epoch et longueur des données. Aucun N+1 n'est introduit.
|
||||
|
||||
## Pagination et limites physiques
|
||||
|
||||
Le chemin inspection convertit avant I/O :
|
||||
|
||||
```text
|
||||
limit -> i64 PostgreSQL
|
||||
offset -> i64 PostgreSQL
|
||||
```
|
||||
|
||||
Un limit non représentable renvoie `PageLimitUnsupported`; un offset non représentable renvoie `QueryInvalid`.
|
||||
|
||||
Les quatre statements Account historiques de pagination cursor/keyset restent OFFSET-free :
|
||||
|
||||
```text
|
||||
LIST_ACCOUNT_STATES_ASC_SQL
|
||||
LIST_ACCOUNT_STATES_DESC_SQL
|
||||
LIST_ACCOUNT_STATES_BY_PUBKEY_ASC_SQL
|
||||
LIST_ACCOUNT_STATES_BY_PUBKEY_DESC_SQL
|
||||
```
|
||||
|
||||
L'OFFSET reste donc strictement local à l'inspection random-access.
|
||||
|
||||
## Façade Store et inventaire
|
||||
|
||||
`PostgresBackend` et `Store` implémentent désormais `RawAccountStateInspectionRead`, avec guard network côté façade avant dispatch.
|
||||
|
||||
L'inventaire RAW passe de 11 à 12 capabilities sur les deux couches :
|
||||
|
||||
```text
|
||||
11 historiques incluant RawTransactionInspectionRead
|
||||
+ RawAccountStateInspectionRead
|
||||
= 12
|
||||
```
|
||||
|
||||
Les inventaires Store/PostgreSQL restent exactement symétriques.
|
||||
|
||||
## Tests et live proof
|
||||
|
||||
Le delta ajoute ou renforce les preuves suivantes :
|
||||
|
||||
- conversion hostile limit/offset avant I/O ;
|
||||
- summary data-free avec domaine `u64` complet ;
|
||||
- rejet widths/décimaux/data length hostiles sans echo ;
|
||||
- séparation explicite keyset OFFSET-free / inspection avec OFFSET ;
|
||||
- SQL counted, single-statement, `OCTET_LENGTH(data)` et aucun data bytes ;
|
||||
- inventaires exacts 12/12 ;
|
||||
- live proof Account opt-in : filtre pubkey, counts, ordre asc/desc, offset, data length et page vide profonde.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
deltas/0.3.8/pre.005.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-store-lib/src/lib.rs
|
||||
crates/ksp-store-lib/src/store.rs
|
||||
crates/ksp-store-lib/tests/dependency_boundary.rs
|
||||
crates/ksp-store-lib/tests/hardening_completeness.rs
|
||||
crates/ksp-store-lib/tests/public_api.rs
|
||||
crates/ksp-store-postgres-lib/src/lib.rs
|
||||
crates/ksp-store-postgres-lib/src/raw_account.rs
|
||||
crates/ksp-store-postgres-lib/src/runtime.rs
|
||||
crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||
crates/ksp-store-postgres-lib/tests/postgres_raw_account_live.rs
|
||||
crates/ksp-store-postgres-lib/tests/public_api.rs
|
||||
crates/ksp-store-postgres-lib/unit_tests/raw_account.rs
|
||||
docs/plans/029-V0_3_8_STORE_DESK_PLAN.md
|
||||
docs/validation/025-V0_3_8_STORE_DESK.md
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
```text
|
||||
aucun
|
||||
```
|
||||
|
||||
## Validations d'assemblage
|
||||
|
||||
Contrôles exécutés avant emballage :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
KSP workspace Rust rule audit: clean
|
||||
Markdown table audit: clean (314 table(s), 698 file(s))
|
||||
pre.005 structural contract audit: clean
|
||||
workspace.package.version: 0.3.8-pre.5
|
||||
Store RAW capability implementations: 12
|
||||
PostgreSQL RAW capability implementations: 12
|
||||
RawAccount keyset OFFSET occurrences: 0
|
||||
RawAccount inspection OFFSET occurrences: 2
|
||||
delta scope: 1 ajout / 16 modifications / 0 suppression
|
||||
```
|
||||
|
||||
Le sandbox d'assemblage ne dispose pas de `cargo`/`rustfmt`; aucun gate Cargo de `pre.005` n'est donc revendiqué localement.
|
||||
|
||||
## Gate requis
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-store-postgres-lib
|
||||
cargo test -p ksp-store-lib
|
||||
cargo check -p ksp-store-lib --no-default-features
|
||||
cargo test -p ksp-store-api
|
||||
```
|
||||
|
||||
Live proof opt-in, uniquement sur base PostgreSQL dédiée vide :
|
||||
|
||||
```bash
|
||||
read -rsp 'Dedicated PostgreSQL URI: ' KSP_LIVE_PG_URI
|
||||
printf '\n'
|
||||
printf '%s\n' "$KSP_LIVE_PG_URI" | cargo test -p ksp-store-postgres-lib --test postgres_raw_account_live -- --ignored --nocapture
|
||||
unset KSP_LIVE_PG_URI
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/029-V0_3_8_STORE_DESK_PLAN.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# Plan v0.3.8 — Store Desk V1 RAW
|
||||
|
||||
@@ -572,7 +572,7 @@ Implémenter summary query, counts exacts, offset/limit checked et trait Tx dans
|
||||
|
||||
### pre.005 — inspection PostgreSQL RawAccountState
|
||||
|
||||
Même vertical slice pour Account : pubkey/slot filters, ordre canonique, data length sans data bytes, counts exacts, offset/limit checked, façade et canaris cross-family.
|
||||
Même vertical slice pour Account : filtre exact `pubkey`, range slot inclusif, ordre canonique `(slot, pubkey, state_hash)`, `data_length_bytes` via `OCTET_LENGTH(data)` sans sélectionner `data`, counts exacts et page `LIMIT/OFFSET` dans une seule instruction SQL par direction. `RawAccountStateInspectionRead` est implémenté sur PostgreSQL puis dispatché par `Store`, ce qui porte l'inventaire symétrique à 12 capabilities RAW. Les quatre statements cursor/keyset Account restent strictement OFFSET-free et ne sont jamais réécrits. Le live proof opt-in couvre filtre pubkey, counts, asc/desc, offset et page vide profonde.
|
||||
|
||||
### pre.006 — composite Config + lifecycle Store + Overview
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/025-V0_3_8_STORE_DESK.md -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 8 -->
|
||||
|
||||
# Validation v0.3.8 — Store Desk V1 RAW
|
||||
|
||||
@@ -358,4 +358,49 @@ unset KSP_LIVE_PG_URI
|
||||
```
|
||||
|
||||
Aucun SQL, contrat Store, runtime PostgreSQL ou comportement d'inspection de production n'est modifié par ce fix.
|
||||
## 20. Gate opérateur `pre.004-fix.001` et live proof PostgreSQL
|
||||
|
||||
Le rejeu opérateur sur `0.3.8-pre.4.fix.1` est entièrement vert :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
KSP workspace Rust rule audit: clean
|
||||
Markdown table audit: clean (314 table(s), 697 file(s))
|
||||
cargo check --workspace: PASS
|
||||
cargo clippy --workspace --all-targets: PASS
|
||||
cargo test -p ksp-store-postgres-lib: PASS
|
||||
cargo test -p ksp-store-lib: PASS
|
||||
cargo check -p ksp-store-lib --no-default-features: PASS
|
||||
cargo test -p ksp-store-api: PASS
|
||||
```
|
||||
|
||||
Le live proof `postgres_raw_transaction_live` a ensuite été exécuté sur une base PostgreSQL dédiée vide. Il atteint réellement PostgreSQL 17 et passe :
|
||||
|
||||
```text
|
||||
KSP Store RawTransaction live proof: server major 17
|
||||
test pre_009_real_postgres_raw_transaction_vertical_slice_is_atomic_concurrent_and_recoverable ... ok
|
||||
```
|
||||
|
||||
`pre.004` est donc fermé sans réserve et `pre.005` peut ouvrir uniquement le vertical slice `RawAccountStateInspectionRead`.
|
||||
|
||||
## 21. `pre.005` — inspection PostgreSQL RawAccountState + façade Store
|
||||
|
||||
- [X] `workspace.package.version = 0.3.8-pre.5` ;
|
||||
- [X] `PostgresBackend` implémente `RawAccountStateInspectionRead` ;
|
||||
- [X] `Store` implémente le même trait avec guard network avant dispatch ;
|
||||
- [X] les inventaires RAW Store/PostgreSQL passent de 11 à 12 et restent exactement symétriques ;
|
||||
- [X] chaque direction d'inspection Account utilise une seule instruction SQL regroupant `filtered_count`, `total_count` conditionnel et page LATERAL ;
|
||||
- [X] `filtered_items` applique le filtre exact `pubkey` optionnel et le range slot inclusif ;
|
||||
- [X] `total_items` compte toute la famille `ksp_raw_account_states` et réutilise `filtered_count` lorsqu'aucun filtre n'est actif ;
|
||||
- [X] l'ordre inspection est canonique `(slot, pubkey, state_hash)` en ascendant ou descendant ;
|
||||
- [X] la page random-access utilise `LIMIT/OFFSET` uniquement dans le chemin inspection ;
|
||||
- [X] limit/offset non représentables par PostgreSQL sont rejetés avant acquisition du pool ;
|
||||
- [X] les quatre statements cursor/keyset Account historiques restent sans `OFFSET` ;
|
||||
- [X] le SQL d'inspection ne sélectionne jamais `account_row.data` et projette seulement `OCTET_LENGTH(account_row.data)` ;
|
||||
- [X] `RawAccountStateSummary` est reconstruit avec reference, lamports, owner, executable, rent epoch et data length uniquement ;
|
||||
- [X] longueurs, widths et décimaux hostiles sont rejetés sans écho de valeur ;
|
||||
- [X] une page vide obtenue par offset profond conserve les counts exacts ;
|
||||
- [X] le live proof Account opt-in couvre filtre pubkey, counts, ordre asc/desc, offset, data length et page vide profonde ;
|
||||
- [X] aucune migration, table, index, capability destructive Account, UI Desk ou protocole DataTables n'est ajouté.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user