v0.3.1-pre.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 325
|
# version: 326
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-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-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-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-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.1-pre.3-fix.1"
|
version = "0.3.1-pre.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/lib.rs
|
// file: crates/ksp-store-api/src/lib.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -26,6 +26,14 @@ pub use self::error::ERROR_CODE_RAW_MODEL_INVALID;
|
|||||||
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
|
pub use self::error::ERROR_CODE_RAW_PAYLOAD_INVALID;
|
||||||
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
/// Error code used when acquisition provenance is malformed, unsafe or internally inconsistent.
|
||||||
pub use self::error::ERROR_CODE_RAW_PROVENANCE_INVALID;
|
pub use self::error::ERROR_CODE_RAW_PROVENANCE_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;
|
||||||
|
/// 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.
|
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
||||||
pub use self::model::raw_primitives::MAX_RAW_CODE_BYTES;
|
pub use self::model::raw_primitives::MAX_RAW_CODE_BYTES;
|
||||||
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
|
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/src/model.rs
|
// file: crates/ksp-store-api/src/model.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Private home for persistent Store models.
|
//! Private home for persistent Store models.
|
||||||
//!
|
//!
|
||||||
@@ -8,5 +8,6 @@
|
|||||||
//! backend capabilities remain separate even when one capability operates on
|
//! backend capabilities remain separate even when one capability operates on
|
||||||
//! one or more models.
|
//! one or more models.
|
||||||
|
|
||||||
|
pub(crate) mod raw_account;
|
||||||
pub(crate) mod raw_primitives;
|
pub(crate) mod raw_primitives;
|
||||||
pub(crate) mod raw_transaction;
|
pub(crate) mod raw_transaction;
|
||||||
|
|||||||
231
crates/ksp-store-api/src/model/raw_account.rs
Normal file
231
crates/ksp-store-api/src/model/raw_account.rs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// file: crates/ksp-store-api/src/model/raw_account.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Durable backend-independent identity of one canonical RAW account state.
|
||||||
|
///
|
||||||
|
/// The content hash is part of the identity because one account can be written more than once
|
||||||
|
/// inside the same slot while standard HTTP/WebSocket surfaces do not expose Yellowstone's
|
||||||
|
/// `write_version`. Multiple observations of the same complete state therefore converge on the
|
||||||
|
/// same reference without making a provider-specific write ordinal part of the common model.
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct RawAccountStateReference {
|
||||||
|
network: crate::RawNetworkId,
|
||||||
|
pubkey: ksp_core_lib::Pubkey,
|
||||||
|
slot: u64,
|
||||||
|
state_hash: crate::RawContentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountStateReference {
|
||||||
|
/// Creates one durable account-state identity from network, account, slot and canonical state digest.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(network: crate::RawNetworkId, pubkey: ksp_core_lib::Pubkey, slot: u64, state_hash: crate::RawContentHash) -> Self {
|
||||||
|
return Self { network, pubkey, slot, state_hash };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the logical Solana network/cluster identifier.
|
||||||
|
#[must_use]
|
||||||
|
pub fn network(&self) -> &crate::RawNetworkId {
|
||||||
|
return &self.network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account public key.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||||
|
return &self.pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slot associated with this complete account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn slot(&self) -> u64 {
|
||||||
|
return self.slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the producer-supplied digest of the complete canonical account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn state_hash(&self) -> crate::RawContentHash {
|
||||||
|
return self.state_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canonical complete N1 RAW account state independent from HTTP, WebSocket or gRPC acquisition.
|
||||||
|
///
|
||||||
|
/// Only complete raw account bytes are admissible. A transport response using `jsonParsed`, a
|
||||||
|
/// request-side data slice, or a response without a durable slot context must be normalized or
|
||||||
|
/// reacquired before this model is constructed.
|
||||||
|
pub struct RawAccountState {
|
||||||
|
data: std::boxed::Box<[u8]>,
|
||||||
|
executable: bool,
|
||||||
|
lamports: u64,
|
||||||
|
owner: ksp_core_lib::Pubkey,
|
||||||
|
reference: crate::RawAccountStateReference,
|
||||||
|
rent_epoch: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountState {
|
||||||
|
/// Creates one complete canonical RAW account state after Store-owned admission checks.
|
||||||
|
pub fn try_new(
|
||||||
|
reference: crate::RawAccountStateReference,
|
||||||
|
lamports: u64,
|
||||||
|
owner: ksp_core_lib::Pubkey,
|
||||||
|
executable: bool,
|
||||||
|
rent_epoch: u64,
|
||||||
|
data: std::boxed::Box<[u8]>,
|
||||||
|
) -> ksp_core_lib::Result<Self> {
|
||||||
|
if data.len() > crate::MAX_RAW_ACCOUNT_DATA_BYTES {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_RAW_MODEL_INVALID, "invalid backend-agnostic RAW Store model")
|
||||||
|
.with_context("field", "account_data")
|
||||||
|
.with_context("actual_len", data.len().to_string())
|
||||||
|
.with_context("maximum_len", crate::MAX_RAW_ACCOUNT_DATA_BYTES.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { data, executable, lamports, owner, reference, rent_epoch });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact complete account bytes used by future decoders.
|
||||||
|
#[must_use]
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
return self.data.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the complete account-data length in bytes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn data_len(&self) -> usize {
|
||||||
|
return self.data.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the account is executable.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn executable(&self) -> bool {
|
||||||
|
return self.executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account lamport balance.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn lamports(&self) -> u64 {
|
||||||
|
return self.lamports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the account owner program public key.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn owner(&self) -> &ksp_core_lib::Pubkey {
|
||||||
|
return &self.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable source-independent account-state identity.
|
||||||
|
#[must_use]
|
||||||
|
pub fn reference(&self) -> &crate::RawAccountStateReference {
|
||||||
|
return &self.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the rent epoch reported for this account state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn rent_epoch(&self) -> u64 {
|
||||||
|
return self.rent_epoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for RawAccountState {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter
|
||||||
|
.debug_struct("RawAccountState")
|
||||||
|
.field("reference", &self.reference)
|
||||||
|
.field("lamports", &self.lamports)
|
||||||
|
.field("owner", &self.owner)
|
||||||
|
.field("executable", &self.executable)
|
||||||
|
.field("rent_epoch", &self.rent_epoch)
|
||||||
|
.field("data_len", &self.data.len())
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persistable acquisition observation linked to one complete canonical RAW account state.
|
||||||
|
///
|
||||||
|
/// Yellowstone-only metadata remains optional observation detail and never changes the canonical
|
||||||
|
/// account state itself. HTTP/WS acquisitions therefore use the same observation type without
|
||||||
|
/// inventing a `write_version`, transaction signature or startup flag.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct RawAccountObservation {
|
||||||
|
account: crate::RawAccountStateReference,
|
||||||
|
is_startup: std::option::Option<bool>,
|
||||||
|
observation_key: crate::RawObservationKey,
|
||||||
|
provenance: crate::RawAcquisitionProvenance,
|
||||||
|
transaction_signature: std::option::Option<crate::RawTransactionSignature>,
|
||||||
|
write_version: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAccountObservation {
|
||||||
|
/// Creates one successful observation of a complete canonical RAW account state.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(observation_key: crate::RawObservationKey, account: crate::RawAccountStateReference, provenance: crate::RawAcquisitionProvenance) -> Self {
|
||||||
|
return Self {
|
||||||
|
account,
|
||||||
|
is_startup: std::option::Option::None,
|
||||||
|
observation_key,
|
||||||
|
provenance,
|
||||||
|
transaction_signature: std::option::Option::None,
|
||||||
|
write_version: std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a provider-reported startup/replay marker when the source exposes one.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_is_startup(mut self, value: bool) -> Self {
|
||||||
|
self.is_startup = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the transaction signature associated with the account write when exposed by the source.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_transaction_signature(mut self, value: crate::RawTransactionSignature) -> Self {
|
||||||
|
self.transaction_signature = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the source-specific account write version when the source exposes one.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_write_version(mut self, value: u64) -> Self {
|
||||||
|
self.write_version = std::option::Option::Some(value);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the durable account-state identity observed by this acquisition.
|
||||||
|
#[must_use]
|
||||||
|
pub fn account(&self) -> &crate::RawAccountStateReference {
|
||||||
|
return &self.account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional source-reported startup/replay marker.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_startup(&self) -> std::option::Option<bool> {
|
||||||
|
return self.is_startup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the deterministic producer-owned observation idempotence key.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn observation_key(&self) -> crate::RawObservationKey {
|
||||||
|
return self.observation_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns safe source-independent acquisition provenance.
|
||||||
|
#[must_use]
|
||||||
|
pub fn provenance(&self) -> &crate::RawAcquisitionProvenance {
|
||||||
|
return &self.provenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional transaction signature associated with this account write.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn transaction_signature(&self) -> std::option::Option<crate::RawTransactionSignature> {
|
||||||
|
return self.transaction_signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional source-specific account write version.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn write_version(&self) -> std::option::Option<u64> {
|
||||||
|
return self.write_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../../unit_tests/model/raw_account.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
// file: crates/ksp-store-api/src/model/raw_primitives.rs
|
// file: crates/ksp-store-api/src/model/raw_primitives.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
|
/// Maximum complete RAW account-data length admitted by the Store API.
|
||||||
|
///
|
||||||
|
/// This is a Store admission guard, not a Solana protocol-size claim.
|
||||||
|
pub const MAX_RAW_ACCOUNT_DATA_BYTES: usize = 16 * 1024 * 1024;
|
||||||
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
/// Maximum UTF-8 byte length accepted for one safe logical RAW/provenance code.
|
||||||
pub const MAX_RAW_CODE_BYTES: usize = 128;
|
pub const MAX_RAW_CODE_BYTES: usize = 128;
|
||||||
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
|
/// Maximum KSP-owned canonical RAW payload admitted by the Store API.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
// file: crates/ksp-store-api/tests/dependency_boundary.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Dependency canaries for the Store API RAW foundation.
|
//! Dependency canaries for the Store API RAW foundation.
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_003_manifest_keeps_exact_core_only_runtime_dependency() {
|
fn pre_004_manifest_keeps_exact_core_only_runtime_dependency() {
|
||||||
let manifest = include_str!("../Cargo.toml");
|
let manifest = include_str!("../Cargo.toml");
|
||||||
let dependencies_tail = manifest.split("[dependencies]").nth(1);
|
let dependencies_tail = manifest.split("[dependencies]").nth(1);
|
||||||
assert!(dependencies_tail.is_some(), "Store API dependencies section must exist");
|
assert!(dependencies_tail.is_some(), "Store API dependencies section must exist");
|
||||||
@@ -49,17 +49,19 @@ fn pre_003_manifest_keeps_exact_core_only_runtime_dependency() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_003_source_boundary_keeps_raw_models_passive_and_backend_free() {
|
fn pre_004_source_boundary_keeps_raw_models_passive_and_backend_free() {
|
||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
let model_home = include_str!("../src/model.rs");
|
let model_home = include_str!("../src/model.rs");
|
||||||
|
let raw_account = include_str!("../src/model/raw_account.rs");
|
||||||
let raw_primitives = include_str!("../src/model/raw_primitives.rs");
|
let raw_primitives = include_str!("../src/model/raw_primitives.rs");
|
||||||
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
|
let raw_transaction = include_str!("../src/model/raw_transaction.rs");
|
||||||
assert!(crate_root.contains("mod capability;"));
|
assert!(crate_root.contains("mod capability;"));
|
||||||
assert!(crate_root.contains("mod error;"));
|
assert!(crate_root.contains("mod error;"));
|
||||||
assert!(crate_root.contains("mod model;"));
|
assert!(crate_root.contains("mod model;"));
|
||||||
|
assert!(model_home.contains("raw_account"));
|
||||||
assert!(model_home.contains("raw_primitives"));
|
assert!(model_home.contains("raw_primitives"));
|
||||||
assert!(model_home.contains("raw_transaction"));
|
assert!(model_home.contains("raw_transaction"));
|
||||||
for source in [crate_root, model_home, raw_primitives, raw_transaction] {
|
for source in [crate_root, model_home, raw_account, raw_primitives, raw_transaction] {
|
||||||
for forbidden in [
|
for forbidden in [
|
||||||
"ksp_store_lib",
|
"ksp_store_lib",
|
||||||
"ksp_store_postgres_lib",
|
"ksp_store_postgres_lib",
|
||||||
@@ -77,6 +79,9 @@ fn pre_003_source_boundary_keeps_raw_models_passive_and_backend_free() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert!(!raw_transaction.contains("RawLog"));
|
assert!(!raw_transaction.contains("RawLog"));
|
||||||
|
for forbidden in ["TransactionStatusObservation", "RawLogNotification", "RawSlotEvent", "RawVoteEvent", "RawBlock", "YellowstoneEntry"] {
|
||||||
|
assert!(!crate_root.contains(forbidden), "deferred pre.004 model leaked into Store API surface: {forbidden}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-api/tests/public_api.rs
|
// file: crates/ksp-store-api/tests/public_api.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Integration canaries for the public `ksp-store-api` surface.
|
//! Integration canaries for the public `ksp-store-api` surface.
|
||||||
|
|
||||||
@@ -80,3 +80,48 @@ fn public_pre_003_surface_keeps_backend_and_structural_types_out() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_004_raw_account_state_and_observation_are_constructible_from_crate_root() {
|
||||||
|
let network = match ksp_store_api::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let reference = ksp_store_api::RawAccountStateReference::new(
|
||||||
|
network,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([21_u8; 32]),
|
||||||
|
55,
|
||||||
|
ksp_store_api::RawContentHash::new([22_u8; 32]),
|
||||||
|
);
|
||||||
|
let state = ksp_store_api::RawAccountState::try_new(
|
||||||
|
reference.clone(),
|
||||||
|
123,
|
||||||
|
ksp_store_api::Pubkey::new_from_array([23_u8; 32]),
|
||||||
|
false,
|
||||||
|
9,
|
||||||
|
vec![1_u8, 2_u8].into_boxed_slice(),
|
||||||
|
);
|
||||||
|
assert!(state.is_ok());
|
||||||
|
let received_at = match ksp_store_api::RawTimestamp::from_unix_millis(2_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let provider = match code("provider") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let protocol = match code("solana_http") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let method = match code("getAccountInfo") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let provenance = ksp_store_api::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_api::RawAcquisitionOrigin::Backfill, received_at);
|
||||||
|
let observation = ksp_store_api::RawAccountObservation::new(ksp_store_api::RawObservationKey::new([24_u8; 32]), reference, provenance);
|
||||||
|
assert_eq!(observation.account().slot(), 55);
|
||||||
|
assert!(observation.write_version().is_none());
|
||||||
|
assert_eq!(ksp_store_api::MAX_RAW_ACCOUNT_DATA_BYTES, 16 * 1024 * 1024);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
108
crates/ksp-store-api/unit_tests/model/raw_account.rs
Normal file
108
crates/ksp-store-api/unit_tests/model/raw_account.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// file: crates/ksp-store-api/unit_tests/model/raw_account.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn network() -> std::option::Option<crate::RawNetworkId> {
|
||||||
|
return match crate::RawNetworkId::new("mainnet-beta".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||||
|
std::result::Result::Err(_) => std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provenance() -> std::option::Option<crate::RawAcquisitionProvenance> {
|
||||||
|
let provider = match crate::RawProvenanceCode::new("publicnode".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let protocol = match crate::RawProvenanceCode::new("yellowstone_grpc".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let method = match crate::RawProvenanceCode::new("accounts".to_owned()) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
let received_at = match crate::RawTimestamp::from_unix_millis(1_000) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
|
};
|
||||||
|
return std::option::Option::Some(crate::RawAcquisitionProvenance::new(provider, protocol, method, crate::RawAcquisitionOrigin::Live, received_at));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_state_preserves_complete_common_fields_and_redacts_data_debug() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let pubkey = ksp_core_lib::Pubkey::new_from_array([1_u8; 32]);
|
||||||
|
let owner = ksp_core_lib::Pubkey::new_from_array([2_u8; 32]);
|
||||||
|
let reference = crate::RawAccountStateReference::new(network, pubkey, 42, crate::RawContentHash::new([3_u8; 32]));
|
||||||
|
let data = b"ACCOUNT_DATA_SENTINEL_NEVER_RENDER".to_vec().into_boxed_slice();
|
||||||
|
let state_result = crate::RawAccountState::try_new(reference.clone(), 500, owner, false, 7, data);
|
||||||
|
assert!(state_result.is_ok());
|
||||||
|
let state = match state_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(state.reference(), &reference);
|
||||||
|
assert_eq!(state.lamports(), 500);
|
||||||
|
assert!(!state.executable());
|
||||||
|
assert_eq!(state.rent_epoch(), 7);
|
||||||
|
assert_eq!(state.data(), b"ACCOUNT_DATA_SENTINEL_NEVER_RENDER");
|
||||||
|
let debug = format!("{state:?}");
|
||||||
|
assert!(!debug.contains("ACCOUNT_DATA_SENTINEL_NEVER_RENDER"));
|
||||||
|
assert!(debug.contains("data_len"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_state_rejects_only_oversized_data_and_allows_empty_accounts() {
|
||||||
|
let first_network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let first_reference =
|
||||||
|
crate::RawAccountStateReference::new(first_network, ksp_core_lib::Pubkey::new_from_array([4_u8; 32]), 1, crate::RawContentHash::new([5_u8; 32]));
|
||||||
|
let empty = crate::RawAccountState::try_new(
|
||||||
|
first_reference,
|
||||||
|
0,
|
||||||
|
ksp_core_lib::Pubkey::new_from_array([6_u8; 32]),
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
std::vec::Vec::new().into_boxed_slice(),
|
||||||
|
);
|
||||||
|
assert!(empty.is_ok());
|
||||||
|
let second_network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let second_reference =
|
||||||
|
crate::RawAccountStateReference::new(second_network, ksp_core_lib::Pubkey::new_from_array([7_u8; 32]), 2, crate::RawContentHash::new([8_u8; 32]));
|
||||||
|
let oversized = vec![0_u8; crate::MAX_RAW_ACCOUNT_DATA_BYTES + 1].into_boxed_slice();
|
||||||
|
let rejected = crate::RawAccountState::try_new(second_reference, 0, ksp_core_lib::Pubkey::new_from_array([9_u8; 32]), false, 0, oversized);
|
||||||
|
assert!(rejected.is_err());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_account_observation_keeps_yellowstone_specific_metadata_optional() {
|
||||||
|
let network = match network() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let reference =
|
||||||
|
crate::RawAccountStateReference::new(network, ksp_core_lib::Pubkey::new_from_array([10_u8; 32]), 99, crate::RawContentHash::new([11_u8; 32]));
|
||||||
|
let provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let observation = crate::RawAccountObservation::new(crate::RawObservationKey::new([12_u8; 32]), reference.clone(), provenance)
|
||||||
|
.with_write_version(17)
|
||||||
|
.with_transaction_signature(crate::RawTransactionSignature::new([13_u8; 64]))
|
||||||
|
.with_is_startup(false);
|
||||||
|
assert_eq!(observation.account(), &reference);
|
||||||
|
assert_eq!(observation.write_version(), std::option::Option::Some(17));
|
||||||
|
assert_eq!(observation.is_startup(), std::option::Option::Some(false));
|
||||||
|
assert_eq!(observation.transaction_signature(), std::option::Option::Some(crate::RawTransactionSignature::new([13_u8; 64])));
|
||||||
|
return;
|
||||||
|
}
|
||||||
252
deltas/0.3.1/pre.004.md
Normal file
252
deltas/0.3.1/pre.004.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<!-- file: deltas/0.3.1/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.1-pre.004` — admission cross-source + account state N1
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.3-fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003-fix.001` est fourni vert : `cargo fmt --all`, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace et `cargo test -p ksp-store-api` passent.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Auditer les formes HTTP/WS/gRPC déjà possédées par `ksp-onchain-transport-lib` avant d'ajouter une nouvelle famille N1, puis matérialiser uniquement le modèle dont la sémantique commune est démontrée.
|
||||||
|
|
||||||
|
La tranche :
|
||||||
|
|
||||||
|
- admet `RawAccountState`/`RawAccountObservation` avec bytes complets + slot durable ;
|
||||||
|
- sépare les enrichissements Yellowstone de l'état canonique commun ;
|
||||||
|
- refuse conceptuellement les réponses account partielles/parsées comme états persistants ;
|
||||||
|
- diffère `TransactionStatusObservation` parce que snapshot HTTP, transition WS et update Yellowstone ne représentent pas encore le même fait ;
|
||||||
|
- ferme la classification logs/slot/vote/block/Entry sans créer de modèles Store prématurés.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
Le workspace passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.1-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit account cross-source
|
||||||
|
|
||||||
|
Surfaces KSP relues :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP
|
||||||
|
getAccountInfo
|
||||||
|
getMultipleAccounts
|
||||||
|
getProgramAccounts
|
||||||
|
|
||||||
|
WebSocket
|
||||||
|
accountSubscribe
|
||||||
|
programSubscribe
|
||||||
|
Helius standard account/program reuse
|
||||||
|
|
||||||
|
Yellowstone gRPC
|
||||||
|
Account / AccountInfo
|
||||||
|
```
|
||||||
|
|
||||||
|
Champs communs retenus pour un état complet :
|
||||||
|
|
||||||
|
```text
|
||||||
|
network
|
||||||
|
pubkey
|
||||||
|
slot
|
||||||
|
lamports
|
||||||
|
owner
|
||||||
|
executable
|
||||||
|
rent_epoch
|
||||||
|
complete account data bytes
|
||||||
|
canonical state hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Règles d'admission :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getAccountInfo/getMultipleAccounts
|
||||||
|
-> admissibles avec account non-null + bytes complets
|
||||||
|
|
||||||
|
getProgramAccounts/programSubscribe
|
||||||
|
-> slot/context obligatoire
|
||||||
|
|
||||||
|
accountSubscribe
|
||||||
|
-> admissible avec bytes complets
|
||||||
|
|
||||||
|
Yellowstone Account
|
||||||
|
-> admissible seulement sans accounts_data_slice tronquant les bytes
|
||||||
|
|
||||||
|
jsonParsed/dataSlice/bare program notification
|
||||||
|
-X-> RawAccountState persistant
|
||||||
|
```
|
||||||
|
|
||||||
|
`space` n'est pas une vérité stockée séparément lorsque les bytes complets sont présents : `data.len()` est déterministe.
|
||||||
|
|
||||||
|
## Modèles ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawAccountStateReference
|
||||||
|
network
|
||||||
|
pubkey
|
||||||
|
slot
|
||||||
|
state_hash
|
||||||
|
|
||||||
|
RawAccountState
|
||||||
|
reference
|
||||||
|
lamports
|
||||||
|
owner
|
||||||
|
executable
|
||||||
|
rent_epoch
|
||||||
|
complete data bytes
|
||||||
|
|
||||||
|
RawAccountObservation
|
||||||
|
observation_key
|
||||||
|
account reference
|
||||||
|
provenance
|
||||||
|
optional write_version
|
||||||
|
optional transaction_signature
|
||||||
|
optional is_startup
|
||||||
|
```
|
||||||
|
|
||||||
|
La référence inclut `state_hash` parce que plusieurs écritures d'une account peuvent survenir dans le même slot alors que HTTP/WS standards ne possèdent pas le `write_version` Yellowstone. Plusieurs sources observant le même état complet peuvent donc converger sans faire de l'ordinal Yellowstone un identifiant commun.
|
||||||
|
|
||||||
|
Le calcul du digest reste producer/converter-owned. `ksp-store-api` n'ajoute aucun codec ni algorithme de hash.
|
||||||
|
|
||||||
|
## Borne account data
|
||||||
|
|
||||||
|
Nouvelle admission guard Store-owned :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_ACCOUNT_DATA_BYTES = 16 MiB
|
||||||
|
```
|
||||||
|
|
||||||
|
Les bytes vides restent valides pour une account vide. La borne n'est pas présentée comme une limite protocolaire Solana.
|
||||||
|
|
||||||
|
`RawAccountState` n'implémente pas `Clone` et son `Debug` ne rend jamais les bytes.
|
||||||
|
|
||||||
|
## Observation account
|
||||||
|
|
||||||
|
Les informations présentes uniquement sur certaines sources restent observation-only :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Yellowstone write_version
|
||||||
|
Yellowstone transaction signature
|
||||||
|
Yellowstone is_startup
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP/WS utilisent le même `RawAccountObservation` sans inventer ces valeurs.
|
||||||
|
|
||||||
|
Provider/protocol/method/endpoint/commitment/timing restent dans `RawAcquisitionProvenance` conformément à `pre.003`.
|
||||||
|
|
||||||
|
## Transaction status différé
|
||||||
|
|
||||||
|
Surfaces auditées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getSignatureStatuses
|
||||||
|
= snapshot interrogé avec confirmations/confirmationStatus
|
||||||
|
|
||||||
|
signatureSubscribe
|
||||||
|
= event one-shot de réception/commitment demandé
|
||||||
|
|
||||||
|
Yellowstone TransactionStatus
|
||||||
|
= update avec slot/signature/is_vote/index/error, sans commitment commun
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche n'introduit donc aucun `TransactionStatusObservation` générique rempli d'options. La future conception devra distinguer snapshot durable et event realtime et auditer l'ownership `ksp-interface-lib` des événements passifs.
|
||||||
|
|
||||||
|
## Classification fermée
|
||||||
|
|
||||||
|
```text
|
||||||
|
logsSubscribe
|
||||||
|
-> event-only candidat ; pas de RawLog Store
|
||||||
|
|
||||||
|
transaction logMessages
|
||||||
|
-> restent dans RawTransaction jusqu'à N2 STRUCTURAL
|
||||||
|
|
||||||
|
slot/root/slotsUpdates
|
||||||
|
-> event-only candidat
|
||||||
|
|
||||||
|
vote
|
||||||
|
-> event-only candidat après compatibilité utile
|
||||||
|
|
||||||
|
getBlock/Yellowstone Block
|
||||||
|
-> conteneur d'acquisition RawTransaction ; RawBlock reste IDEA
|
||||||
|
|
||||||
|
Yellowstone Entry
|
||||||
|
-> explicitement non retenu
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Store runtime ne publie aucune notification ; workers/analyzers/runtime possèdent les futurs déclenchements.
|
||||||
|
|
||||||
|
## Tests ajoutés/mis à jour
|
||||||
|
|
||||||
|
Unitaires account :
|
||||||
|
|
||||||
|
- état complet et champs communs ;
|
||||||
|
- account data vide accepté ;
|
||||||
|
- account data oversized rejeté ;
|
||||||
|
- `Debug` sans bytes ;
|
||||||
|
- metadata Yellowstone optionnelle sur observation.
|
||||||
|
|
||||||
|
Canaris d'intégration :
|
||||||
|
|
||||||
|
- surface crate-root `RawAccountState*`/`RawAccountObservation` ;
|
||||||
|
- dépendance runtime toujours exactement Core-only ;
|
||||||
|
- absence de Transport/backend/runtime/codec ;
|
||||||
|
- absence de `TransactionStatusObservation`, `RawLogNotification`, `RawSlotEvent`, `RawVoteEvent`, `RawBlock` et `YellowstoneEntry` publics.
|
||||||
|
|
||||||
|
## Documentation mise à jour
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/022-V0_3_1_STORE_RAW_PLAN.md
|
||||||
|
docs/validation/018-V0_3_1_STORE_RAW.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La matrice cross-source est désormais explicite et `pre.004` ne prétend pas que toutes les réponses on-chain constituent des modèles Store.
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations non exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement de génération. L'opérateur doit donc exécuter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
```
|
||||||
|
|
||||||
|
Une commande non exécutée n'est pas déclarée PASS.
|
||||||
|
|
||||||
|
## Hors scope confirmé
|
||||||
|
|
||||||
|
```text
|
||||||
|
source converter HTTP/WS/gRPC concret
|
||||||
|
TransactionStatus model commun
|
||||||
|
logs/slot/vote event model
|
||||||
|
RawBlock persistence
|
||||||
|
Yellowstone Entry persistence
|
||||||
|
capabilities read/write
|
||||||
|
queries/outcomes
|
||||||
|
retention/tombstone concret
|
||||||
|
ksp-store-lib
|
||||||
|
ksp-store-postgres-lib
|
||||||
|
PostgreSQL/tokio-postgres
|
||||||
|
Config std.store
|
||||||
|
N2 STRUCTURAL
|
||||||
|
N3/N4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.3.1-pre.005` introduit les capabilities backend extensibles et object-safe pour les modèles persistants réellement matérialisés, sans façade runtime `Store`, sans backend PostgreSQL et sans obliger un backend à supporter toutes les familles N1.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/022-V0_3_1_STORE_RAW_PLAN.md -->
|
<!-- file: docs/plans/022-V0_3_1_STORE_RAW_PLAN.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Plan `0.3.1` — Store API RAW foundation
|
# Plan `0.3.1` — Store API RAW foundation
|
||||||
|
|
||||||
@@ -434,61 +434,123 @@ logsSubscribe
|
|||||||
|
|
||||||
Le Store n'envoie lui-même aucun événement.
|
Le Store n'envoie lui-même aucun événement.
|
||||||
|
|
||||||
### 6.4 `RawAccountState` et observation
|
### 6.4 `RawAccountState` et observation — matrice `pre.004`
|
||||||
|
|
||||||
Les réponses account complètes provenant de HTTP, WS ou Yellowstone peuvent potentiellement converger vers un même état canonique :
|
L'audit de la surface KSP réelle confirme qu'un **état complet de compte** possède une sémantique commune entre HTTP, WebSocket et Yellowstone lorsque la source fournit les bytes complets et un slot durable. Le Store ne conserve pas la forme d'encodage réseau : base58/base64/base64+zstd et protobuf doivent être décodés avant construction du modèle commun.
|
||||||
|
|
||||||
|
Le modèle commun matérialisé est :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
pubkey
|
RawAccountStateReference
|
||||||
slot/context utile
|
network
|
||||||
lamports
|
pubkey
|
||||||
owner
|
slot
|
||||||
executable
|
state_hash
|
||||||
rent_epoch
|
|
||||||
data bytes exacts
|
RawAccountState
|
||||||
|
reference
|
||||||
|
lamports
|
||||||
|
owner
|
||||||
|
executable
|
||||||
|
rent_epoch
|
||||||
|
complete data bytes
|
||||||
|
|
||||||
|
RawAccountObservation
|
||||||
|
observation_key
|
||||||
|
account reference
|
||||||
|
provenance
|
||||||
|
optional write_version
|
||||||
|
optional transaction_signature
|
||||||
|
optional is_startup
|
||||||
```
|
```
|
||||||
|
|
||||||
Les détails propres à l'acquisition (`write_version`, signature source, startup, provider, transport, timing, etc.) appartiennent à `RawAccountObservation` lorsqu'ils sont utiles et disponibles.
|
`state_hash` fait partie de la référence car une même account peut subir plusieurs écritures dans un slot, alors que les surfaces HTTP/WS standards n'exposent pas le `write_version` Yellowstone. Le digest permet à plusieurs sources observant **le même état complet** de converger sans promouvoir un ordinal provider-specific dans l'identité commune. Le producer/converter possède le calcul déterministe du digest ; Store API ne choisit pas l'algorithme de hash.
|
||||||
|
|
||||||
`RawAccountState`/`RawAccountObservation` doivent être **prévus dans `ksp-store-api`** après validation de la matrice de compatibilité. Leur persistence concrète PostgreSQL peut rester non implémentée au début de `0.3.2`.
|
La matrice d'admission courante est :
|
||||||
|
|
||||||
Un compte demandé sous une forme déjà interprétée par le provider ne doit pas remplacer arbitrairement les bytes canoniques nécessaires à un futur decoder.
|
| Source KSP | État complet commun | Slot durable | Admission `RawAccountState` |
|
||||||
|
|------------------------------------|---------------------|---------------|-------------------------------------------------------------------------------------|
|
||||||
|
| HTTP `getAccountInfo` | oui | oui | oui si account non-null, bytes complets et aucun `dataSlice`/`jsonParsed` |
|
||||||
|
| HTTP `getMultipleAccounts` | oui | oui | oui par position non-null si bytes complets ; pubkey reprise depuis la requête |
|
||||||
|
| HTTP `getProgramAccounts` | oui | conditionnel | oui seulement avec résultat contextualisé + bytes complets ; forme bare refusée |
|
||||||
|
| WS `accountSubscribe` | oui | oui | oui si bytes complets ; pubkey reprise depuis l'identité de subscription |
|
||||||
|
| WS `programSubscribe` | oui | conditionnel | oui seulement pour la forme contextualisée + bytes complets ; forme bare event-only |
|
||||||
|
| Helius standard account/program WS | oui | idem standard | mêmes règles que le wire Solana standard réutilisé |
|
||||||
|
| Yellowstone `Account` | oui | oui | oui si `accounts_data_slice` n'a pas tronqué les bytes |
|
||||||
|
|
||||||
### 6.5 Transaction status
|
Règles négatives :
|
||||||
|
|
||||||
Les surfaces signature/status peuvent représenter un fait distinct d'une transaction complète :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
signatureSubscribe
|
jsonParsed
|
||||||
getSignatureStatuses
|
request-side data slice
|
||||||
Yellowstone TransactionStatus
|
Yellowstone accounts_data_slice
|
||||||
provider equivalent
|
program account sans context/slot
|
||||||
|
account absent/null
|
||||||
|
-X-> RawAccountState persistant incomplet
|
||||||
```
|
```
|
||||||
|
|
||||||
Le candidat `TransactionStatusObservation` doit être étudié et, si la sémantique commune est démontrée, prévu dans l'API même si sa persistence n'est pas immédiatement implémentée.
|
`space` n'est pas conservé comme vérité indépendante : lorsqu'on possède les bytes complets, leur longueur est déterministe. Les enrichissements Yellowstone `write_version`, `txn_signature` et `is_startup` appartiennent à `RawAccountObservation`. Le timestamp serveur et les filters/capture ids restent de la provenance lorsque le converter peut les représenter sans perte utile.
|
||||||
|
|
||||||
Il peut plus tard servir à un worker/analyser pour détecter des transitions de commitment/status. Ce déclenchement n'est pas une responsabilité du Store.
|
La borne initiale Store-owned est :
|
||||||
|
|
||||||
### 6.6 Slot, vote, block et Yellowstone Entry
|
|
||||||
|
|
||||||
Classification actuelle :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
complete account data <= 16 MiB
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle est un admission guard KSP, pas une affirmation sur la limite protocolaire Solana.
|
||||||
|
|
||||||
|
### 6.5 Transaction status — convergence insuffisante pour un modèle Store unique
|
||||||
|
|
||||||
|
L'audit `pre.004` conclut que les trois surfaces candidates ne représentent pas encore exactement le même fait :
|
||||||
|
|
||||||
|
| Source | Sémantique principale | Conclusion `0.3.1` |
|
||||||
|
|-------------------------------|---------------------------------------------------------------------------|-----------------------------------------------------|
|
||||||
|
| HTTP `getSignatureStatuses` | snapshot interrogé : slot, confirmations, error, confirmation status | candidat snapshot durable, non figé |
|
||||||
|
| WS `signatureSubscribe` | event one-shot : received puis/ou commitment demandé atteint | event runtime, ownership Interface/worker à étudier |
|
||||||
|
| Yellowstone TransactionStatus | update d'exécution : slot, signature, vote, index, error, sans commitment | event/status provider-neutral potentiel, non figé |
|
||||||
|
|
||||||
|
Créer maintenant un `TransactionStatusObservation` rempli d'options ferait perdre la distinction entre **snapshot interrogé**, **transition de commitment** et **update d'exécution**. Aucun modèle Store n'est donc ajouté en `pre.004`.
|
||||||
|
|
||||||
|
TODO avant matérialisation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
séparer explicitement snapshot durable vs event realtime
|
||||||
|
étudier l'ownership ksp-interface-lib des events passifs
|
||||||
|
prouver la correspondance des états/commitments
|
||||||
|
prévoir un éventuel wake-up worker/analyser sans notification émise par Store
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.6 Logs, slot, vote, block et Yellowstone Entry — classification fermée `pre.004`
|
||||||
|
|
||||||
|
Classification actuelle après audit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
logsSubscribe
|
||||||
|
-> event realtime passif distinct
|
||||||
|
-> signature + error + ordered log lines + context slot
|
||||||
|
-> pas de RawLog Store
|
||||||
|
-> ownership ksp-interface-lib/worker à préciser
|
||||||
|
|
||||||
slot/root/slotsUpdates
|
slot/root/slotsUpdates
|
||||||
-> event-only candidat ; persistence non justifiée actuellement
|
-> event-only candidat
|
||||||
|
-> aucune persistence N1 démontrée
|
||||||
|
|
||||||
vote
|
vote
|
||||||
-> event-only candidat si la forme est suffisamment commune entre les ledgers/providers qui l'exposent
|
-> event-only candidat si la forme commune utile est prouvée
|
||||||
|
-> aucune persistence N1 par défaut
|
||||||
|
|
||||||
getBlock / Yellowstone Block
|
getBlock / Yellowstone Block
|
||||||
-> source/conteneur d'acquisition de RawTransaction par défaut
|
-> source/conteneur d'acquisition de RawTransaction
|
||||||
RawBlock persistant seulement si un besoin block-level non reconstructible est démontré
|
-> RawBlock persistant reste IDEA uniquement
|
||||||
|
|
||||||
Yellowstone Entry
|
Yellowstone Entry
|
||||||
-> examiné et non retenu actuellement : trop bas niveau et aucune destination replay/decomposition/event métier suffisante identifiée
|
-> transport-only
|
||||||
|
-> explicitement non retenu actuellement
|
||||||
```
|
```
|
||||||
|
|
||||||
`RawBlock` reste une IDEA, pas un modèle actif. Recréer le ledger bloc par bloc sans besoin supplémentaire irait à l'encontre de l'objectif KSP de transformer la donnée blockchain en unités directement exploitables.
|
Les logs contenus dans `RawTransaction` restent distincts de `logsSubscribe` : les premiers sont de la matière replayable de transaction et seront extraits en N2 STRUCTURAL ; le second est un événement realtime léger pouvant éventuellement déclencher l'hydratation de la transaction complète.
|
||||||
|
|
||||||
|
`RawBlock` ne doit être rouvert que si un besoin block-level non reconstructible apporte une valeur concrète au pipeline. Recréer le ledger bloc par bloc sans besoin supplémentaire irait à l'encontre de l'objectif KSP de produire des unités directement exploitables.
|
||||||
|
|
||||||
### 6.7 Modèles et capabilities sont indépendants
|
### 6.7 Modèles et capabilities sont indépendants
|
||||||
|
|
||||||
@@ -702,9 +764,25 @@ La signature ne doit pas être représentée comme un identifiant SQL `i64` dans
|
|||||||
|
|
||||||
Chaque observation possède une clé d'idempotence déterministe fournie par le producer/composition selon un contrat documenté. Deux acquisitions légitimes distinctes peuvent donc être conservées même si elles pointent vers le même RAW.
|
Chaque observation possède une clé d'idempotence déterministe fournie par le producer/composition selon un contrat documenté. Deux acquisitions légitimes distinctes peuvent donc être conservées même si elles pointent vers le même RAW.
|
||||||
|
|
||||||
### 10.4 Événements et autres familles
|
### 10.4 Account states
|
||||||
|
|
||||||
Les événements realtime non persistés n'ont pas d'identité Store à inventer. Lorsqu'une nouvelle famille persistante est admise (`RawAccountState`, status durable, etc.), son identité d'idempotence doit être définie avec le modèle concret et ne jamais dépendre d'une primary key backend.
|
La référence matérialisée par `pre.004` est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawAccountStateReference
|
||||||
|
network
|
||||||
|
pubkey
|
||||||
|
slot
|
||||||
|
canonical state hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Le hash couvre conceptuellement l'état canonique complet et sert à distinguer/converger les écritures multiples possibles dans un même slot sans dépendre de `write_version`. Le Store API ne calcule pas ce digest et ne transforme pas les bytes de transport.
|
||||||
|
|
||||||
|
Une `RawAccountObservation` possède sa propre `RawObservationKey`; plusieurs acquisitions HTTP/WS/gRPC peuvent donc viser la même référence d'état sans être fusionnées comme observations.
|
||||||
|
|
||||||
|
### 10.5 Événements et autres familles
|
||||||
|
|
||||||
|
Les événements realtime non persistés n'ont pas d'identité Store à inventer. Si une autre famille persistante est admise ultérieurement, son identité d'idempotence doit être définie avec le modèle concret et ne jamais dépendre d'une primary key backend.
|
||||||
|
|
||||||
Aucune promesse exactly-once distribuée n'est faite.
|
Aucune promesse exactly-once distribuée n'est faite.
|
||||||
|
|
||||||
@@ -1293,7 +1371,7 @@ Introduire payload/reference/provenance/idempotence/timestamps bornés puis `Raw
|
|||||||
|
|
||||||
### `pre.004` — Matrice cross-source + familles N1 prévues
|
### `pre.004` — Matrice cross-source + familles N1 prévues
|
||||||
|
|
||||||
Auditer HTTP/WS/gRPC pour `RawAccountState`/observation et `TransactionStatusObservation`; introduire les modèles communs seulement lorsque la sémantique converge. Classer explicitement logsSubscribe/slot/vote/block/Entry en event, IDEA ou rejet.
|
Audit HTTP/WS/gRPC matérialisé. `RawAccountState`/observation sont admis avec bytes complets + slot et enrichissements source-specific séparés. `TransactionStatusObservation` est différé car snapshot HTTP, transition WS et update Yellowstone ne convergent pas encore assez. `logsSubscribe`/slot/vote restent event-only candidats, block reste conteneur/IDEA et Entry reste rejeté.
|
||||||
|
|
||||||
### `pre.005` — Capabilities backend extensibles
|
### `pre.005` — Capabilities backend extensibles
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/018-V0_3_1_STORE_RAW.md -->
|
<!-- file: docs/validation/018-V0_3_1_STORE_RAW.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Validation `0.3.1` — Store API RAW foundation
|
# Validation `0.3.1` — Store API RAW foundation
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ contrats/capabilities backend externes
|
|||||||
cycle de rétention logique + tombstone
|
cycle de rétention logique + tombstone
|
||||||
```
|
```
|
||||||
|
|
||||||
`RawAccountState`/observation et `TransactionStatusObservation` doivent être audités cross-source et prévus dans l'API lorsque leur sémantique commune est prouvée. Les notifications de logs/slot/vote sont classées séparément comme candidats event-only, avec ownership Interface préféré lorsqu'elles ne sont pas persistées.
|
`pre.004` prouve la convergence de `RawAccountState`/observation entre HTTP/WS/gRPC sous admission stricte bytes complets + slot. `TransactionStatusObservation` reste différé : snapshot HTTP, transition WS et update Yellowstone ne sont pas encore un fait unique. Les notifications logs/slot/vote restent event-only candidates, avec ownership Interface préféré lorsqu'elles ne sont pas persistées.
|
||||||
|
|
||||||
## 2. Gate `pre.001`
|
## 2. Gate `pre.001`
|
||||||
|
|
||||||
@@ -118,8 +118,8 @@ Toute divergence exige un usage public réel et une révision du plan.
|
|||||||
|--------------------------------|--------------------------|-------------------|----------------|--------------------------------------------------------------------|
|
|--------------------------------|--------------------------|-------------------|----------------|--------------------------------------------------------------------|
|
||||||
| transaction complète | N1 RAW | oui | STRUCTURAL oui | implémenter modèle + observation |
|
| transaction complète | N1 RAW | oui | STRUCTURAL oui | implémenter modèle + observation |
|
||||||
| logs contenus dans transaction | partie de RawTransaction | via transaction | STRUCTURAL oui | préserver lossless, ne pas dupliquer en `RawLog` |
|
| logs contenus dans transaction | partie de RawTransaction | via transaction | STRUCTURAL oui | préserver lossless, ne pas dupliquer en `RawLog` |
|
||||||
| account state complet | N1 RAW candidat | futur oui | à déterminer | audit HTTP/WS/gRPC puis prévoir modèle/observation |
|
| account state complet | N1 RAW | oui à terme | pas démontré | modèle + observation matérialisés en `pre.004` |
|
||||||
| transaction status | observation candidat | optionnelle | non | audit signature/status sources |
|
| transaction status | familles distinctes | non figée | non | différer : snapshot HTTP != transition WS != update Yellowstone |
|
||||||
| `logsSubscribe` notification | event-only candidat | non par défaut | non | ownership Interface/worker à figer |
|
| `logsSubscribe` notification | event-only candidat | non par défaut | non | ownership Interface/worker à figer |
|
||||||
| slot/root/slotsUpdates | event-only candidat | non | non | TODO use-case + compatibilité |
|
| slot/root/slotsUpdates | event-only candidat | non | non | TODO use-case + compatibilité |
|
||||||
| vote | event-only candidat | non | non | TODO seulement si forme commune utile |
|
| vote | event-only candidat | non | non | TODO seulement si forme commune utile |
|
||||||
@@ -169,26 +169,30 @@ crate/test backend externe
|
|||||||
|
|
||||||
## 8. Threat/API gates futurs
|
## 8. Threat/API gates futurs
|
||||||
|
|
||||||
| Gate | Attendu | Statut initial |
|
| Gate | Attendu | Statut initial |
|
||||||
|----------------------------------|-------------------------------------------------|----------------|
|
|---------------------------------------------|-------------------------------------------------|----------------|
|
||||||
| oversized RAW payload | rejet avant allocation pathologique | `pre.003` |
|
| oversized RAW payload | rejet avant allocation pathologique | `pre.003` |
|
||||||
| payload Debug | aucun bytes brut | `pre.003` |
|
| payload Debug | aucun bytes brut | `pre.003` |
|
||||||
| partial source -> RawTransaction | interdit ; contrat complet exigé | `pre.003/004` |
|
| partial source -> RawTransaction | interdit ; contrat complet exigé | `pre.003/004` |
|
||||||
| HTTP/WS/gRPC semantic mismatch | détecté par admission matrix | `pre.004` |
|
| HTTP/WS/gRPC semantic mismatch | détecté par admission matrix | `pre.004` PASS |
|
||||||
| duplicate same content | `AlreadyPresent` | `pre.006` |
|
| account bytes partiels/parsés | refusés avant `RawAccountState` | `pre.004` PASS |
|
||||||
| duplicate divergent content | Conflict stable | `pre.006` |
|
| account sans slot durable | refusé comme état persistant | `pre.004` PASS |
|
||||||
| partial transaction+observation | interdit par atomic acquisition | `pre.005` |
|
| account data Debug | aucun bytes brut | `pre.004` PASS |
|
||||||
| event-only -> Store capability | absent par défaut | `pre.004/007` |
|
| status surfaces artificiellement fusionnées | aucun modèle commun prématuré | `pre.004` PASS |
|
||||||
| Interface/Store duplicate model | absent | `pre.007` |
|
| duplicate same content | `AlreadyPresent` | `pre.006` |
|
||||||
| page limit 0/>500 | rejet | `pre.006` |
|
| duplicate divergent content | Conflict stable | `pre.006` |
|
||||||
| SQL/backend cursor leak | absent | `pre.006/007` |
|
| partial transaction+observation | interdit par atomic acquisition | `pre.005` |
|
||||||
| external backend | implémente API sans Store lib | `pre.005` |
|
| event-only -> Store capability | absent par défaut | `pre.004/007` |
|
||||||
| processing `bool` comme vérité | absent ; future preuve version-aware documentée | `pre.006/007` |
|
| Interface/Store duplicate model | absent | `pre.007` |
|
||||||
| purge sans policy/evidence | impossible par contrat | `pre.006/007` |
|
| page limit 0/>500 | rejet | `pre.006` |
|
||||||
| tombstone supprimé avec payload | interdit | `pre.006` |
|
| SQL/backend cursor leak | absent | `pre.006/007` |
|
||||||
| rebackfill normal après purge | skip | `pre.006` |
|
| external backend | implémente API sans Store lib | `pre.005` |
|
||||||
| force rehydrate implicite | interdit | `pre.006` |
|
| processing `bool` comme vérité | absent ; future preuve version-aware documentée | `pre.006/007` |
|
||||||
| N2/N3/N4 creep | aucune surface | `pre.007` |
|
| purge sans policy/evidence | impossible par contrat | `pre.006/007` |
|
||||||
|
| tombstone supprimé avec payload | interdit | `pre.006` |
|
||||||
|
| rebackfill normal après purge | skip | `pre.006` |
|
||||||
|
| force rehydrate implicite | interdit | `pre.006` |
|
||||||
|
| N2/N3/N4 creep | aucune surface | `pre.007` |
|
||||||
|
|
||||||
### 8.1 Matérialisation `pre.003`
|
### 8.1 Matérialisation `pre.003`
|
||||||
|
|
||||||
@@ -227,6 +231,43 @@ aucun RawLog/N2 STRUCTURAL/backend/runtime ajouté
|
|||||||
|
|
||||||
La complétude sémantique d'une source HTTP/WS/gRPC vers le format canonique n'est pas simulée dans Store API : elle reste le gate d'admission/conversion de `pre.004`. `pre.003` exige seulement qu'un `RawTransaction` reçoive un `RawPayload` déjà canonique complet selon son format KSP déclaré.
|
La complétude sémantique d'une source HTTP/WS/gRPC vers le format canonique n'est pas simulée dans Store API : elle reste le gate d'admission/conversion de `pre.004`. `pre.003` exige seulement qu'un `RawTransaction` reçoive un `RawPayload` déjà canonique complet selon son format KSP déclaré.
|
||||||
|
|
||||||
|
### 8.2 Matérialisation `pre.004`
|
||||||
|
|
||||||
|
La tranche ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_ACCOUNT_DATA_BYTES
|
||||||
|
RawAccountStateReference
|
||||||
|
RawAccountState
|
||||||
|
RawAccountObservation
|
||||||
|
```
|
||||||
|
|
||||||
|
Invariants vérifiés par modèle/canaris :
|
||||||
|
|
||||||
|
```text
|
||||||
|
référence = network + pubkey + slot + canonical state hash
|
||||||
|
account data complet <= 16 MiB
|
||||||
|
empty account data autorisé
|
||||||
|
Debug RawAccountState ne rend jamais les bytes
|
||||||
|
write_version/transaction_signature/is_startup restent observation-only optionnels
|
||||||
|
HTTP/WS/gRPC source details n'entrent pas dans RawAccountState
|
||||||
|
aucun TransactionStatusObservation artificiel
|
||||||
|
aucun RawLogNotification/RawSlotEvent/RawVoteEvent/RawBlock/YellowstoneEntry public
|
||||||
|
```
|
||||||
|
|
||||||
|
Matrice d'admission validée architecturalement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getAccountInfo/getMultipleAccounts -> oui avec bytes complets
|
||||||
|
getProgramAccounts -> contexte obligatoire
|
||||||
|
accountSubscribe -> oui avec bytes complets
|
||||||
|
programSubscribe -> contexte obligatoire
|
||||||
|
Yellowstone Account -> aucun accounts_data_slice
|
||||||
|
jsonParsed/dataSlice/bare program -> non
|
||||||
|
```
|
||||||
|
|
||||||
|
La conversion source -> modèle reste hors `ksp-store-api`; la crate ne dépend toujours que de `ksp-core-lib`.
|
||||||
|
|
||||||
## 9. Gates de fermeture prévus
|
## 9. Gates de fermeture prévus
|
||||||
|
|
||||||
### Gate technique final `pre.008`
|
### Gate technique final `pre.008`
|
||||||
@@ -317,7 +358,7 @@ sécurité
|
|||||||
| `pre.001` | audit/design/taxonomie/split | PRÊT après gate local |
|
| `pre.001` | audit/design/taxonomie/split | PRÊT après gate local |
|
||||||
| `pre.002` | scaffold + taxonomie Store API | À FAIRE |
|
| `pre.002` | scaffold + taxonomie Store API | À FAIRE |
|
||||||
| `pre.003` | primitives + RawTransaction | À FAIRE |
|
| `pre.003` | primitives + RawTransaction | À FAIRE |
|
||||||
| `pre.004` | admission matrix + account/status models | À FAIRE |
|
| `pre.004` | admission matrix + account/status models | PRÊT après gate local |
|
||||||
| `pre.005` | backend contracts/capabilities | À FAIRE |
|
| `pre.005` | backend contracts/capabilities | À FAIRE |
|
||||||
| `pre.006` | queries/outcomes/retention/tombstone | À FAIRE |
|
| `pre.006` | queries/outcomes/retention/tombstone | À FAIRE |
|
||||||
| `pre.007` | boundary/adversarial/completeness | À FAIRE |
|
| `pre.007` | boundary/adversarial/completeness | À FAIRE |
|
||||||
|
|||||||
Reference in New Issue
Block a user