v0.3.1-pre.006
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Private home for backend-agnostic Store capability contracts.
|
||||
//!
|
||||
@@ -9,6 +9,7 @@
|
||||
//! outside `ksp-store-api`.
|
||||
|
||||
pub(crate) mod raw_account;
|
||||
pub(crate) mod raw_retention;
|
||||
pub(crate) mod raw_transaction;
|
||||
|
||||
/// Boxed async operation returned by object-safe Store capability contracts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_account.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Read capability for complete canonical RAW account states.
|
||||
///
|
||||
@@ -12,21 +12,31 @@ pub trait RawAccountStateRead: std::marker::Send + std::marker::Sync {
|
||||
&'a self,
|
||||
reference: &'a crate::RawAccountStateReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawAccountState>>>;
|
||||
|
||||
/// Lists deterministic account-state references using an opaque backend cursor.
|
||||
///
|
||||
/// The backend must not invent a KSP policy ceiling below the caller's
|
||||
/// request. A real backend/physical limitation may determine the page that
|
||||
/// can be served and continuation is represented by the opaque cursor.
|
||||
fn list_raw_account_states<'a>(
|
||||
&'a self,
|
||||
query: &'a crate::RawAccountStateQuery,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawAccountStateReference>>>;
|
||||
}
|
||||
|
||||
/// Write capability for complete RAW account-state acquisitions.
|
||||
///
|
||||
/// The account state and its acquisition observation form one logical
|
||||
/// persistence operation. An implementation must not leave one side durable if
|
||||
/// the other side fails. Detailed idempotence/conflict outcomes are introduced
|
||||
/// by `0.3.1-pre.006`; this tranche exposes only success/failure.
|
||||
/// the other side fails. Divergent content for one logical identity is a stable
|
||||
/// conflict error, never a silent overwrite.
|
||||
pub trait RawAccountStateWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Persists one complete RAW account state together with one observation atomically.
|
||||
fn persist_raw_account_acquisition<'a>(
|
||||
&'a self,
|
||||
state: crate::RawAccountState,
|
||||
observation: crate::RawAccountObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<()>>;
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||
}
|
||||
|
||||
/// Read capability for persisted RAW account-state observations.
|
||||
@@ -41,9 +51,11 @@ pub trait RawAccountObservationRead: std::marker::Send + std::marker::Sync {
|
||||
/// Write capability for an additional observation of an already persisted RAW account state.
|
||||
///
|
||||
/// This capability allows repeated HTTP/WS/gRPC acquisitions to be retained
|
||||
/// without resubmitting account bytes. The referenced state must already exist;
|
||||
/// detailed outcomes are deferred to `0.3.1-pre.006`.
|
||||
/// without resubmitting account bytes. The referenced state must already exist.
|
||||
pub trait RawAccountObservationWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Persists one additional acquisition observation for an existing RAW account state.
|
||||
fn record_raw_account_observation<'a>(&'a self, observation: crate::RawAccountObservation) -> crate::StoreApiFuture<'a, crate::Result<()>>;
|
||||
fn record_raw_account_observation<'a>(
|
||||
&'a self,
|
||||
observation: crate::RawAccountObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawObservationWriteOutcome>>;
|
||||
}
|
||||
|
||||
29
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
29
crates/ksp-store-api/src/capability/raw_retention.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_retention.rs
|
||||
// version: 1
|
||||
|
||||
/// Read capability for canonical RAW transaction retention metadata.
|
||||
pub trait RawTransactionRetentionRead: std::marker::Send + std::marker::Sync {
|
||||
/// Reads the logical retention state for one known transaction identity.
|
||||
fn get_raw_transaction_retention_state<'a>(
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawRetentionState>>>;
|
||||
|
||||
/// Reads the minimal durable tombstone when the transaction payload was purged.
|
||||
fn get_raw_transaction_tombstone<'a>(
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransactionTombstone>>>;
|
||||
}
|
||||
|
||||
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||
///
|
||||
/// This contract applies a transition selected by an external worker/job policy;
|
||||
/// the Store backend never decides by itself that higher processing is complete.
|
||||
pub trait RawTransactionRetentionWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Applies one atomic forward lifecycle transition chosen by the caller.
|
||||
fn transition_raw_transaction_retention<'a>(
|
||||
&'a self,
|
||||
transition: crate::RawTransactionRetentionTransition,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawRetentionWriteOutcome>>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/capability/raw_transaction.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Read capability for canonical RAW transactions.
|
||||
///
|
||||
@@ -12,21 +12,32 @@ pub trait RawTransactionRead: std::marker::Send + std::marker::Sync {
|
||||
&'a self,
|
||||
reference: &'a crate::RawTransactionReference,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<std::option::Option<crate::RawTransaction>>>;
|
||||
|
||||
/// Lists deterministic transaction references using an opaque backend cursor.
|
||||
///
|
||||
/// The backend must honor the caller-requested page limit unless a real
|
||||
/// backend/physical limitation prevents it. `ksp-store-api` imposes no
|
||||
/// arbitrary smaller result ceiling or executor policy.
|
||||
fn list_raw_transactions<'a>(
|
||||
&'a self,
|
||||
query: &'a crate::RawTransactionQuery,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawPage<crate::RawTransactionReference>>>;
|
||||
}
|
||||
|
||||
/// Write capability for canonical RAW transaction acquisitions.
|
||||
///
|
||||
/// The transaction and its acquisition observation form one logical persistence
|
||||
/// operation. An implementation must not leave one side durable if the other
|
||||
/// side fails. Detailed idempotence/conflict outcomes are introduced by
|
||||
/// `0.3.1-pre.006`; this tranche exposes only success/failure.
|
||||
/// side fails. Divergent content for one logical identity is a stable conflict
|
||||
/// error, never a silent overwrite.
|
||||
pub trait RawTransactionWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Persists one complete RAW transaction together with one observation atomically.
|
||||
fn persist_raw_transaction_acquisition<'a>(
|
||||
&'a self,
|
||||
transaction: crate::RawTransaction,
|
||||
observation: crate::RawTransactionObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<()>>;
|
||||
mode: crate::RawTransactionAcquisitionMode,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawAcquisitionWriteOutcome>>;
|
||||
}
|
||||
|
||||
/// Read capability for persisted RAW transaction observations.
|
||||
@@ -42,9 +53,11 @@ pub trait RawTransactionObservationRead: std::marker::Send + std::marker::Sync {
|
||||
///
|
||||
/// This capability exists so repeated acquisitions can be recorded without
|
||||
/// resubmitting the potentially large canonical transaction payload. The
|
||||
/// referenced transaction must already exist; detailed outcomes are deferred to
|
||||
/// `0.3.1-pre.006`.
|
||||
/// referenced transaction must already exist.
|
||||
pub trait RawTransactionObservationWrite: std::marker::Send + std::marker::Sync {
|
||||
/// Persists one additional acquisition observation for an existing RAW transaction.
|
||||
fn record_raw_transaction_observation<'a>(&'a self, observation: crate::RawTransactionObservation) -> crate::StoreApiFuture<'a, crate::Result<()>>;
|
||||
fn record_raw_transaction_observation<'a>(
|
||||
&'a self,
|
||||
observation: crate::RawTransactionObservation,
|
||||
) -> crate::StoreApiFuture<'a, crate::Result<crate::RawObservationWriteOutcome>>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// file: crates/ksp-store-api/src/error.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Error code used when a RAW write collides with divergent content for the same logical identity.
|
||||
pub const ERROR_CODE_RAW_CONFLICT: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_conflict");
|
||||
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
|
||||
pub const ERROR_CODE_RAW_MODEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_model_invalid");
|
||||
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
|
||||
pub const ERROR_CODE_RAW_PAYLOAD_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_payload_invalid");
|
||||
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||
pub const ERROR_CODE_RAW_PROVENANCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_provenance_invalid");
|
||||
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||
pub const ERROR_CODE_RAW_QUERY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_query_invalid");
|
||||
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||
pub const ERROR_CODE_RAW_RETENTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_api", "raw_retention_invalid");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/lib.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -30,6 +30,10 @@ pub use self::capability::raw_account::RawAccountObservationWrite;
|
||||
pub use self::capability::raw_account::RawAccountStateRead;
|
||||
/// Write capability for complete canonical RAW account-state acquisitions.
|
||||
pub use self::capability::raw_account::RawAccountStateWrite;
|
||||
/// Read capability for canonical RAW transaction retention metadata.
|
||||
pub use self::capability::raw_retention::RawTransactionRetentionRead;
|
||||
/// Write capability for policy-authorized RAW transaction retention transitions.
|
||||
pub use self::capability::raw_retention::RawTransactionRetentionWrite;
|
||||
/// Read capability for persisted RAW transaction observations.
|
||||
pub use self::capability::raw_transaction::RawTransactionObservationRead;
|
||||
/// Write capability for additional observations of already persisted RAW transactions.
|
||||
@@ -38,18 +42,48 @@ pub use self::capability::raw_transaction::RawTransactionObservationWrite;
|
||||
pub use self::capability::raw_transaction::RawTransactionRead;
|
||||
/// Write capability for canonical RAW transaction acquisitions.
|
||||
pub use self::capability::raw_transaction::RawTransactionWrite;
|
||||
/// Error code used when a RAW write collides with divergent content for the same logical identity.
|
||||
pub use self::error::ERROR_CODE_RAW_CONFLICT;
|
||||
/// Error code used when a RAW Store model violates one of its backend-agnostic invariants.
|
||||
pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;
|
||||
/// Error code used when a KSP-owned RAW persistence payload violates its format or admission contract.
|
||||
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
|
||||
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||
pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
|
||||
/// Error code used when one RAW query or cursor violates backend-agnostic query invariants.
|
||||
pub use self::error::ERROR_CODE_RAW_QUERY_INVALID;
|
||||
/// Error code used when a RAW retention transition violates the logical lifecycle contract.
|
||||
pub use self::error::ERROR_CODE_RAW_RETENTION_INVALID;
|
||||
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||
pub use self::model::raw_account::RawAccountObservation;
|
||||
/// Canonical complete N1 RAW account state independent from acquisition transport.
|
||||
pub use self::model::raw_account::RawAccountState;
|
||||
/// Durable backend-independent identity of one canonical RAW account state.
|
||||
pub use self::model::raw_account::RawAccountStateReference;
|
||||
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||
pub use self::model::raw_outcome::RawAcquisitionWriteOutcome;
|
||||
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||
pub use self::model::raw_outcome::RawEntityWriteOutcome;
|
||||
/// Outcome for one deterministic acquisition observation write.
|
||||
pub use self::model::raw_outcome::RawObservationWriteOutcome;
|
||||
/// Maximum opaque query cursor length admitted by the Store API.
|
||||
pub use self::model::raw_pagination::MAX_RAW_PAGE_CURSOR_BYTES;
|
||||
/// Backend-independent list query for complete canonical RAW account states.
|
||||
pub use self::model::raw_pagination::RawAccountStateQuery;
|
||||
/// One deterministic page of backend-independent Store results.
|
||||
pub use self::model::raw_pagination::RawPage;
|
||||
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||
pub use self::model::raw_pagination::RawPageCursor;
|
||||
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||
pub use self::model::raw_pagination::RawPageLimit;
|
||||
/// Opaque-cursor page request used by backend-independent list operations.
|
||||
pub use self::model::raw_pagination::RawPageRequest;
|
||||
/// Optional inclusive Solana slot bounds for one Store query.
|
||||
pub use self::model::raw_pagination::RawSlotRange;
|
||||
/// Deterministic traversal direction for Store list queries.
|
||||
pub use self::model::raw_pagination::RawSortDirection;
|
||||
/// Backend-independent list query for canonical RAW transactions.
|
||||
pub use self::model::raw_pagination::RawTransactionQuery;
|
||||
/// Maximum complete RAW account-data length admitted by the Store API.
|
||||
pub use self::model::raw_primitives::MAX_RAW_ACCOUNT_DATA_BYTES;
|
||||
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
||||
@@ -80,6 +114,16 @@ pub use self::model::raw_primitives::RawProvenanceCode;
|
||||
pub use self::model::raw_primitives::RawTimestamp;
|
||||
/// Canonical 64-byte Solana transaction signature used by Store identities.
|
||||
pub use self::model::raw_primitives::RawTransactionSignature;
|
||||
/// Logical availability state of one canonical RAW payload.
|
||||
pub use self::model::raw_retention::RawRetentionState;
|
||||
/// Outcome of one atomic RAW retention transition.
|
||||
pub use self::model::raw_retention::RawRetentionWriteOutcome;
|
||||
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||
pub use self::model::raw_retention::RawTransactionAcquisitionMode;
|
||||
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||
pub use self::model::raw_retention::RawTransactionRetentionTransition;
|
||||
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||
pub use self::model::raw_retention::RawTransactionTombstone;
|
||||
/// Canonical source-independent N1 RAW transaction persisted by Store backends.
|
||||
pub use self::model::raw_transaction::RawTransaction;
|
||||
/// Persistable acquisition observation linked to one canonical RAW transaction.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/src/model.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Private home for persistent Store models.
|
||||
//!
|
||||
@@ -9,5 +9,8 @@
|
||||
//! one or more models.
|
||||
|
||||
pub(crate) mod raw_account;
|
||||
pub(crate) mod raw_outcome;
|
||||
pub(crate) mod raw_pagination;
|
||||
pub(crate) mod raw_primitives;
|
||||
pub(crate) mod raw_retention;
|
||||
pub(crate) mod raw_transaction;
|
||||
|
||||
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
55
crates/ksp-store-api/src/model/raw_outcome.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_outcome.rs
|
||||
// version: 1
|
||||
|
||||
/// Outcome for one canonical RAW entity in an idempotent persistence operation.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawEntityWriteOutcome {
|
||||
/// The canonical RAW entity was inserted for the first time.
|
||||
Inserted,
|
||||
/// Identical canonical content was already durable.
|
||||
AlreadyPresent,
|
||||
/// A previously purged RAW entity was explicitly rehydrated.
|
||||
Rehydrated,
|
||||
/// Normal persistence skipped a durable purged tombstone.
|
||||
SkippedPurged,
|
||||
}
|
||||
|
||||
/// Outcome for one deterministic acquisition observation write.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawObservationWriteOutcome {
|
||||
/// The observation was inserted for the first time.
|
||||
Inserted,
|
||||
/// The same observation key already identified identical durable content.
|
||||
AlreadyPresent,
|
||||
/// No observation was recorded because the associated RAW entity was intentionally skipped.
|
||||
NotRecorded,
|
||||
}
|
||||
|
||||
/// Combined outcome of one atomic canonical RAW entity plus observation acquisition.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawAcquisitionWriteOutcome {
|
||||
entity: crate::RawEntityWriteOutcome,
|
||||
observation: crate::RawObservationWriteOutcome,
|
||||
}
|
||||
|
||||
impl RawAcquisitionWriteOutcome {
|
||||
/// Creates one backend-independent atomic acquisition outcome.
|
||||
#[must_use]
|
||||
pub const fn new(entity: crate::RawEntityWriteOutcome, observation: crate::RawObservationWriteOutcome) -> Self {
|
||||
return Self { entity, observation };
|
||||
}
|
||||
|
||||
/// Returns the canonical RAW entity write outcome.
|
||||
#[must_use]
|
||||
pub const fn entity(&self) -> crate::RawEntityWriteOutcome {
|
||||
return self.entity;
|
||||
}
|
||||
|
||||
/// Returns the acquisition observation write outcome.
|
||||
#[must_use]
|
||||
pub const fn observation(&self) -> crate::RawObservationWriteOutcome {
|
||||
return self.observation;
|
||||
}
|
||||
}
|
||||
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
267
crates/ksp-store-api/src/model/raw_pagination.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_pagination.rs
|
||||
// version: 1
|
||||
|
||||
/// Maximum opaque cursor length accepted by Store API queries.
|
||||
///
|
||||
/// This bounds untrusted token material only. It is not a policy limit on the
|
||||
/// number of rows/results a backend may return.
|
||||
pub const MAX_RAW_PAGE_CURSOR_BYTES: usize = 4 * 1024;
|
||||
|
||||
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct RawPageCursor(std::boxed::Box<[u8]>);
|
||||
|
||||
impl RawPageCursor {
|
||||
/// Creates one non-empty bounded opaque cursor.
|
||||
pub fn try_new(bytes: std::boxed::Box<[u8]>) -> crate::Result<Self> {
|
||||
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAGE_CURSOR_BYTES {
|
||||
return std::result::Result::Err(raw_query_error("cursor"));
|
||||
}
|
||||
return std::result::Result::Ok(Self(bytes));
|
||||
}
|
||||
|
||||
/// Returns the opaque cursor bytes unchanged.
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
return self.0.as_ref();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RawPageCursor {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter.debug_struct("RawPageCursor").field("len", &self.0.len()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawPageLimit(u64);
|
||||
|
||||
impl RawPageLimit {
|
||||
/// Creates one strictly positive requested page size.
|
||||
///
|
||||
/// `ksp-store-api` deliberately imposes no smaller functional maximum.
|
||||
/// Concrete backends may expose or return their real physical limitations.
|
||||
pub fn new(value: u64) -> crate::Result<Self> {
|
||||
if value == 0 {
|
||||
return std::result::Result::Err(raw_query_error("limit"));
|
||||
}
|
||||
return std::result::Result::Ok(Self(value));
|
||||
}
|
||||
|
||||
/// Returns the exact caller-requested item count.
|
||||
#[must_use]
|
||||
pub const fn get(&self) -> u64 {
|
||||
return self.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque-cursor page request used by backend-independent list operations.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawPageRequest {
|
||||
cursor: std::option::Option<crate::RawPageCursor>,
|
||||
limit: crate::RawPageLimit,
|
||||
}
|
||||
|
||||
impl RawPageRequest {
|
||||
/// Creates one first-page request.
|
||||
#[must_use]
|
||||
pub const fn first(limit: crate::RawPageLimit) -> Self {
|
||||
return Self { cursor: std::option::Option::None, limit };
|
||||
}
|
||||
|
||||
/// Creates one continuation-page request with an opaque backend cursor.
|
||||
#[must_use]
|
||||
pub const fn after(limit: crate::RawPageLimit, cursor: crate::RawPageCursor) -> Self {
|
||||
return Self { cursor: std::option::Option::Some(cursor), limit };
|
||||
}
|
||||
|
||||
/// Returns the opaque continuation cursor when present.
|
||||
#[must_use]
|
||||
pub fn cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||
return self.cursor.as_ref();
|
||||
}
|
||||
|
||||
/// Returns the exact caller-requested page size.
|
||||
#[must_use]
|
||||
pub const fn limit(&self) -> crate::RawPageLimit {
|
||||
return self.limit;
|
||||
}
|
||||
}
|
||||
|
||||
/// One deterministic page of backend-independent Store results.
|
||||
#[derive(Debug)]
|
||||
pub struct RawPage<T> {
|
||||
items: std::vec::Vec<T>,
|
||||
next_cursor: std::option::Option<crate::RawPageCursor>,
|
||||
}
|
||||
|
||||
impl<T> RawPage<T> {
|
||||
/// Creates one result page from backend-provided items and optional continuation cursor.
|
||||
#[must_use]
|
||||
pub fn new(items: std::vec::Vec<T>, next_cursor: std::option::Option<crate::RawPageCursor>) -> Self {
|
||||
return Self { items, next_cursor };
|
||||
}
|
||||
|
||||
/// Returns the current page items.
|
||||
#[must_use]
|
||||
pub fn items(&self) -> &[T] {
|
||||
return self.items.as_slice();
|
||||
}
|
||||
|
||||
/// Consumes the page and returns its items.
|
||||
#[must_use]
|
||||
pub fn into_items(self) -> std::vec::Vec<T> {
|
||||
return self.items;
|
||||
}
|
||||
|
||||
/// Returns the next opaque cursor when more results are available.
|
||||
#[must_use]
|
||||
pub fn next_cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
||||
return self.next_cursor.as_ref();
|
||||
}
|
||||
}
|
||||
|
||||
/// Deterministic traversal direction for Store list queries.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawSortDirection {
|
||||
/// Oldest/lower ordered keys first.
|
||||
Ascending,
|
||||
/// Newest/higher ordered keys first.
|
||||
Descending,
|
||||
}
|
||||
|
||||
/// Optional inclusive Solana slot bounds for one Store query.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct RawSlotRange {
|
||||
end_inclusive: std::option::Option<u64>,
|
||||
start_inclusive: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl RawSlotRange {
|
||||
/// Creates one optional inclusive slot range and rejects reversed bounds.
|
||||
pub fn new(start_inclusive: std::option::Option<u64>, end_inclusive: std::option::Option<u64>) -> crate::Result<Self> {
|
||||
if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start_inclusive, end_inclusive) {
|
||||
if start > end {
|
||||
return std::result::Result::Err(raw_query_error("slot_range"));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(Self { end_inclusive, start_inclusive });
|
||||
}
|
||||
|
||||
/// Returns the optional inclusive ending slot.
|
||||
#[must_use]
|
||||
pub const fn end_inclusive(&self) -> std::option::Option<u64> {
|
||||
return self.end_inclusive;
|
||||
}
|
||||
|
||||
/// Returns the optional inclusive starting slot.
|
||||
#[must_use]
|
||||
pub const fn start_inclusive(&self) -> std::option::Option<u64> {
|
||||
return self.start_inclusive;
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend-independent list query for canonical RAW transactions.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionQuery {
|
||||
direction: crate::RawSortDirection,
|
||||
network: crate::RawNetworkId,
|
||||
page: crate::RawPageRequest,
|
||||
slots: crate::RawSlotRange,
|
||||
}
|
||||
|
||||
impl RawTransactionQuery {
|
||||
/// Creates one deterministic transaction-reference query.
|
||||
#[must_use]
|
||||
pub fn new(network: crate::RawNetworkId, slots: crate::RawSlotRange, direction: crate::RawSortDirection, page: crate::RawPageRequest) -> Self {
|
||||
return Self { direction, network, page, slots };
|
||||
}
|
||||
|
||||
/// Returns the requested deterministic traversal direction.
|
||||
#[must_use]
|
||||
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||
return self.direction;
|
||||
}
|
||||
|
||||
/// Returns the required logical network scope.
|
||||
#[must_use]
|
||||
pub fn network(&self) -> &crate::RawNetworkId {
|
||||
return &self.network;
|
||||
}
|
||||
|
||||
/// Returns pagination inputs without exposing backend cursor contents.
|
||||
#[must_use]
|
||||
pub fn page(&self) -> &crate::RawPageRequest {
|
||||
return &self.page;
|
||||
}
|
||||
|
||||
/// Returns optional inclusive slot bounds.
|
||||
#[must_use]
|
||||
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||
return self.slots;
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend-independent list query for complete canonical RAW account states.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawAccountStateQuery {
|
||||
direction: crate::RawSortDirection,
|
||||
network: crate::RawNetworkId,
|
||||
page: crate::RawPageRequest,
|
||||
pubkey: std::option::Option<crate::Pubkey>,
|
||||
slots: crate::RawSlotRange,
|
||||
}
|
||||
|
||||
impl RawAccountStateQuery {
|
||||
/// Creates one deterministic account-state-reference query.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
network: crate::RawNetworkId,
|
||||
pubkey: std::option::Option<crate::Pubkey>,
|
||||
slots: crate::RawSlotRange,
|
||||
direction: crate::RawSortDirection,
|
||||
page: crate::RawPageRequest,
|
||||
) -> Self {
|
||||
return Self { direction, network, page, pubkey, slots };
|
||||
}
|
||||
|
||||
/// Returns the requested deterministic traversal direction.
|
||||
#[must_use]
|
||||
pub const fn direction(&self) -> crate::RawSortDirection {
|
||||
return self.direction;
|
||||
}
|
||||
|
||||
/// Returns the required logical network scope.
|
||||
#[must_use]
|
||||
pub fn network(&self) -> &crate::RawNetworkId {
|
||||
return &self.network;
|
||||
}
|
||||
|
||||
/// Returns pagination inputs without exposing backend cursor contents.
|
||||
#[must_use]
|
||||
pub fn page(&self) -> &crate::RawPageRequest {
|
||||
return &self.page;
|
||||
}
|
||||
|
||||
/// Returns an optional account-address restriction.
|
||||
#[must_use]
|
||||
pub fn pubkey(&self) -> std::option::Option<&crate::Pubkey> {
|
||||
return self.pubkey.as_ref();
|
||||
}
|
||||
|
||||
/// Returns optional inclusive slot bounds.
|
||||
#[must_use]
|
||||
pub const fn slots(&self) -> crate::RawSlotRange {
|
||||
return self.slots;
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_query_error(field: &'static str) -> crate::Error {
|
||||
return crate::Error::new(crate::ERROR_CODE_RAW_QUERY_INVALID, "invalid backend-agnostic RAW Store query").with_context("field", field);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../../unit_tests/model/raw_pagination.rs"]
|
||||
mod tests;
|
||||
152
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
152
crates/ksp-store-api/src/model/raw_retention.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
// file: crates/ksp-store-api/src/model/raw_retention.rs
|
||||
// version: 1
|
||||
|
||||
/// Logical availability state of one canonical RAW payload.
|
||||
///
|
||||
/// These states describe payload retention only. They never prove that a
|
||||
/// STRUCTURAL/DECODED/DOMAIN processor has completed.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawRetentionState {
|
||||
/// Complete canonical RAW payload is retained in ordinary Store availability.
|
||||
Full,
|
||||
/// Complete logical RAW content is retained in a compacted representation.
|
||||
Compacted,
|
||||
/// RAW content has been moved to an archive managed outside the hot Store path.
|
||||
Archived,
|
||||
/// RAW payload is absent while a durable tombstone remains.
|
||||
Purged,
|
||||
}
|
||||
|
||||
/// Explicit write mode for canonical RAW transaction acquisitions.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawTransactionAcquisitionMode {
|
||||
/// Normal acquisition must skip an existing purged tombstone.
|
||||
Normal,
|
||||
/// Explicitly allow an existing purged transaction to be rehydrated.
|
||||
ForceRehydrate,
|
||||
}
|
||||
|
||||
/// Minimal durable identity retained after a canonical RAW transaction payload is purged.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionTombstone {
|
||||
content_hash: crate::RawContentHash,
|
||||
format_id: crate::RawFormatId,
|
||||
format_version: u32,
|
||||
reference: crate::RawTransactionReference,
|
||||
slot: u64,
|
||||
}
|
||||
|
||||
impl RawTransactionTombstone {
|
||||
/// Creates one minimal transaction tombstone from canonical RAW identity metadata.
|
||||
pub fn try_new(
|
||||
reference: crate::RawTransactionReference,
|
||||
slot: u64,
|
||||
format_id: crate::RawFormatId,
|
||||
format_version: u32,
|
||||
content_hash: crate::RawContentHash,
|
||||
) -> crate::Result<Self> {
|
||||
if format_version == 0 {
|
||||
return std::result::Result::Err(raw_retention_error("format_version"));
|
||||
}
|
||||
return std::result::Result::Ok(Self { content_hash, format_id, format_version, reference, slot });
|
||||
}
|
||||
|
||||
/// Returns the canonical content hash retained after purge.
|
||||
#[must_use]
|
||||
pub const fn content_hash(&self) -> crate::RawContentHash {
|
||||
return self.content_hash;
|
||||
}
|
||||
|
||||
/// Returns the KSP-owned canonical RAW format identifier retained after purge.
|
||||
#[must_use]
|
||||
pub fn format_id(&self) -> &crate::RawFormatId {
|
||||
return &self.format_id;
|
||||
}
|
||||
|
||||
/// Returns the KSP-owned canonical RAW format version retained after purge.
|
||||
#[must_use]
|
||||
pub const fn format_version(&self) -> u32 {
|
||||
return self.format_version;
|
||||
}
|
||||
|
||||
/// Returns the durable backend-independent transaction identity.
|
||||
#[must_use]
|
||||
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||
return &self.reference;
|
||||
}
|
||||
|
||||
/// Returns the known transaction slot retained after purge.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
}
|
||||
|
||||
/// Requested compare-and-transition operation for one RAW transaction retention state.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RawTransactionRetentionTransition {
|
||||
expected: crate::RawRetentionState,
|
||||
reference: crate::RawTransactionReference,
|
||||
target: crate::RawRetentionState,
|
||||
}
|
||||
|
||||
impl RawTransactionRetentionTransition {
|
||||
/// Creates one allowed forward retention transition.
|
||||
///
|
||||
/// Rehydration from `Purged` is deliberately excluded and uses
|
||||
/// [`crate::RawTransactionAcquisitionMode::ForceRehydrate`] instead.
|
||||
pub fn try_new(reference: crate::RawTransactionReference, expected: crate::RawRetentionState, target: crate::RawRetentionState) -> crate::Result<Self> {
|
||||
if !allowed_transition(expected, target) {
|
||||
return std::result::Result::Err(raw_retention_error("transition"));
|
||||
}
|
||||
return std::result::Result::Ok(Self { expected, reference, target });
|
||||
}
|
||||
|
||||
/// Returns the state the caller expects before applying the transition.
|
||||
#[must_use]
|
||||
pub const fn expected(&self) -> crate::RawRetentionState {
|
||||
return self.expected;
|
||||
}
|
||||
|
||||
/// Returns the durable transaction identity targeted by the transition.
|
||||
#[must_use]
|
||||
pub fn reference(&self) -> &crate::RawTransactionReference {
|
||||
return &self.reference;
|
||||
}
|
||||
|
||||
/// Returns the requested forward retention state.
|
||||
#[must_use]
|
||||
pub const fn target(&self) -> crate::RawRetentionState {
|
||||
return self.target;
|
||||
}
|
||||
}
|
||||
|
||||
/// Outcome of one atomic RAW retention transition.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum RawRetentionWriteOutcome {
|
||||
/// The requested forward transition was applied.
|
||||
Applied,
|
||||
/// The entity was already at the requested target state.
|
||||
AlreadyAtTarget,
|
||||
}
|
||||
|
||||
fn allowed_transition(expected: crate::RawRetentionState, target: crate::RawRetentionState) -> bool {
|
||||
return matches!(
|
||||
(expected, target),
|
||||
(crate::RawRetentionState::Full, crate::RawRetentionState::Compacted)
|
||||
| (crate::RawRetentionState::Full, crate::RawRetentionState::Archived)
|
||||
| (crate::RawRetentionState::Compacted, crate::RawRetentionState::Archived)
|
||||
| (crate::RawRetentionState::Archived, crate::RawRetentionState::Purged)
|
||||
);
|
||||
}
|
||||
|
||||
fn raw_retention_error(field: &'static str) -> crate::Error {
|
||||
return crate::Error::new(crate::ERROR_CODE_RAW_RETENTION_INVALID, "invalid RAW retention lifecycle request").with_context("field", field);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../../unit_tests/model/raw_retention.rs"]
|
||||
mod tests;
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Dependency canaries for the Store API RAW foundation.
|
||||
|
||||
#[test]
|
||||
fn pre_005_manifest_keeps_exact_core_only_runtime_dependency() {
|
||||
fn pre_006_manifest_keeps_exact_core_only_runtime_dependency() {
|
||||
let manifest = include_str!("../Cargo.toml");
|
||||
let dependencies_tail = manifest.split("[dependencies]").nth(1);
|
||||
assert!(dependencies_tail.is_some(), "Store API dependencies section must exist");
|
||||
@@ -49,24 +49,45 @@ fn pre_005_manifest_keeps_exact_core_only_runtime_dependency() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_source_boundary_keeps_models_and_capabilities_backend_free() {
|
||||
fn pre_006_source_boundary_keeps_models_and_capabilities_backend_free() {
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
let model_home = include_str!("../src/model.rs");
|
||||
let raw_account = include_str!("../src/model/raw_account.rs");
|
||||
let raw_outcome = include_str!("../src/model/raw_outcome.rs");
|
||||
let raw_pagination = include_str!("../src/model/raw_pagination.rs");
|
||||
let raw_primitives = include_str!("../src/model/raw_primitives.rs");
|
||||
let raw_retention = include_str!("../src/model/raw_retention.rs");
|
||||
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
|
||||
let capability_home = include_str!("../src/capability.rs");
|
||||
let raw_account_capability = include_str!("../src/capability/raw_account.rs");
|
||||
let raw_retention_capability = include_str!("../src/capability/raw_retention.rs");
|
||||
let raw_transaction_capability = include_str!("../src/capability/raw_transaction.rs");
|
||||
assert!(crate_root.contains("mod capability;"));
|
||||
assert!(crate_root.contains("mod error;"));
|
||||
assert!(crate_root.contains("mod model;"));
|
||||
assert!(model_home.contains("raw_account"));
|
||||
assert!(model_home.contains("raw_outcome"));
|
||||
assert!(model_home.contains("raw_pagination"));
|
||||
assert!(model_home.contains("raw_primitives"));
|
||||
assert!(model_home.contains("raw_retention"));
|
||||
assert!(model_home.contains("raw_transaction"));
|
||||
assert!(capability_home.contains("raw_account"));
|
||||
assert!(capability_home.contains("raw_retention"));
|
||||
assert!(capability_home.contains("raw_transaction"));
|
||||
for source in [crate_root, model_home, raw_account, raw_primitives, raw_transaction, capability_home, raw_account_capability, raw_transaction_capability] {
|
||||
for source in [
|
||||
crate_root,
|
||||
model_home,
|
||||
raw_account,
|
||||
raw_outcome,
|
||||
raw_pagination,
|
||||
raw_primitives,
|
||||
raw_retention,
|
||||
raw_transaction,
|
||||
capability_home,
|
||||
raw_account_capability,
|
||||
raw_retention_capability,
|
||||
raw_transaction_capability,
|
||||
] {
|
||||
for forbidden in [
|
||||
"ksp_store_lib",
|
||||
"ksp_store_postgres_lib",
|
||||
@@ -85,12 +106,14 @@ fn pre_005_source_boundary_keeps_models_and_capabilities_backend_free() {
|
||||
}
|
||||
assert!(!raw_transaction.contains("RawLog"));
|
||||
for forbidden in ["TransactionStatusObservation", "RawLogNotification", "RawSlotEvent", "RawVoteEvent", "RawBlock", "YellowstoneEntry"] {
|
||||
assert!(!crate_root.contains(forbidden), "deferred pre.005 model leaked into Store API surface: {forbidden}");
|
||||
assert!(!crate_root.contains(forbidden), "deferred pre.006 model leaked into Store API surface: {forbidden}");
|
||||
}
|
||||
assert!(raw_transaction_capability.contains("trait RawTransactionRead"));
|
||||
assert!(raw_transaction_capability.contains("trait RawTransactionWrite"));
|
||||
assert!(raw_transaction_capability.contains("trait RawTransactionObservationRead"));
|
||||
assert!(raw_transaction_capability.contains("trait RawTransactionObservationWrite"));
|
||||
assert!(raw_retention_capability.contains("trait RawTransactionRetentionRead"));
|
||||
assert!(raw_retention_capability.contains("trait RawTransactionRetentionWrite"));
|
||||
assert!(raw_account_capability.contains("trait RawAccountStateRead"));
|
||||
assert!(raw_account_capability.contains("trait RawAccountStateWrite"));
|
||||
assert!(raw_account_capability.contains("trait RawAccountObservationRead"));
|
||||
@@ -98,8 +121,11 @@ fn pre_005_source_boundary_keeps_models_and_capabilities_backend_free() {
|
||||
for forbidden in ["trait StoreBackend", "trait Store", "PostgresStore", "MySqlStore", "Arc<dyn"] {
|
||||
assert!(!capability_home.contains(forbidden));
|
||||
assert!(!raw_account_capability.contains(forbidden));
|
||||
assert!(!raw_retention_capability.contains(forbidden));
|
||||
assert!(!raw_transaction_capability.contains(forbidden));
|
||||
}
|
||||
assert!(raw_pagination.contains("u64::MAX") == false);
|
||||
assert!(!raw_pagination.contains("MAX_RAW_PAGE_ITEMS"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/tests/external_backend.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! External-implementation canary for object-safe Store API capabilities.
|
||||
|
||||
@@ -15,6 +15,16 @@ impl ksp_store_api::RawTransactionRead for ExternalMemoryBackend {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
});
|
||||
}
|
||||
|
||||
fn list_raw_transactions<'a>(
|
||||
&'a self,
|
||||
query: &'a ksp_store_api::RawTransactionQuery,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawPage<ksp_store_api::RawTransactionReference>>> {
|
||||
let _ = query;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(ksp_store_api::RawPage::new(std::vec::Vec::new(), std::option::Option::None));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionWrite for ExternalMemoryBackend {
|
||||
@@ -22,11 +32,16 @@ impl ksp_store_api::RawTransactionWrite for ExternalMemoryBackend {
|
||||
&'a self,
|
||||
transaction: ksp_store_api::RawTransaction,
|
||||
observation: ksp_store_api::RawTransactionObservation,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
|
||||
mode: ksp_store_api::RawTransactionAcquisitionMode,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawAcquisitionWriteOutcome>> {
|
||||
let _ = transaction;
|
||||
let _ = observation;
|
||||
let _ = mode;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(());
|
||||
return std::result::Result::Ok(ksp_store_api::RawAcquisitionWriteOutcome::new(
|
||||
ksp_store_api::RawEntityWriteOutcome::Inserted,
|
||||
ksp_store_api::RawObservationWriteOutcome::Inserted,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -47,10 +62,10 @@ impl ksp_store_api::RawTransactionObservationWrite for ExternalMemoryBackend {
|
||||
fn record_raw_transaction_observation<'a>(
|
||||
&'a self,
|
||||
observation: ksp_store_api::RawTransactionObservation,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawObservationWriteOutcome>> {
|
||||
let _ = observation;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(());
|
||||
return std::result::Result::Ok(ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -65,6 +80,16 @@ impl ksp_store_api::RawAccountStateRead for ExternalMemoryBackend {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
});
|
||||
}
|
||||
|
||||
fn list_raw_account_states<'a>(
|
||||
&'a self,
|
||||
query: &'a ksp_store_api::RawAccountStateQuery,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawPage<ksp_store_api::RawAccountStateReference>>> {
|
||||
let _ = query;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(ksp_store_api::RawPage::new(std::vec::Vec::new(), std::option::Option::None));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawAccountStateWrite for ExternalMemoryBackend {
|
||||
@@ -72,11 +97,14 @@ impl ksp_store_api::RawAccountStateWrite for ExternalMemoryBackend {
|
||||
&'a self,
|
||||
state: ksp_store_api::RawAccountState,
|
||||
observation: ksp_store_api::RawAccountObservation,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawAcquisitionWriteOutcome>> {
|
||||
let _ = state;
|
||||
let _ = observation;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(());
|
||||
return std::result::Result::Ok(ksp_store_api::RawAcquisitionWriteOutcome::new(
|
||||
ksp_store_api::RawEntityWriteOutcome::Inserted,
|
||||
ksp_store_api::RawObservationWriteOutcome::Inserted,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -97,16 +125,50 @@ impl ksp_store_api::RawAccountObservationWrite for ExternalMemoryBackend {
|
||||
fn record_raw_account_observation<'a>(
|
||||
&'a self,
|
||||
observation: ksp_store_api::RawAccountObservation,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<()>> {
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawObservationWriteOutcome>> {
|
||||
let _ = observation;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(());
|
||||
return std::result::Result::Ok(ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionRetentionRead for ExternalMemoryBackend {
|
||||
fn get_raw_transaction_retention_state<'a>(
|
||||
&'a self,
|
||||
reference: &'a ksp_store_api::RawTransactionReference,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawRetentionState>>> {
|
||||
let _ = reference;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_raw_transaction_tombstone<'a>(
|
||||
&'a self,
|
||||
reference: &'a ksp_store_api::RawTransactionReference,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<std::option::Option<ksp_store_api::RawTransactionTombstone>>> {
|
||||
let _ = reference;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_store_api::RawTransactionRetentionWrite for ExternalMemoryBackend {
|
||||
fn transition_raw_transaction_retention<'a>(
|
||||
&'a self,
|
||||
transition: ksp_store_api::RawTransactionRetentionTransition,
|
||||
) -> ksp_store_api::StoreApiFuture<'a, ksp_store_api::Result<ksp_store_api::RawRetentionWriteOutcome>> {
|
||||
let _ = transition;
|
||||
return std::boxed::Box::pin(async {
|
||||
return std::result::Result::Ok(ksp_store_api::RawRetentionWriteOutcome::Applied);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_external_backend_implements_each_capability_without_store_runtime_crate() {
|
||||
fn pre_006_external_backend_implements_each_capability_without_store_runtime_crate() {
|
||||
let backend = ExternalMemoryBackend;
|
||||
let transaction_read: &dyn ksp_store_api::RawTransactionRead = &backend;
|
||||
let transaction_write: &dyn ksp_store_api::RawTransactionWrite = &backend;
|
||||
@@ -116,6 +178,8 @@ fn pre_005_external_backend_implements_each_capability_without_store_runtime_cra
|
||||
let account_write: &dyn ksp_store_api::RawAccountStateWrite = &backend;
|
||||
let account_observation_read: &dyn ksp_store_api::RawAccountObservationRead = &backend;
|
||||
let account_observation_write: &dyn ksp_store_api::RawAccountObservationWrite = &backend;
|
||||
let retention_read: &dyn ksp_store_api::RawTransactionRetentionRead = &backend;
|
||||
let retention_write: &dyn ksp_store_api::RawTransactionRetentionWrite = &backend;
|
||||
let _ = transaction_read;
|
||||
let _ = transaction_write;
|
||||
let _ = transaction_observation_read;
|
||||
@@ -124,5 +188,7 @@ fn pre_005_external_backend_implements_each_capability_without_store_runtime_cra
|
||||
let _ = account_write;
|
||||
let _ = account_observation_read;
|
||||
let _ = account_observation_write;
|
||||
let _ = retention_read;
|
||||
let _ = retention_write;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-api/tests/public_api.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Integration canaries for the public `ksp-store-api` surface.
|
||||
|
||||
@@ -146,3 +146,36 @@ fn public_pre_005_capabilities_are_available_from_crate_root_and_dyn_compatible(
|
||||
assert!(account_observation_write.is_none());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_pre_006_query_outcome_and_retention_contracts_are_available_from_crate_root() {
|
||||
let limit = match ksp_store_api::RawPageLimit::new(5_000_000) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(limit.get(), 5_000_000);
|
||||
assert!(ksp_store_api::RawPageLimit::new(u64::MAX).is_ok());
|
||||
let slots = match ksp_store_api::RawSlotRange::new(std::option::Option::Some(1), std::option::Option::Some(2)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let query =
|
||||
ksp_store_api::RawTransactionQuery::new(network, slots, ksp_store_api::RawSortDirection::Ascending, ksp_store_api::RawPageRequest::first(limit));
|
||||
assert_eq!(query.page().limit().get(), 5_000_000);
|
||||
let outcome =
|
||||
ksp_store_api::RawAcquisitionWriteOutcome::new(ksp_store_api::RawEntityWriteOutcome::Inserted, ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||
assert_eq!(outcome.entity(), ksp_store_api::RawEntityWriteOutcome::Inserted);
|
||||
assert_eq!(outcome.observation(), ksp_store_api::RawObservationWriteOutcome::Inserted);
|
||||
let retention_read: std::option::Option<&dyn ksp_store_api::RawTransactionRetentionRead> = std::option::Option::None;
|
||||
let retention_write: std::option::Option<&dyn ksp_store_api::RawTransactionRetentionWrite> = std::option::Option::None;
|
||||
assert!(retention_read.is_none());
|
||||
assert!(retention_write.is_none());
|
||||
assert_eq!(ksp_store_api::ERROR_CODE_RAW_CONFLICT.domain(), "store_api");
|
||||
assert_eq!(ksp_store_api::ERROR_CODE_RAW_QUERY_INVALID.domain(), "store_api");
|
||||
assert_eq!(ksp_store_api::ERROR_CODE_RAW_RETENTION_INVALID.domain(), "store_api");
|
||||
return;
|
||||
}
|
||||
|
||||
54
crates/ksp-store-api/unit_tests/model/raw_pagination.rs
Normal file
54
crates/ksp-store-api/unit_tests/model/raw_pagination.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_pagination.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn page_limit_rejects_only_zero_and_keeps_no_ksp_policy_maximum() {
|
||||
assert!(crate::RawPageLimit::new(0).is_err());
|
||||
let maximum = crate::RawPageLimit::new(u64::MAX);
|
||||
assert!(maximum.is_ok());
|
||||
let maximum = match maximum {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(maximum.get(), u64::MAX);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_is_opaque_bounded_and_debug_does_not_render_bytes() {
|
||||
assert!(crate::RawPageCursor::try_new(std::boxed::Box::new([])).is_err());
|
||||
assert!(crate::RawPageCursor::try_new(vec![7_u8; crate::MAX_RAW_PAGE_CURSOR_BYTES + 1].into_boxed_slice()).is_err());
|
||||
let cursor = crate::RawPageCursor::try_new(vec![0xAB_u8; 8].into_boxed_slice());
|
||||
assert!(cursor.is_ok());
|
||||
let cursor = match cursor {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let debug = format!("{cursor:?}");
|
||||
assert!(debug.contains("len"));
|
||||
assert!(!debug.contains("171"));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_query_preserves_caller_limit_range_direction_and_network() {
|
||||
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let slots = match crate::RawSlotRange::new(std::option::Option::Some(10), std::option::Option::Some(20)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let limit = match crate::RawPageLimit::new(5_000_000) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let query = crate::RawTransactionQuery::new(network, slots, crate::RawSortDirection::Ascending, crate::RawPageRequest::first(limit));
|
||||
assert_eq!(query.network().as_str(), "mainnet-beta");
|
||||
assert_eq!(query.slots().start_inclusive(), std::option::Option::Some(10));
|
||||
assert_eq!(query.page().limit().get(), 5_000_000);
|
||||
assert_eq!(query.direction(), crate::RawSortDirection::Ascending);
|
||||
assert!(crate::RawSlotRange::new(std::option::Option::Some(20), std::option::Option::Some(10)).is_err());
|
||||
return;
|
||||
}
|
||||
53
crates/ksp-store-api/unit_tests/model/raw_retention.rs
Normal file
53
crates/ksp-store-api/unit_tests/model/raw_retention.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
// file: crates/ksp-store-api/unit_tests/model/raw_retention.rs
|
||||
// version: 1
|
||||
|
||||
fn reference() -> std::option::Option<crate::RawTransactionReference> {
|
||||
let network = match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::option::Option::None,
|
||||
};
|
||||
return std::option::Option::Some(crate::RawTransactionReference::new(network, crate::RawTransactionSignature::new([3_u8; 64])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retention_transitions_are_forward_only_and_force_rehydrate_is_separate() {
|
||||
let reference = match reference() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Full, crate::RawRetentionState::Compacted).is_ok());
|
||||
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Full, crate::RawRetentionState::Archived).is_ok());
|
||||
assert!(
|
||||
crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Compacted, crate::RawRetentionState::Archived).is_ok()
|
||||
);
|
||||
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Archived, crate::RawRetentionState::Purged).is_ok());
|
||||
assert!(crate::RawTransactionRetentionTransition::try_new(reference.clone(), crate::RawRetentionState::Purged, crate::RawRetentionState::Full).is_err());
|
||||
assert!(crate::RawTransactionRetentionTransition::try_new(reference, crate::RawRetentionState::Full, crate::RawRetentionState::Purged).is_err());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tombstone_keeps_minimal_identity_without_raw_payload() {
|
||||
let reference = match reference() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let tombstone = crate::RawTransactionTombstone::try_new(reference, 99, format, 1, crate::RawContentHash::new([4_u8; 32]));
|
||||
assert!(tombstone.is_ok());
|
||||
let tombstone = match tombstone {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(tombstone.slot(), 99);
|
||||
assert_eq!(tombstone.format_version(), 1);
|
||||
let second_format = match crate::RawFormatId::new("ksp.solana.raw_transaction".to_owned()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert!(crate::RawTransactionTombstone::try_new(tombstone.reference().clone(), tombstone.slot(), second_format, 0, tombstone.content_hash()).is_err());
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user