v0.2.9-pre.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 242
|
# version: 243
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.9-pre.3.fix.1"
|
version = "0.2.9-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"
|
||||||
|
|||||||
562
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
Normal file
562
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
const MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT: usize = 128;
|
||||||
|
const MAX_GRPC_SUBSCRIBE_DATA_SLICE_LENGTH_BYTES: u64 = 64 * 1024 * 1024;
|
||||||
|
const MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT: usize = 1_024;
|
||||||
|
const MAX_GRPC_SUBSCRIBE_FILTER_NAME_LENGTH_BYTES: usize = 128;
|
||||||
|
|
||||||
|
/// Validated logical filter name used by the standard Yellowstone `SubscribeRequest` maps.
|
||||||
|
///
|
||||||
|
/// Filter names are returned by Yellowstone in update envelopes. KSP therefore keeps them as typed public data, but omits their text from `Debug` so routine
|
||||||
|
/// diagnostics cannot accidentally disclose caller-defined labels.
|
||||||
|
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct YellowstoneSubscribeFilterName {
|
||||||
|
value: std::string::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeFilterName {
|
||||||
|
/// Validates one provider-neutral Yellowstone filter-group name.
|
||||||
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
||||||
|
let value = value.into();
|
||||||
|
if value.is_empty() || value.len() > MAX_GRPC_SUBSCRIBE_FILTER_NAME_LENGTH_BYTES || value.trim() != value || value.chars().any(char::is_control) {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter name is invalid")
|
||||||
|
.with_context("field", "grpc_subscribe.filter_name"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the validated filter name text.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
return self.value.as_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for YellowstoneSubscribeFilterName {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter.write_str("YellowstoneSubscribeFilterName(<redacted>)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One standard Yellowstone account-data slice applied to subscribed account payloads.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneAccountsDataSlice {
|
||||||
|
offset: u64,
|
||||||
|
length: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneAccountsDataSlice {
|
||||||
|
/// Creates and validates one account-data slice.
|
||||||
|
pub fn new(offset: u64, length: u64) -> ksp_core_lib::Result<Self> {
|
||||||
|
if length > MAX_GRPC_SUBSCRIBE_DATA_SLICE_LENGTH_BYTES || offset.checked_add(length).is_none() {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice exceeds KSP bounds")
|
||||||
|
.with_context("field", "grpc_subscribe.accounts_data_slice"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(Self { offset, length });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the byte offset.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn offset(self) -> u64 {
|
||||||
|
return self.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the requested byte length.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn length(self) -> u64 {
|
||||||
|
return self.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestAccountsDataSlice {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestAccountsDataSlice { offset: self.offset, length: self.length };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional ping mutation carried inside the standard Yellowstone subscribe stream.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribePing {
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribePing {
|
||||||
|
/// Creates one subscribe-stream ping request.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(id: i32) -> Self {
|
||||||
|
return Self { id };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the exact ping identifier carried on the wire.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn id(self) -> i32 {
|
||||||
|
return self.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestPing {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestPing { id: self.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Account-family filter-group shell for the standard Yellowstone subscribe surface.
|
||||||
|
///
|
||||||
|
/// `0.2.9-pre.004` intentionally materializes only the top-level map contract. Account selectors and account-specific filter oneofs are added by `pre.005`.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeAccountFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeAccountFilter {
|
||||||
|
/// Creates an empty account filter group.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slot-family filter-group shell for the standard Yellowstone subscribe surface.
|
||||||
|
///
|
||||||
|
/// Slot optional flags and update decoding are added by `0.2.9-pre.005`.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeSlotFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeSlotFilter {
|
||||||
|
/// Creates an empty slot filter group.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction-family filter-group shell shared by `transactions` and `transactions_status`.
|
||||||
|
///
|
||||||
|
/// Transaction selectors, Cuckoo filters and token-account expansion are added by `0.2.9-pre.006`.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeTransactionFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeTransactionFilter {
|
||||||
|
/// Creates an empty transaction filter group.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block-family filter-group shell for the standard Yellowstone subscribe surface.
|
||||||
|
///
|
||||||
|
/// Block selectors, include flags and Cuckoo filters are added by `0.2.9-pre.007`.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeBlockFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeBlockFilter {
|
||||||
|
/// Creates an empty block filter group.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Named empty filter activating the standard Yellowstone `blocks_meta` family.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeBlocksMetaFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeBlocksMetaFilter {
|
||||||
|
/// Creates the empty marker filter.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocksMeta {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocksMeta::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Named empty filter activating the standard Yellowstone `entry` family.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeEntryFilter {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeEntryFilter {
|
||||||
|
/// Creates the empty marker filter.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return Self { _private: () };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterEntry {
|
||||||
|
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterEntry::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider-neutral standard Yellowstone subscribe request owned by KSP.
|
||||||
|
///
|
||||||
|
/// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active
|
||||||
|
/// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven
|
||||||
|
/// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or future
|
||||||
|
/// filter payloads.
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeRequest {
|
||||||
|
accounts: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
|
||||||
|
slots: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeSlotFilter>,
|
||||||
|
transactions: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeTransactionFilter>,
|
||||||
|
transactions_status: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeTransactionFilter>,
|
||||||
|
blocks: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeBlockFilter>,
|
||||||
|
blocks_meta: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeBlocksMetaFilter>,
|
||||||
|
entry: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeEntryFilter>,
|
||||||
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||||
|
accounts_data_slice: std::vec::Vec<crate::YellowstoneAccountsDataSlice>,
|
||||||
|
ping: std::option::Option<crate::YellowstoneSubscribePing>,
|
||||||
|
from_slot: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YellowstoneSubscribeRequest {
|
||||||
|
/// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping state.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
return Self {
|
||||||
|
accounts: std::collections::BTreeMap::new(),
|
||||||
|
slots: std::collections::BTreeMap::new(),
|
||||||
|
transactions: std::collections::BTreeMap::new(),
|
||||||
|
transactions_status: std::collections::BTreeMap::new(),
|
||||||
|
blocks: std::collections::BTreeMap::new(),
|
||||||
|
blocks_meta: std::collections::BTreeMap::new(),
|
||||||
|
entry: std::collections::BTreeMap::new(),
|
||||||
|
commitment: std::option::Option::None,
|
||||||
|
accounts_data_slice: std::vec::Vec::new(),
|
||||||
|
ping: std::option::Option::None,
|
||||||
|
from_slot: std::option::Option::None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named account filter group.
|
||||||
|
pub fn insert_account_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeAccountFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.accounts.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named slot filter group.
|
||||||
|
pub fn insert_slot_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeSlotFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.slots.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named transaction filter group.
|
||||||
|
pub fn insert_transaction_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeTransactionFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.transactions.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named transaction-status filter group.
|
||||||
|
pub fn insert_transaction_status_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeTransactionFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.transactions_status.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named block filter group.
|
||||||
|
pub fn insert_block_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeBlockFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.blocks.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named empty `blocks_meta` filter group.
|
||||||
|
pub fn insert_blocks_meta_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeBlocksMetaFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.blocks_meta.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts one named empty `entry` filter group.
|
||||||
|
pub fn insert_entry_filter(
|
||||||
|
&mut self,
|
||||||
|
name: crate::YellowstoneSubscribeFilterName,
|
||||||
|
filter: crate::YellowstoneSubscribeEntryFilter,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
let validation = self.validate_new_filter_name(&name);
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
self.entry.insert(name, filter);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets or clears the optional standard Yellowstone commitment.
|
||||||
|
pub fn set_commitment(&mut self, commitment: std::option::Option<crate::SolanaCommitment>) {
|
||||||
|
self.commitment = commitment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds one validated account-data slice while preserving insertion order.
|
||||||
|
pub fn push_accounts_data_slice(&mut self, slice: crate::YellowstoneAccountsDataSlice) -> ksp_core_lib::Result<()> {
|
||||||
|
if self.accounts_data_slice.len() >= MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice count exceeds KSP bounds")
|
||||||
|
.with_context("field", "grpc_subscribe.accounts_data_slice"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.accounts_data_slice.push(slice);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets or clears the optional subscribe-stream ping mutation.
|
||||||
|
pub fn set_ping(&mut self, ping: std::option::Option<crate::YellowstoneSubscribePing>) {
|
||||||
|
self.ping = ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets or clears the optional replay starting slot.
|
||||||
|
pub fn set_from_slot(&mut self, from_slot: std::option::Option<u64>) {
|
||||||
|
self.from_slot = from_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active account filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn account_filter_count(&self) -> usize {
|
||||||
|
return self.accounts.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active slot filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn slot_filter_count(&self) -> usize {
|
||||||
|
return self.slots.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active transaction filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction_filter_count(&self) -> usize {
|
||||||
|
return self.transactions.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active transaction-status filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transaction_status_filter_count(&self) -> usize {
|
||||||
|
return self.transactions_status.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active block filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn block_filter_count(&self) -> usize {
|
||||||
|
return self.blocks.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active block-meta filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn blocks_meta_filter_count(&self) -> usize {
|
||||||
|
return self.blocks_meta.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of active entry filter groups.
|
||||||
|
#[must_use]
|
||||||
|
pub fn entry_filter_count(&self) -> usize {
|
||||||
|
return self.entry.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional request commitment.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||||
|
return self.commitment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns account-data slices in exact wire order.
|
||||||
|
#[must_use]
|
||||||
|
pub fn accounts_data_slices(&self) -> &[crate::YellowstoneAccountsDataSlice] {
|
||||||
|
return self.accounts_data_slice.as_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional subscribe-stream ping mutation.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn ping(&self) -> std::option::Option<crate::YellowstoneSubscribePing> {
|
||||||
|
return self.ping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional replay starting slot.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn from_slot(&self) -> std::option::Option<u64> {
|
||||||
|
return self.from_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates all deterministic common subscribe-request bounds before any network I/O.
|
||||||
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
||||||
|
if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter-group count exceeds KSP bounds")
|
||||||
|
.with_context("field", "grpc_subscribe.filter_groups"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self.accounts_data_slice.len() > MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone account-data slice count exceeds KSP bounds")
|
||||||
|
.with_context("field", "grpc_subscribe.accounts_data_slice"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_wire(&self) -> ksp_core_lib::Result<yellowstone_grpc_proto::geyser::SubscribeRequest> {
|
||||||
|
let validation = self.validate();
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(yellowstone_grpc_proto::geyser::SubscribeRequest {
|
||||||
|
accounts: self.accounts.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(),
|
||||||
|
slots: self.slots.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(),
|
||||||
|
transactions: self.transactions.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(),
|
||||||
|
transactions_status: self.transactions_status.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(),
|
||||||
|
blocks: self.blocks.iter().map(|(name, filter)| return (name.as_str().to_owned(), filter.to_wire())).collect(),
|
||||||
|
blocks_meta: self.blocks_meta.iter().map(|(name, filter)| return (name.as_str().to_owned(), (*filter).to_wire())).collect(),
|
||||||
|
entry: self.entry.iter().map(|(name, filter)| return (name.as_str().to_owned(), (*filter).to_wire())).collect(),
|
||||||
|
commitment: commitment_to_wire(self.commitment),
|
||||||
|
accounts_data_slice: self.accounts_data_slice.iter().copied().map(crate::YellowstoneAccountsDataSlice::to_wire).collect(),
|
||||||
|
ping: self.ping.map(crate::YellowstoneSubscribePing::to_wire),
|
||||||
|
from_slot: self.from_slot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_filter_count(&self) -> usize {
|
||||||
|
return self.accounts.len()
|
||||||
|
+ self.slots.len()
|
||||||
|
+ self.transactions.len()
|
||||||
|
+ self.transactions_status.len()
|
||||||
|
+ self.blocks.len()
|
||||||
|
+ self.blocks_meta.len()
|
||||||
|
+ self.entry.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_filter_name(&self, name: &crate::YellowstoneSubscribeFilterName) -> bool {
|
||||||
|
return self.accounts.contains_key(name)
|
||||||
|
|| self.slots.contains_key(name)
|
||||||
|
|| self.transactions.contains_key(name)
|
||||||
|
|| self.transactions_status.contains_key(name)
|
||||||
|
|| self.blocks.contains_key(name)
|
||||||
|
|| self.blocks_meta.contains_key(name)
|
||||||
|
|| self.entry.contains_key(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_new_filter_name(&self, name: &crate::YellowstoneSubscribeFilterName) -> ksp_core_lib::Result<()> {
|
||||||
|
if self.contains_filter_name(name) {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter name must be globally unique")
|
||||||
|
.with_context("field", "grpc_subscribe.filter_name"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self.total_filter_count() >= MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone subscribe filter-group count exceeds KSP bounds")
|
||||||
|
.with_context("field", "grpc_subscribe.filter_groups"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commitment_to_wire(commitment: std::option::Option<crate::SolanaCommitment>) -> std::option::Option<i32> {
|
||||||
|
return commitment.map(|value| {
|
||||||
|
return match value {
|
||||||
|
crate::SolanaCommitment::Processed => yellowstone_grpc_proto::geyser::CommitmentLevel::Processed as i32,
|
||||||
|
crate::SolanaCommitment::Confirmed => yellowstone_grpc_proto::geyser::CommitmentLevel::Confirmed as i32,
|
||||||
|
crate::SolanaCommitment::Finalized => yellowstone_grpc_proto::geyser::CommitmentLevel::Finalized as i32,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for YellowstoneSubscribeRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
return Self::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for YellowstoneSubscribeRequest {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter
|
||||||
|
.debug_struct("YellowstoneSubscribeRequest")
|
||||||
|
.field("account_filter_count", &self.accounts.len())
|
||||||
|
.field("slot_filter_count", &self.slots.len())
|
||||||
|
.field("transaction_filter_count", &self.transactions.len())
|
||||||
|
.field("transaction_status_filter_count", &self.transactions_status.len())
|
||||||
|
.field("block_filter_count", &self.blocks.len())
|
||||||
|
.field("blocks_meta_filter_count", &self.blocks_meta.len())
|
||||||
|
.field("entry_filter_count", &self.entry.len())
|
||||||
|
.field("commitment", &self.commitment)
|
||||||
|
.field("accounts_data_slice_count", &self.accounts_data_slice.len())
|
||||||
|
.field("ping", &self.ping)
|
||||||
|
.field("from_slot", &self.from_slot)
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/grpc_subscribe.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
// version: 36
|
// version: 37
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -34,7 +34,9 @@
|
|||||||
//! `0.2.9-pre.002` opens the Yellowstone gRPC N1 engine foundation with Transport-owned redacted settings, bounded reconnect/channel/message policies, the
|
//! `0.2.9-pre.002` opens the Yellowstone gRPC N1 engine foundation with Transport-owned redacted settings, bounded reconnect/channel/message policies, the
|
||||||
//! published Yellowstone protobuf dependency and a lazy Tonic HTTP/2 channel wrapper that exposes no raw Tonic or upstream protobuf types.
|
//! published Yellowstone protobuf dependency and a lazy Tonic HTTP/2 channel wrapper that exposes no raw Tonic or upstream protobuf types.
|
||||||
//! `0.2.9-pre.003` adds bounded TLS/WebPKI connection establishment, generic redacted ASCII request metadata and the seven standard Yellowstone unary RPCs
|
//! `0.2.9-pre.003` adds bounded TLS/WebPKI connection establishment, generic redacted ASCII request metadata and the seven standard Yellowstone unary RPCs
|
||||||
//! through KSP-owned DTOs. Streaming `Subscribe` remains deliberately deferred to `pre.004`.
|
//! through KSP-owned DTOs.
|
||||||
|
//! `0.2.9-pre.004` materializes the provider-neutral standard `SubscribeRequest` foundation: all seven named filter maps, global filter-name bounds/uniqueness,
|
||||||
|
//! commitment, ordered account-data slices, ping and `from_slot`. Family-specific account/slot/transaction/block filter fields remain staged for `pre.005–007`.
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod constants;
|
mod constants;
|
||||||
@@ -42,6 +44,7 @@ mod error;
|
|||||||
mod executor;
|
mod executor;
|
||||||
mod grpc_channel;
|
mod grpc_channel;
|
||||||
mod grpc_settings;
|
mod grpc_settings;
|
||||||
|
mod grpc_subscribe;
|
||||||
mod grpc_unary;
|
mod grpc_unary;
|
||||||
mod json_rpc;
|
mod json_rpc;
|
||||||
mod pool;
|
mod pool;
|
||||||
@@ -131,6 +134,26 @@ pub use self::grpc_settings::YellowstoneGrpcReconnectSettings;
|
|||||||
pub use self::grpc_settings::YellowstoneGrpcSessionSettings;
|
pub use self::grpc_settings::YellowstoneGrpcSessionSettings;
|
||||||
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
/// Complete runtime settings consumed by the KSP Yellowstone gRPC transport engine.
|
||||||
pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
|
pub use self::grpc_settings::YellowstoneGrpcTransportSettings;
|
||||||
|
/// One standard Yellowstone account-data slice.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneAccountsDataSlice;
|
||||||
|
/// Account-family filter-group shell for standard Yellowstone Subscribe.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeAccountFilter;
|
||||||
|
/// Block-family filter-group shell for standard Yellowstone Subscribe.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeBlockFilter;
|
||||||
|
/// Empty filter marker activating the standard Yellowstone blocks-meta family.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeBlocksMetaFilter;
|
||||||
|
/// Empty filter marker activating the standard Yellowstone entry family.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeEntryFilter;
|
||||||
|
/// Validated globally unique logical filter name for standard Yellowstone Subscribe maps.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeFilterName;
|
||||||
|
/// Optional ping mutation carried by the standard Yellowstone Subscribe request.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribePing;
|
||||||
|
/// Provider-neutral standard Yellowstone Subscribe request.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
|
||||||
|
/// Slot-family filter-group shell for standard Yellowstone Subscribe.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
|
||||||
|
/// Transaction-family filter-group shell shared by transactions and transaction-status maps.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeTransactionFilter;
|
||||||
/// Standard Solana Yellowstone unary facade over one KSP-owned physical gRPC channel.
|
/// Standard Solana Yellowstone unary facade over one KSP-owned physical gRPC channel.
|
||||||
pub use self::grpc_unary::SolanaYellowstoneGrpcUnaryClient;
|
pub use self::grpc_unary::SolanaYellowstoneGrpcUnaryClient;
|
||||||
/// Block height returned by the standard Yellowstone unary surface.
|
/// Block height returned by the standard Yellowstone unary surface.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
// version: 41
|
// version: 42
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -825,3 +825,44 @@ fn public_v0_2_9_pre_003_yellowstone_metadata_and_seven_unary_contracts_are_avai
|
|||||||
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.domain(), "onchain_transport");
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.domain(), "onchain_transport");
|
||||||
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.code(), "grpc_status");
|
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_GRPC_STATUS.code(), "grpc_status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_2_9_pre_004_yellowstone_subscribe_common_contract_is_available_from_crate_root() {
|
||||||
|
let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
||||||
|
let account_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("accounts").expect("account filter name must validate");
|
||||||
|
let slot_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("slots").expect("slot filter name must validate");
|
||||||
|
let transaction_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("transactions").expect("transaction filter name must validate");
|
||||||
|
let transaction_status_name =
|
||||||
|
ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("transaction-status").expect("transaction-status filter name must validate");
|
||||||
|
let block_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("blocks").expect("block filter name must validate");
|
||||||
|
let block_meta_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("blocks-meta").expect("block-meta filter name must validate");
|
||||||
|
let entry_name = ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("entry").expect("entry filter name must validate");
|
||||||
|
assert!(request.insert_account_filter(account_name, ksp_onchain_transport_lib::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_slot_filter(slot_name, ksp_onchain_transport_lib::YellowstoneSubscribeSlotFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_transaction_filter(transaction_name, ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter::new()).is_ok());
|
||||||
|
assert!(
|
||||||
|
request
|
||||||
|
.insert_transaction_status_filter(transaction_status_name, ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter::new())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(request.insert_block_filter(block_name, ksp_onchain_transport_lib::YellowstoneSubscribeBlockFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_blocks_meta_filter(block_meta_name, ksp_onchain_transport_lib::YellowstoneSubscribeBlocksMetaFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_entry_filter(entry_name, ksp_onchain_transport_lib::YellowstoneSubscribeEntryFilter::new()).is_ok());
|
||||||
|
request.set_commitment(std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
|
||||||
|
let slice = ksp_onchain_transport_lib::YellowstoneAccountsDataSlice::new(4, 32).expect("account-data slice must validate");
|
||||||
|
assert!(request.push_accounts_data_slice(slice).is_ok());
|
||||||
|
request.set_ping(std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneSubscribePing::new(7)));
|
||||||
|
request.set_from_slot(std::option::Option::Some(99));
|
||||||
|
assert!(request.validate().is_ok());
|
||||||
|
assert_eq!(request.account_filter_count(), 1);
|
||||||
|
assert_eq!(request.slot_filter_count(), 1);
|
||||||
|
assert_eq!(request.transaction_filter_count(), 1);
|
||||||
|
assert_eq!(request.transaction_status_filter_count(), 1);
|
||||||
|
assert_eq!(request.block_filter_count(), 1);
|
||||||
|
assert_eq!(request.blocks_meta_filter_count(), 1);
|
||||||
|
assert_eq!(request.entry_filter_count(), 1);
|
||||||
|
assert_eq!(request.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
|
||||||
|
assert_eq!(request.accounts_data_slices(), &[slice]);
|
||||||
|
assert_eq!(request.ping(), std::option::Option::Some(ksp_onchain_transport_lib::YellowstoneSubscribePing::new(7)));
|
||||||
|
assert_eq!(request.from_slot(), std::option::Option::Some(99));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
// version: 34
|
// version: 35
|
||||||
|
|
||||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||||
|
|
||||||
@@ -1066,3 +1066,48 @@ fn release_v0_2_9_pre_003_adds_tls_metadata_and_exactly_seven_standard_unary_met
|
|||||||
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||||
let _client = std::any::type_name::<ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient>();
|
let _client = std::any::type_name::<ksp_onchain_transport_lib::SolanaYellowstoneGrpcUnaryClient>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_9_pre_004_materializes_only_standard_subscribe_common_contract() {
|
||||||
|
let source = include_str!("../src/grpc_subscribe.rs");
|
||||||
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
|
for wire_field in [
|
||||||
|
"accounts:",
|
||||||
|
"slots:",
|
||||||
|
"transactions:",
|
||||||
|
"transactions_status:",
|
||||||
|
"blocks:",
|
||||||
|
"blocks_meta:",
|
||||||
|
"entry:",
|
||||||
|
"commitment:",
|
||||||
|
"accounts_data_slice:",
|
||||||
|
"ping:",
|
||||||
|
"from_slot:",
|
||||||
|
] {
|
||||||
|
assert!(source.contains(wire_field), "missing standard Yellowstone SubscribeRequest field: {wire_field}");
|
||||||
|
}
|
||||||
|
assert!(source.contains("MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT"));
|
||||||
|
assert!(source.contains("MAX_GRPC_SUBSCRIBE_FILTER_NAME_LENGTH_BYTES"));
|
||||||
|
assert!(source.contains("MAX_GRPC_SUBSCRIBE_DATA_SLICE_COUNT"));
|
||||||
|
assert!(source.contains("offset.checked_add(length)"));
|
||||||
|
assert!(source.contains("globally unique"));
|
||||||
|
assert!(source.contains("SubscribeRequestFilterBlocksMeta"));
|
||||||
|
assert!(source.contains("SubscribeRequestFilterEntry"));
|
||||||
|
assert!(source.contains("YellowstoneSubscribeAccountFilter"));
|
||||||
|
assert!(source.contains("YellowstoneSubscribeSlotFilter"));
|
||||||
|
assert!(source.contains("YellowstoneSubscribeTransactionFilter"));
|
||||||
|
assert!(source.contains("YellowstoneSubscribeBlockFilter"));
|
||||||
|
assert!(!source.contains("SubscribeDeshred"));
|
||||||
|
assert!(!source.contains("PublicNode"));
|
||||||
|
assert!(!source.contains("OrbitFlare"));
|
||||||
|
assert!(!source.contains("Helius"));
|
||||||
|
assert!(!source.contains("pub fn to_wire"));
|
||||||
|
assert!(!source.contains("pub(crate) fn to_wire"));
|
||||||
|
assert!(!crate_root.contains("pub use tonic"));
|
||||||
|
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||||
|
let _request = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeRequest>();
|
||||||
|
let _account = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeAccountFilter>();
|
||||||
|
let _slot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeSlotFilter>();
|
||||||
|
let _transaction = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeTransactionFilter>();
|
||||||
|
let _block = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeBlockFilter>();
|
||||||
|
}
|
||||||
|
|||||||
125
crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
Normal file
125
crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
||||||
|
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_filter_name_is_bounded_globally_unique_and_debug_redacted() {
|
||||||
|
assert!(crate::YellowstoneSubscribeFilterName::new("").is_err());
|
||||||
|
assert!(crate::YellowstoneSubscribeFilterName::new(" leading").is_err());
|
||||||
|
assert!(crate::YellowstoneSubscribeFilterName::new("trailing ").is_err());
|
||||||
|
assert!(crate::YellowstoneSubscribeFilterName::new("line\nbreak").is_err());
|
||||||
|
assert!(crate::YellowstoneSubscribeFilterName::new("x".repeat(129)).is_err());
|
||||||
|
let name = filter_name("account-primary");
|
||||||
|
assert_eq!(name.as_str(), "account-primary");
|
||||||
|
assert!(!format!("{name:?}").contains("account-primary"));
|
||||||
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
assert!(request.insert_account_filter(name.clone(), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
||||||
|
let duplicate = request.insert_slot_filter(name, crate::YellowstoneSubscribeSlotFilter::new());
|
||||||
|
assert!(duplicate.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_empty_and_named_empty_maps_encode_exactly() {
|
||||||
|
let empty = crate::YellowstoneSubscribeRequest::new().to_wire().expect("empty request must encode");
|
||||||
|
assert!(empty.accounts.is_empty());
|
||||||
|
assert!(empty.slots.is_empty());
|
||||||
|
assert!(empty.transactions.is_empty());
|
||||||
|
assert!(empty.transactions_status.is_empty());
|
||||||
|
assert!(empty.blocks.is_empty());
|
||||||
|
assert!(empty.blocks_meta.is_empty());
|
||||||
|
assert!(empty.entry.is_empty());
|
||||||
|
assert_eq!(empty.commitment, std::option::Option::None);
|
||||||
|
assert!(empty.accounts_data_slice.is_empty());
|
||||||
|
assert_eq!(empty.ping, std::option::Option::None);
|
||||||
|
assert_eq!(empty.from_slot, std::option::Option::None);
|
||||||
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
assert!(request.insert_account_filter(filter_name("accounts"), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_slot_filter(filter_name("slots"), crate::YellowstoneSubscribeSlotFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_transaction_filter(filter_name("transactions"), crate::YellowstoneSubscribeTransactionFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_transaction_status_filter(filter_name("transaction-status"), crate::YellowstoneSubscribeTransactionFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_block_filter(filter_name("blocks"), crate::YellowstoneSubscribeBlockFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_blocks_meta_filter(filter_name("blocks-meta"), crate::YellowstoneSubscribeBlocksMetaFilter::new()).is_ok());
|
||||||
|
assert!(request.insert_entry_filter(filter_name("entry"), crate::YellowstoneSubscribeEntryFilter::new()).is_ok());
|
||||||
|
let wire = request.to_wire().expect("named empty request must encode");
|
||||||
|
assert_eq!(wire.accounts.len(), 1);
|
||||||
|
assert_eq!(wire.slots.len(), 1);
|
||||||
|
assert_eq!(wire.transactions.len(), 1);
|
||||||
|
assert_eq!(wire.transactions_status.len(), 1);
|
||||||
|
assert_eq!(wire.blocks.len(), 1);
|
||||||
|
assert_eq!(wire.blocks_meta.len(), 1);
|
||||||
|
assert_eq!(wire.entry.len(), 1);
|
||||||
|
assert_eq!(wire.accounts.get("accounts"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterAccounts::default()));
|
||||||
|
assert_eq!(wire.slots.get("slots"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterSlots::default()));
|
||||||
|
assert_eq!(
|
||||||
|
wire.transactions.get("transactions"),
|
||||||
|
std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions::default())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wire.transactions_status.get("transaction-status"),
|
||||||
|
std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterTransactions::default())
|
||||||
|
);
|
||||||
|
assert_eq!(wire.blocks.get("blocks"), std::option::Option::Some(&yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks::default()));
|
||||||
|
assert!(wire.blocks_meta.contains_key("blocks-meta"));
|
||||||
|
assert!(wire.entry.contains_key("entry"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_common_fields_preserve_optional_and_ordered_wire_semantics() {
|
||||||
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
request.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
||||||
|
let first = crate::YellowstoneAccountsDataSlice::new(8, 16).expect("first slice must validate");
|
||||||
|
let second = crate::YellowstoneAccountsDataSlice::new(64, 0).expect("zero-length slice remains representable");
|
||||||
|
assert!(request.push_accounts_data_slice(first).is_ok());
|
||||||
|
assert!(request.push_accounts_data_slice(second).is_ok());
|
||||||
|
request.set_ping(std::option::Option::Some(crate::YellowstoneSubscribePing::new(-7)));
|
||||||
|
request.set_from_slot(std::option::Option::Some(42));
|
||||||
|
assert_eq!(request.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
||||||
|
assert_eq!(request.accounts_data_slices(), &[first, second]);
|
||||||
|
assert_eq!(request.ping(), std::option::Option::Some(crate::YellowstoneSubscribePing::new(-7)));
|
||||||
|
assert_eq!(request.from_slot(), std::option::Option::Some(42));
|
||||||
|
let wire = request.to_wire().expect("common fields must encode");
|
||||||
|
assert_eq!(wire.commitment, std::option::Option::Some(yellowstone_grpc_proto::geyser::CommitmentLevel::Finalized as i32));
|
||||||
|
assert_eq!(wire.accounts_data_slice.len(), 2);
|
||||||
|
assert_eq!(wire.accounts_data_slice[0].offset, 8);
|
||||||
|
assert_eq!(wire.accounts_data_slice[0].length, 16);
|
||||||
|
assert_eq!(wire.accounts_data_slice[1].offset, 64);
|
||||||
|
assert_eq!(wire.accounts_data_slice[1].length, 0);
|
||||||
|
assert_eq!(wire.ping.map(|ping| return ping.id), std::option::Option::Some(-7));
|
||||||
|
assert_eq!(wire.from_slot, std::option::Option::Some(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_common_bounds_reject_before_wire_conversion() {
|
||||||
|
assert!(crate::YellowstoneAccountsDataSlice::new(0, 64 * 1024 * 1024).is_ok());
|
||||||
|
assert!(crate::YellowstoneAccountsDataSlice::new(0, 64 * 1024 * 1024 + 1).is_err());
|
||||||
|
assert!(crate::YellowstoneAccountsDataSlice::new(u64::MAX, 1).is_err());
|
||||||
|
let mut slices = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
for index in 0..128_u64 {
|
||||||
|
let slice = crate::YellowstoneAccountsDataSlice::new(index, 1).expect("bounded fixture slice must validate");
|
||||||
|
assert!(slices.push_accounts_data_slice(slice).is_ok());
|
||||||
|
}
|
||||||
|
let excess = crate::YellowstoneAccountsDataSlice::new(129, 1).expect("excess fixture slice itself must validate");
|
||||||
|
assert!(slices.push_accounts_data_slice(excess).is_err());
|
||||||
|
let mut filters = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
for index in 0..1_024_u32 {
|
||||||
|
let name = crate::YellowstoneSubscribeFilterName::new(format!("f{index}")).expect("bounded filter name must validate");
|
||||||
|
assert!(filters.insert_account_filter(name, crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
||||||
|
}
|
||||||
|
let excess_name = crate::YellowstoneSubscribeFilterName::new("excess").expect("excess filter name must validate independently");
|
||||||
|
assert!(filters.insert_account_filter(excess_name, crate::YellowstoneSubscribeAccountFilter::new()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
|
||||||
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
assert!(request.insert_account_filter(filter_name("sensitive-label"), crate::YellowstoneSubscribeAccountFilter::new()).is_ok());
|
||||||
|
request.set_ping(std::option::Option::Some(crate::YellowstoneSubscribePing::new(9)));
|
||||||
|
request.set_from_slot(std::option::Option::Some(77));
|
||||||
|
let debug = format!("{request:?}");
|
||||||
|
assert!(!debug.contains("sensitive-label"));
|
||||||
|
assert!(debug.contains("account_filter_count"));
|
||||||
|
assert!(debug.contains("from_slot"));
|
||||||
|
}
|
||||||
250
deltas/0.2.9/pre.004.md
Normal file
250
deltas/0.2.9/pre.004.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
<!-- file: deltas/0.2.9/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.9-pre.004` — fondation `SubscribeRequest` Yellowstone standard
|
||||||
|
|
||||||
|
## 1. Base et signal de version
|
||||||
|
|
||||||
|
Base obligatoire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.9-pre.3.fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni ferme `pre.003-fix.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS / clean
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
Transport unit 354/354
|
||||||
|
Transport public_api 43/43
|
||||||
|
Transport release_completeness 36/36
|
||||||
|
Transport doctests 4/4
|
||||||
|
cargo tree -p ... fourni/relu
|
||||||
|
cargo tree -p ... -e features fourni/relu
|
||||||
|
cargo tree -p ... --duplicates fourni/relu
|
||||||
|
cargo tree --duplicates fourni/relu
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe pertinent reste unifié sur `tonic 0.14.6`, `tonic-prost 0.14.6`, `prost/prost-types 0.14.4`, `yellowstone-grpc-proto 12.6.0` et `solana-pubkey 4.3.0`.
|
||||||
|
|
||||||
|
Cette tranche non-fix synchronise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.9-pre.4
|
||||||
|
commit attendu = v0.2.9-pre.004
|
||||||
|
aucun tag prerelease
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Scope exact
|
||||||
|
|
||||||
|
`pre.004` matérialise uniquement la fondation N2 du `SubscribeRequest` standard publié :
|
||||||
|
|
||||||
|
```text
|
||||||
|
accounts
|
||||||
|
slots
|
||||||
|
transactions
|
||||||
|
transactions_status
|
||||||
|
blocks
|
||||||
|
blocks_meta
|
||||||
|
entry
|
||||||
|
commitment
|
||||||
|
accounts_data_slice
|
||||||
|
ping
|
||||||
|
from_slot
|
||||||
|
```
|
||||||
|
|
||||||
|
Le proto `yellowstone-grpc-proto 12.6.0` a été recontrôlé avant implémentation et conserve exactement ces champs top-level.
|
||||||
|
|
||||||
|
Sont explicitement hors tranche :
|
||||||
|
|
||||||
|
```text
|
||||||
|
champs détaillés Accounts / Slots -> pre.005
|
||||||
|
champs détaillés Transactions / transaction_status -> pre.006
|
||||||
|
champs détaillés Blocks / block_meta / entry updates -> pre.007
|
||||||
|
stream bidi / mutation / Ping-Pong runtime -> pre.008
|
||||||
|
reconnect / replay / gaps / duplicates -> pre.009
|
||||||
|
Config V3 / PublicNode -> pre.010/011
|
||||||
|
SubscribeDeshred -> OUT 0.2.9 standard
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Contrat KSP ajouté
|
||||||
|
|
||||||
|
Nouveaux types publics :
|
||||||
|
|
||||||
|
```text
|
||||||
|
YellowstoneSubscribeFilterName
|
||||||
|
YellowstoneAccountsDataSlice
|
||||||
|
YellowstoneSubscribePing
|
||||||
|
YellowstoneSubscribeAccountFilter
|
||||||
|
YellowstoneSubscribeSlotFilter
|
||||||
|
YellowstoneSubscribeTransactionFilter
|
||||||
|
YellowstoneSubscribeBlockFilter
|
||||||
|
YellowstoneSubscribeBlocksMetaFilter
|
||||||
|
YellowstoneSubscribeEntryFilter
|
||||||
|
YellowstoneSubscribeRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
Les quatre filtres de famille riches sont volontairement des shells typés en `pre.004`. Ils permettent de matérialiser les sept maps et les entrées nommées vides sans anticiper les champs propres aux tranches suivantes.
|
||||||
|
|
||||||
|
Le wire `yellowstone_grpc_proto::geyser::SubscribeRequest` reste privé : aucun type Tonic/Prost/Yellowstone n'est réexporté au crate root.
|
||||||
|
|
||||||
|
## 4. Sémantique maps / noms
|
||||||
|
|
||||||
|
KSP conserve sept maps logiques indépendantes.
|
||||||
|
|
||||||
|
```text
|
||||||
|
map vide = aucun filtre actif dans la famille
|
||||||
|
entrée nommée + message vide = activation explicite d'un groupe vide supporté par le proto
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Protobuf ne distingue pas une map top-level omise d'une map vide ; KSP ne prétend donc pas préserver une différence wire inexistante.
|
||||||
|
|
||||||
|
Les noms sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
non vides
|
||||||
|
trim exact
|
||||||
|
sans caractère de contrôle
|
||||||
|
<= 128 octets UTF-8
|
||||||
|
uniques globalement entre les sept maps
|
||||||
|
```
|
||||||
|
|
||||||
|
L'unicité globale évite l'ambiguïté lorsque Yellowstone renvoie seulement les noms correspondants dans `SubscribeUpdate.filters[]`.
|
||||||
|
|
||||||
|
Le nombre total de groupes nommés est plafonné à `1024` sur l'ensemble des sept maps.
|
||||||
|
|
||||||
|
## 5. Champs communs et bounds
|
||||||
|
|
||||||
|
`commitment` réutilise `SolanaCommitment` et mappe exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Processed -> PROCESSED
|
||||||
|
Confirmed -> CONFIRMED
|
||||||
|
Finalized -> FINALIZED
|
||||||
|
```
|
||||||
|
|
||||||
|
`accounts_data_slice` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ordre d'insertion conservé
|
||||||
|
nombre <= 128
|
||||||
|
length <= 64 MiB
|
||||||
|
offset + length doit rester représentable en u64
|
||||||
|
length = 0 reste représentable
|
||||||
|
```
|
||||||
|
|
||||||
|
`ping` conserve l'identifiant `i32` exact.
|
||||||
|
|
||||||
|
`from_slot` conserve l'optional `u64` sans lui attribuer encore une promesse de replay/lossless ; sa sémantique lifecycle reste en `pre.009`.
|
||||||
|
|
||||||
|
## 6. Diagnostics et sécurité
|
||||||
|
|
||||||
|
`Debug` de `YellowstoneSubscribeFilterName` est redacted.
|
||||||
|
|
||||||
|
`Debug` de `YellowstoneSubscribeRequest` expose uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
compteurs par famille
|
||||||
|
commitment
|
||||||
|
nombre de data slices
|
||||||
|
ping
|
||||||
|
from_slot
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'expose aucun nom de filtre ni futur payload account/transaction/block.
|
||||||
|
|
||||||
|
Les erreurs déterministes utilisent `ERROR_CODE_INVALID_RPC_PARAMETERS` et n'incluent pas les valeurs de noms/payloads rejetés.
|
||||||
|
|
||||||
|
## 7. Dépendances
|
||||||
|
|
||||||
|
Aucune dépendance ni feature Cargo n'est modifiée dans `pre.004`.
|
||||||
|
|
||||||
|
Les graphes fournis pour `pre.003-fix.001` restent l'autorité courante ; aucun nouveau `cargo tree` n'est requis spécifiquement par ce delta sauf anomalie de compilation.
|
||||||
|
|
||||||
|
## 8. Tests/canaries ajoutés
|
||||||
|
|
||||||
|
Unit tests :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nom vide/whitespace/control/oversized rejeté
|
||||||
|
Debug nom redacted
|
||||||
|
unicité globale cross-family
|
||||||
|
request vide -> sept maps vides + common absent
|
||||||
|
sept entrées nommées vides -> wire exact
|
||||||
|
commitment/ping/from_slot exacts
|
||||||
|
ordre data slices conservé
|
||||||
|
zero-length slice conservée
|
||||||
|
slice length/overflow/count bornés
|
||||||
|
filter-group count borné
|
||||||
|
request Debug sans filter name
|
||||||
|
```
|
||||||
|
|
||||||
|
Public API : construction des sept maps et champs communs depuis le crate root.
|
||||||
|
|
||||||
|
Release completeness :
|
||||||
|
|
||||||
|
```text
|
||||||
|
onze champs top-level présents dans l'adapter
|
||||||
|
bounds communs matérialisés
|
||||||
|
aucun SubscribeDeshred
|
||||||
|
aucun PublicNode/OrbitFlare/Helius runtime
|
||||||
|
aucun raw Tonic/Yellowstone reexport
|
||||||
|
to_wire protobuf non public
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||||
|
deltas/0.2.9/pre.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md
|
||||||
|
docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Validation de préparation
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit: clean
|
||||||
|
```
|
||||||
|
|
||||||
|
L'environnement de préparation ne fournit pas Cargo/Rust ; aucune compilation n'est déclarée réussie localement.
|
||||||
|
|
||||||
|
## 12. Gate opérateur attendu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-core-lib --test workspace_dependencies
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Attendu si aucun fix n'est nécessaire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport unit 359
|
||||||
|
Transport public_api 44
|
||||||
|
Transport release_completeness 37
|
||||||
|
Transport doctests 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Les live smokes restent ignored/opt-in.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
|
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
|
||||||
<!-- version: 9 -->
|
<!-- version: 10 -->
|
||||||
|
|
||||||
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
|
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
|
||||||
|
|
||||||
> **Statut : `0.2.9-pre.003-fix.001` — `pre.002-fix.002` est fermée sur gate opérateur intégralement vert. Le premier gate `pre.003` confirme fmt/audit/check, les 354 unit tests Transport, 43 public API, 36 completeness, 4 doctests, le dependency canary et le workspace complet ; seul Clippy échoue sur `implicit_return` dans la fixture `#[tonic::async_trait]` et deux closures metadata. `fix.001` corrige uniquement cette conformité de test, sans changement du moteur/TLS/metadata/unary de production. `pre.003` ajoute uniquement TLS client, metadata générique redacted, connexion HTTP/2 réelle, fixture Geyser locale et les sept unary standard ; `Subscribe`, PublicNode et Config V3 restent hors tranche. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 15–20 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
|
> **Statut : `0.2.9-pre.004` candidate. `pre.003-fix.001` est fermée sur gate opérateur intégralement vert : fmt/audit/check/Clippy/workspace PASS, Transport 354 unit + 43 public API + 36 completeness + 4 doctests, et graphes Cargo inspectés sans seconde version Tonic/Prost/Solana. `pre.004` matérialise uniquement la fondation `SubscribeRequest` standard : sept maps nommées, commitment, account-data slices ordonnées, ping, `from_slot`, bornes communes, unicité globale des noms et Debug sans payload arbitraire. Les champs détaillés Accounts/Slots/Transactions/Blocks restent réservés à `pre.005–007` et aucun stream bidi n'est encore ouvert. PublicNode et Config V3 restent hors tranche. `0.2.9` reste bornée à un moteur client Yellowstone partagé, une façade Solana Yellowstone standard et une première intégration concrète PublicNode. Seuls OrbitFlare puis Helius LaserStream gRPC sont actuellement planifiés comme releases provider suivantes ; les autres providers restent en TODO/IDEAS sans numéro réservé. Chaque prerelease vise 15–20 minutes de travail effectif et la release complète doit rester clôturable dans une seule session de chat.**
|
||||||
|
|
||||||
## 1. Objet, base et état d'ouverture
|
## 1. Objet, base et état d'ouverture
|
||||||
|
|
||||||
@@ -431,7 +431,18 @@ ping? id
|
|||||||
from_slot? u64
|
from_slot? u64
|
||||||
```
|
```
|
||||||
|
|
||||||
KSP appliquera des bornes déterministes avant I/O sur les noms, cardinalités, listes de comptes/owners, memcmp, data slices et tailles de payload. Les valeurs exactes sont un contrat KSP et non une copie aveugle des quotas d'un provider ; elles seront matérialisées avec tests en `pre.004`.
|
KSP applique des bornes déterministes avant I/O sur les noms, cardinalités, listes de comptes/owners, memcmp, data slices et tailles de payload. Les valeurs exactes sont un contrat KSP et non une copie aveugle des quotas d'un provider. `pre.004` matérialise les bornes communes suivantes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
filter groups nommés, total <= 1024 sur les sept maps
|
||||||
|
filter name non vide, trim exact, sans caractère de contrôle, <= 128 octets
|
||||||
|
filter names uniques globalement entre les sept maps
|
||||||
|
accounts_data_slice count <= 128
|
||||||
|
accounts_data_slice length <= 64 MiB
|
||||||
|
offset + length aucun overflow u64
|
||||||
|
```
|
||||||
|
|
||||||
|
Les bornes account/owner/include/exclude/required, memcmp et Cuckoo restent volontairement dans les tranches de famille `pre.005–007`.
|
||||||
|
|
||||||
## 8. Matrice `SubscribeUpdate`
|
## 8. Matrice `SubscribeUpdate`
|
||||||
|
|
||||||
@@ -905,11 +916,11 @@ pre.001 DONE — audit upstream/service/proto + providers gratuits + licences/d
|
|||||||
pre.002 DONE — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
|
pre.002 DONE — moteur Yellowstone : proto/dependencies + settings/errors + channel minimal
|
||||||
budget : 15–20 min ; gate final fix.002 : fmt/audit/check/Clippy + Transport 346/42/35/4 + dependency canary + workspace PASS
|
budget : 15–20 min ; gate final fix.002 : fmt/audit/check/Clippy + Transport 346/42/35/4 + dependency canary + workspace PASS
|
||||||
|
|
||||||
pre.003 FIX.001 CANDIDATE — moteur TLS/metadata + façade N2 unary + fixture locale + 7 unary RPCs
|
pre.003 DONE — moteur TLS/metadata + façade N2 unary + fixture locale + 7 unary RPCs
|
||||||
budget : 15–20 min ; preuve cible : connect/TLS/timeouts/Status safe + metadata redacted + wire unary exact + cargo tree
|
budget : 15–20 min ; gate final fix.001 : fmt/audit/check/Clippy/workspace PASS + graphes Cargo inspectés
|
||||||
|
|
||||||
pre.004 standard Solana : Subscribe foundation + maps/commitment/ping/from_slot/data slices/bounds
|
pre.004 CANDIDATE — standard Solana : Subscribe foundation + maps/commitment/ping/from_slot/data slices/bounds
|
||||||
budget : 15–20 min ; preuve : omitted/empty/oneof exact + rejets avant I/O
|
budget : 15–20 min ; preuve : map vide/entrée nommée vide/common wire exact + rejets déterministes avant I/O
|
||||||
|
|
||||||
pre.005 standard Solana : Accounts + Slots filters/updates
|
pre.005 standard Solana : Accounts + Slots filters/updates
|
||||||
budget : 15–20 min ; preuve : fixtures exactes + enum/optional/malformed/adversarial
|
budget : 15–20 min ; preuve : fixtures exactes + enum/optional/malformed/adversarial
|
||||||
@@ -973,14 +984,16 @@ crates/ksp-onchain-transport-lib/unit_tests/grpc_unary.rs
|
|||||||
crates/ksp-onchain-transport-lib/src/grpc_channel.rs
|
crates/ksp-onchain-transport-lib/src/grpc_channel.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
crates/ksp-onchain-transport-lib/src/grpc_settings.rs
|
||||||
|
|
||||||
# pre.004+
|
# pre.004
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_session.rs
|
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_protocol.rs
|
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
crates/ksp-onchain-transport-lib/src/grpc_updates.rs
|
crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||||
unit_tests/ correspondants
|
|
||||||
tests/public_api.rs
|
tests/public_api.rs
|
||||||
tests/release_completeness.rs
|
tests/release_completeness.rs
|
||||||
|
|
||||||
|
# pre.005+
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_session.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_updates.rs
|
||||||
|
unit_tests/ correspondants
|
||||||
tests/yellowstone_grpc_*_smoke.rs
|
tests/yellowstone_grpc_*_smoke.rs
|
||||||
crates/ksp-config-lib/src/transport.rs
|
crates/ksp-config-lib/src/transport.rs
|
||||||
config/std.transport.json
|
config/std.transport.json
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
|
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
|
||||||
|
|
||||||
> **Statut : `pre.002-fix.002` est fermé. Le premier gate `pre.003` confirme fmt/audit/check, Transport 354 unit + 43 public API + 36 release completeness + 4 doctests, Core dependency canary et workspace complet ; seul Clippy échoue sur 11 diagnostics `implicit_return` dans la fixture unary. `0.2.9-pre.003-fix.001` corrige uniquement ces diagnostics de test, sans changement runtime. TLS client, metadata générique redacted, connexion réelle et exactement sept unary Yellowstone standard restent la surface `pre.003` ; `Subscribe`, PublicNode et Config V3 restent hors tranche. OrbitFlare et Helius sont les seules releases provider suivantes planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
|
> **Statut : `pre.003-fix.001` est fermé sur gate opérateur intégralement vert et graphes Cargo inspectés. `0.2.9-pre.004` est candidate et ajoute uniquement la fondation standard `SubscribeRequest` : sept maps, commitment, account-data slices, ping, `from_slot`, bornes communes et redaction Debug. Aucun stream bidi n'est encore ouvert ; les filtres détaillés Accounts/Slots/Transactions/Blocks restent réservés à `pre.005–007`. PublicNode et Config V3 restent hors tranche. OrbitFlare et Helius sont les seules releases provider suivantes planifiées ; les autres providers restent en TODO/IDEAS sans numéro réservé.**
|
||||||
|
|
||||||
## 1. Autorités du gate
|
## 1. Autorités du gate
|
||||||
|
|
||||||
@@ -55,17 +55,17 @@ crate proto : 12.6.0
|
|||||||
|
|
||||||
## 3. Matrice service `Geyser`
|
## 3. Matrice service `Geyser`
|
||||||
|
|
||||||
| RPC | Forme | Classification | Scope | Preuve cible | État |
|
| RPC | Forme | Classification | Scope | Preuve cible | État |
|
||||||
|-----------------------|-------|------------------------------------------------------|-------|-----------------------|------|
|
|-----------------------|-------|------------------------------------------------------|-------|-----------------------|-----------------------------------------------|
|
||||||
| `Subscribe` | bidi | standard | IN | fixture locale + live | TODO |
|
| `Subscribe` | bidi | standard | IN | fixture locale + live | PARTIAL pre.004 request / stream TODO pre.008 |
|
||||||
| `SubscribeDeshred` | bidi | Triton extension/pré-exécution malgré présence proto | OUT | canari d'absence/API | TODO |
|
| `SubscribeDeshred` | bidi | Triton extension/pré-exécution malgré présence proto | OUT | canari d'absence/API | OUT |
|
||||||
| `SubscribeReplayInfo` | unary | standard | IN | fixture unary | TODO |
|
| `SubscribeReplayInfo` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `Ping` | unary | standard | IN | fixture unary | TODO |
|
| `Ping` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `GetLatestBlockhash` | unary | standard | IN | fixture unary | TODO |
|
| `GetLatestBlockhash` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `GetBlockHeight` | unary | standard | IN | fixture unary | TODO |
|
| `GetBlockHeight` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `GetSlot` | unary | standard | IN | fixture unary | TODO |
|
| `GetSlot` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `IsBlockhashValid` | unary | standard | IN | fixture unary | TODO |
|
| `IsBlockhashValid` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
| `GetVersion` | unary | standard | IN | fixture unary | TODO |
|
| `GetVersion` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||||
|
|
||||||
## 4. `SubscribeRequest` — coverage normative
|
## 4. `SubscribeRequest` — coverage normative
|
||||||
|
|
||||||
@@ -265,11 +265,11 @@ DONE/source+tests ajoutés close timeout > 0 et plafonné
|
|||||||
DONE/source+tests ajoutés max inbound/outbound > 0 et plafonnés
|
DONE/source+tests ajoutés max inbound/outbound > 0 et plafonnés
|
||||||
DONE/source+tests ajoutés request/update queue capacities > 0 et plafonnées
|
DONE/source+tests ajoutés request/update queue capacities > 0 et plafonnées
|
||||||
DONE/source+tests ajoutés reconnect attempt/backoff bornés
|
DONE/source+tests ajoutés reconnect attempt/backoff bornés
|
||||||
TODO pre.004 filter-group count
|
CANDIDATE pre.004 filter-group count <= 1024 total sur les sept maps
|
||||||
TODO pre.004 filter-name length/uniqueness
|
CANDIDATE pre.004 filter-name non vide/trim/control-free <= 128 octets + unicité globale
|
||||||
DONE/source+tests ajoutés metadata ASCII key/value count/size + reserved/bin bounds
|
DONE/source+tests ajoutés metadata ASCII key/value count/size + reserved/bin bounds
|
||||||
TODO pre.005+ account/owner/include/exclude/required counts
|
TODO pre.005+ account/owner/include/exclude/required counts
|
||||||
TODO pre.004+ memcmp/data slice bounds
|
CANDIDATE pre.004 data slice count <= 128, length <= 64 MiB, offset+length sans overflow ; memcmp TODO pre.005
|
||||||
TODO pre.005+ Cuckoo dimensions/data bounds
|
TODO pre.005+ Cuckoo dimensions/data bounds
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -279,32 +279,32 @@ Security canaries :
|
|||||||
DONE/source+tests ajoutés endpoint URL Debug redacted
|
DONE/source+tests ajoutés endpoint URL Debug redacted
|
||||||
DONE/source+tests ajoutés metadata publique/secrète redacted ; secret marqué sensitive
|
DONE/source+tests ajoutés metadata publique/secrète redacted ; secret marqué sensitive
|
||||||
DONE/source+fixture Status message/details/metadata distants exclus des KspError
|
DONE/source+fixture Status message/details/metadata distants exclus des KspError
|
||||||
CANDIDATE pre.003 connect réel + TLS WebPKI configuré ; erreur connect/TLS safe, Cargo pending
|
DONE pre.003/fix.001 connect réel + TLS WebPKI configuré ; erreur connect/TLS safe, gate Cargo PASS
|
||||||
TODO pre.004+ request/update Debug sans payload arbitraire sensible
|
CANDIDATE pre.004 SubscribeRequest Debug sans filter names/payload arbitraire ; update Debug TODO pre.005+
|
||||||
DONE pre.003 source/tests channel/client Debug sans URL, metadata value ni raw Tonic ; lifecycle stream ultérieur
|
DONE pre.003 source/tests channel/client Debug sans URL, metadata value ni raw Tonic ; lifecycle stream ultérieur
|
||||||
```
|
```
|
||||||
|
|
||||||
## 11. Lifecycle / backpressure / replay
|
## 11. Lifecycle / backpressure / replay
|
||||||
|
|
||||||
| Cas | Attendu | État |
|
| Cas | Attendu | État |
|
||||||
|--------------------------------------------|------------------------------------------|-----------|
|
|--------------------------------------------|------------------------------------------|-----------------------------------|
|
||||||
| stream open | session bornée | TODO |
|
| stream open | session bornée | TODO |
|
||||||
| request mutation | ordre déterministe | TODO |
|
| request mutation | ordre déterministe | TODO |
|
||||||
| server Ping -> client request ping -> Pong | explicite | TODO |
|
| server Ping -> client request ping -> Pong | explicite | TODO |
|
||||||
| server half-close | terminal/reconnect selon policy | TODO |
|
| server half-close | terminal/reconnect selon policy | TODO |
|
||||||
| client close | cleanup borné | TODO |
|
| client close | cleanup borné | TODO |
|
||||||
| receiver drop | cleanup capacité | TODO |
|
| receiver drop | cleanup capacité | TODO |
|
||||||
| slow subscription | pas de queue infinie | TODO |
|
| slow subscription | pas de queue infinie | TODO |
|
||||||
| inbound oversized | rejet avant allocation excessive | TODO |
|
| inbound oversized | rejet avant allocation excessive | TODO |
|
||||||
| outbound oversized | rejet avant write | TODO |
|
| outbound oversized | rejet avant write | TODO |
|
||||||
| reconnect budget | borné | TODO |
|
| reconnect budget | borné | TODO |
|
||||||
| resubscribe order | déterministe | TODO |
|
| resubscribe order | déterministe | TODO |
|
||||||
| `from_slot` | utilisé sans promesse lossless | TODO |
|
| `from_slot` | utilisé sans promesse lossless | TODO |
|
||||||
| ReplayInfo | informatif | CANDIDATE |
|
| ReplayInfo | informatif | DONE unary ; usage reconnect TODO |
|
||||||
| duplicates | observables | TODO |
|
| duplicates | observables | TODO |
|
||||||
| gaps | observables | TODO |
|
| gaps | observables | TODO |
|
||||||
| divergent node history | couverture documentée | TODO |
|
| divergent node history | couverture documentée | TODO |
|
||||||
| shutdown during reconnect | aucune nouvelle connexion après shutdown | TODO |
|
| shutdown during reconnect | aucune nouvelle connexion après shutdown | TODO |
|
||||||
|
|
||||||
Claims interdits sans nouvelle preuve :
|
Claims interdits sans nouvelle preuve :
|
||||||
|
|
||||||
@@ -404,8 +404,8 @@ Les autres providers restent en TODO/IDEAS sans release dédiée. Aucune façade
|
|||||||
```text
|
```text
|
||||||
pre.001 DONE audit/sizing/architecture 15–20 min nominal
|
pre.001 DONE audit/sizing/architecture 15–20 min nominal
|
||||||
pre.002 DONE moteur: deps/settings/errors/channel 15–20 min ; gate final fix.002 PASS
|
pre.002 DONE moteur: deps/settings/errors/channel 15–20 min ; gate final fix.002 PASS
|
||||||
pre.003 CANDIDATE TLS/metadata + fixture + 7 unary standard 15–20 min
|
pre.003 DONE TLS/metadata + fixture + 7 unary standard 15–20 min ; gate final fix.001 PASS
|
||||||
pre.004 TODO standard: Subscribe common/from_slot/bounds 15–20 min
|
pre.004 CANDIDATE standard: Subscribe common/from_slot/bounds 15–20 min
|
||||||
pre.005 TODO standard: accounts + slots 15–20 min
|
pre.005 TODO standard: accounts + slots 15–20 min
|
||||||
pre.006 TODO standard: transactions + transaction_status 15–20 min
|
pre.006 TODO standard: transactions + transaction_status 15–20 min
|
||||||
pre.007 TODO standard: blocks + block_meta + entry 15–20 min
|
pre.007 TODO standard: blocks + block_meta + entry 15–20 min
|
||||||
@@ -583,3 +583,51 @@ aucun Subscribe/PublicNode/Config V3 anticipé
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Verdict `pre.003-fix.001` : correctif Clippy minimal prêt ; fermeture de `pre.003` après réexécution verte des gates habituels et inspection des graphes Cargo demandée par la tranche.**
|
**Verdict `pre.003-fix.001` : correctif Clippy minimal prêt ; fermeture de `pre.003` après réexécution verte des gates habituels et inspection des graphes Cargo demandée par la tranche.**
|
||||||
|
|
||||||
|
### 18.2 Gate final opérateur `pre.003-fix.001`
|
||||||
|
|
||||||
|
| Gate | Résultat final |
|
||||||
|
|-------------------------------------------|----------------|
|
||||||
|
| `cargo fmt --all` | PASS |
|
||||||
|
| audit Rust workspace | PASS / clean |
|
||||||
|
| `cargo check --workspace` | PASS |
|
||||||
|
| `cargo clippy --workspace --all-targets` | PASS |
|
||||||
|
| Transport unit | 354/354 PASS |
|
||||||
|
| Transport `public_api` | 43/43 PASS |
|
||||||
|
| Transport `release_completeness` | 36/36 PASS |
|
||||||
|
| Transport doctests | 4/4 PASS |
|
||||||
|
| `cargo test --workspace` | PASS |
|
||||||
|
| `cargo tree -p ...` | fourni/relu |
|
||||||
|
| `cargo tree -p ... -e features` | fourni/relu |
|
||||||
|
| `cargo tree -p ... --duplicates` | fourni/relu |
|
||||||
|
| `cargo tree --duplicates` | fourni/relu |
|
||||||
|
|
||||||
|
Graphe pertinent confirmé : `tonic 0.14.6`, `tonic-prost 0.14.6`, `prost/prost-types 0.14.4`, `yellowstone-grpc-proto 12.6.0`, `solana-pubkey 4.3.0`. Les features serveur/codegen et `yellowstone-grpc-proto/tonic` restent liées à la fixture dev/test ; aucune seconde version Tonic/Prost/Solana n'est introduite par la tranche.
|
||||||
|
|
||||||
|
**Verdict : `pre.003` fermée.**
|
||||||
|
|
||||||
|
## 19. Gate `pre.004` — candidate
|
||||||
|
|
||||||
|
| Surface | État candidate |
|
||||||
|
|-----------------------------------------------------|----------------|
|
||||||
|
| workspace version | `0.2.9-pre.4` |
|
||||||
|
| sept maps `SubscribeRequest` | SOURCE PASS |
|
||||||
|
| map vide / entrée nommée vide | SOURCE+TEST |
|
||||||
|
| commitment optional exact | SOURCE+TEST |
|
||||||
|
| account-data slices ordonnées | SOURCE+TEST |
|
||||||
|
| ping optional exact | SOURCE+TEST |
|
||||||
|
| `from_slot` optional exact | SOURCE+TEST |
|
||||||
|
| filter names <=128 octets / unicité globale | SOURCE+TEST |
|
||||||
|
| filter groups <=1024 total | SOURCE+TEST |
|
||||||
|
| slices <=128 / length <=64 MiB / overflow rejeté | SOURCE+TEST |
|
||||||
|
| request Debug sans noms/payloads de filtres | SOURCE+TEST |
|
||||||
|
| filtres Accounts/Slots détaillés | OUT pre.004 |
|
||||||
|
| filtres Transactions/Blocks détaillés | OUT pre.004 |
|
||||||
|
| stream bidi / lifecycle / updates | OUT pre.004 |
|
||||||
|
| PublicNode / Config V3 | OUT pre.004 |
|
||||||
|
| audit Rust workspace local | PASS / clean |
|
||||||
|
| fmt/check/Clippy/tests | opérateur TODO |
|
||||||
|
|
||||||
|
Le proto publié `yellowstone-grpc-proto 12.6.0` a été recontrôlé avant implémentation : les onze champs top-level retenus restent `accounts`, `slots`, `transactions`, `transactions_status`, `blocks`, `blocks_meta`, `entry`, `commitment`, `accounts_data_slice`, `ping`, `from_slot`. Aucun `SubscribeDeshred` n'entre dans la surface KSP.
|
||||||
|
|
||||||
|
**Verdict `pre.004` : candidate source prête ; fermeture après gate Cargo opérateur.**
|
||||||
|
|||||||
Reference in New Issue
Block a user