v0.2.9-pre.008
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 246
|
||||
# version: 247
|
||||
|
||||
[workspace]
|
||||
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"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.9-pre.7"
|
||||
version = "0.2.9-pre.8"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#[cfg(test)]
|
||||
const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128;
|
||||
#[cfg(test)]
|
||||
const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536;
|
||||
#[cfg(test)]
|
||||
const MAX_GRPC_SUBSCRIBE_ACCOUNT_DATA_LENGTH_BYTES: usize = 512 * 1024 * 1024;
|
||||
const MAX_GRPC_SUBSCRIBE_ACCOUNT_PREDICATE_COUNT: usize = 256;
|
||||
@@ -1945,24 +1949,478 @@ impl std::fmt::Debug for YellowstoneTransactionStatusUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.008`.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
/// Complete current block-family filter for the standard Yellowstone subscribe surface.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneSubscribeBlockFilter {
|
||||
_private: (),
|
||||
account_include: std::vec::Vec<ksp_core_lib::Pubkey>,
|
||||
include_transactions: std::option::Option<bool>,
|
||||
include_accounts: std::option::Option<bool>,
|
||||
include_entries: std::option::Option<bool>,
|
||||
cuckoo_account_include: std::option::Option<crate::YellowstoneCuckooFilter>,
|
||||
}
|
||||
|
||||
impl YellowstoneSubscribeBlockFilter {
|
||||
/// Creates an empty block filter group.
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
return Self { _private: () };
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
account_include: std::vec::Vec::new(),
|
||||
include_transactions: std::option::Option::None,
|
||||
include_accounts: std::option::Option::None,
|
||||
include_entries: std::option::Option::None,
|
||||
cuckoo_account_include: std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds one account include selector while preserving insertion order.
|
||||
pub fn push_account_include(&mut self, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> {
|
||||
return push_block_selector(&mut self.account_include, value);
|
||||
}
|
||||
|
||||
/// Sets or clears whether full transaction payloads are included in matching block updates.
|
||||
pub fn set_include_transactions(&mut self, value: std::option::Option<bool>) {
|
||||
self.include_transactions = value;
|
||||
}
|
||||
|
||||
/// Sets or clears whether account payloads are included in matching block updates.
|
||||
pub fn set_include_accounts(&mut self, value: std::option::Option<bool>) {
|
||||
self.include_accounts = value;
|
||||
}
|
||||
|
||||
/// Sets or clears whether entry payloads are included in matching block updates.
|
||||
pub fn set_include_entries(&mut self, value: std::option::Option<bool>) {
|
||||
self.include_entries = value;
|
||||
}
|
||||
|
||||
/// Sets or clears the optional compressed account-include filter.
|
||||
pub fn set_cuckoo_account_include(&mut self, value: std::option::Option<crate::YellowstoneCuckooFilter>) {
|
||||
self.cuckoo_account_include = value;
|
||||
}
|
||||
|
||||
/// Returns ordered account include selectors.
|
||||
#[must_use]
|
||||
pub fn account_include(&self) -> &[ksp_core_lib::Pubkey] {
|
||||
return self.account_include.as_slice();
|
||||
}
|
||||
|
||||
/// Returns the optional transaction-payload inclusion flag.
|
||||
#[must_use]
|
||||
pub const fn include_transactions(&self) -> std::option::Option<bool> {
|
||||
return self.include_transactions;
|
||||
}
|
||||
|
||||
/// Returns the optional account-payload inclusion flag.
|
||||
#[must_use]
|
||||
pub const fn include_accounts(&self) -> std::option::Option<bool> {
|
||||
return self.include_accounts;
|
||||
}
|
||||
|
||||
/// Returns the optional entry-payload inclusion flag.
|
||||
#[must_use]
|
||||
pub const fn include_entries(&self) -> std::option::Option<bool> {
|
||||
return self.include_entries;
|
||||
}
|
||||
|
||||
/// Returns the optional compressed account-include filter.
|
||||
#[must_use]
|
||||
pub const fn cuckoo_account_include(&self) -> std::option::Option<&crate::YellowstoneCuckooFilter> {
|
||||
return self.cuckoo_account_include.as_ref();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_wire(&self) -> yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks {
|
||||
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks::default();
|
||||
return yellowstone_grpc_proto::geyser::SubscribeRequestFilterBlocks {
|
||||
account_include: self.account_include.iter().map(std::string::ToString::to_string).collect(),
|
||||
include_transactions: self.include_transactions,
|
||||
include_accounts: self.include_accounts,
|
||||
include_entries: self.include_entries,
|
||||
cuckoo_account_include: self.cuckoo_account_include.as_ref().map(crate::YellowstoneCuckooFilter::to_wire),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for YellowstoneSubscribeBlockFilter {
|
||||
fn default() -> Self {
|
||||
return Self::new();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneSubscribeBlockFilter {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneSubscribeBlockFilter")
|
||||
.field("account_include_count", &self.account_include.len())
|
||||
.field("include_transactions", &self.include_transactions)
|
||||
.field("include_accounts", &self.include_accounts)
|
||||
.field("include_entries", &self.include_entries)
|
||||
.field("has_cuckoo_account_include", &self.cuckoo_account_include.is_some())
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewards container carried by Yellowstone block and block-meta updates.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneBlockRewards {
|
||||
rewards: std::vec::Vec<crate::YellowstoneReward>,
|
||||
num_partitions: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl YellowstoneBlockRewards {
|
||||
/// Returns ordered reward entries.
|
||||
#[must_use]
|
||||
pub fn rewards(&self) -> &[crate::YellowstoneReward] {
|
||||
return self.rewards.as_slice();
|
||||
}
|
||||
|
||||
/// Returns the optional number of reward partitions represented by the block.
|
||||
#[must_use]
|
||||
pub const fn num_partitions(&self) -> std::option::Option<u64> {
|
||||
return self.num_partitions;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneBlockRewards {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneBlockRewards")
|
||||
.field("reward_count", &self.rewards.len())
|
||||
.field("num_partitions", &self.num_partitions)
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/// One Yellowstone block-entry payload. The same wire message is used standalone and inside full block updates.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct YellowstoneEntryInfo {
|
||||
slot: u64,
|
||||
index: u64,
|
||||
num_hashes: u64,
|
||||
hash: crate::YellowstoneHashBytes,
|
||||
executed_transaction_count: u64,
|
||||
starting_transaction_index: u64,
|
||||
}
|
||||
|
||||
impl YellowstoneEntryInfo {
|
||||
/// Returns the containing slot.
|
||||
#[must_use]
|
||||
pub const fn slot(self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the entry index within the slot.
|
||||
#[must_use]
|
||||
pub const fn index(self) -> u64 {
|
||||
return self.index;
|
||||
}
|
||||
|
||||
/// Returns the number of hashes represented by this entry.
|
||||
#[must_use]
|
||||
pub const fn num_hashes(self) -> u64 {
|
||||
return self.num_hashes;
|
||||
}
|
||||
|
||||
/// Returns the exact fixed-width entry hash.
|
||||
#[must_use]
|
||||
pub const fn hash(self) -> crate::YellowstoneHashBytes {
|
||||
return self.hash;
|
||||
}
|
||||
|
||||
/// Returns the number of transactions executed by this entry.
|
||||
#[must_use]
|
||||
pub const fn executed_transaction_count(self) -> u64 {
|
||||
return self.executed_transaction_count;
|
||||
}
|
||||
|
||||
/// Returns the starting transaction index, including the legacy zero value emitted by older validators.
|
||||
#[must_use]
|
||||
pub const fn starting_transaction_index(self) -> u64 {
|
||||
return self.starting_transaction_index;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneEntryInfo {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneEntryInfo")
|
||||
.field("slot", &self.slot)
|
||||
.field("index", &self.index)
|
||||
.field("num_hashes", &self.num_hashes)
|
||||
.field("executed_transaction_count", &self.executed_transaction_count)
|
||||
.field("starting_transaction_index", &self.starting_transaction_index)
|
||||
.finish_non_exhaustive();
|
||||
}
|
||||
}
|
||||
|
||||
/// Full standard Yellowstone block update projected into KSP-owned types.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct YellowstoneBlockUpdate {
|
||||
filters: std::vec::Vec<crate::YellowstoneSubscribeFilterName>,
|
||||
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
|
||||
slot: u64,
|
||||
blockhash: std::string::String,
|
||||
rewards: std::option::Option<crate::YellowstoneBlockRewards>,
|
||||
block_time: std::option::Option<i64>,
|
||||
block_height: std::option::Option<u64>,
|
||||
parent_slot: u64,
|
||||
parent_blockhash: std::string::String,
|
||||
executed_transaction_count: u64,
|
||||
transactions: std::vec::Vec<crate::YellowstoneTransactionInfo>,
|
||||
updated_account_count: u64,
|
||||
accounts: std::vec::Vec<crate::YellowstoneAccountInfo>,
|
||||
entries_count: u64,
|
||||
entries: std::vec::Vec<crate::YellowstoneEntryInfo>,
|
||||
}
|
||||
|
||||
impl YellowstoneBlockUpdate {
|
||||
/// Returns echoed matching filter names in server order.
|
||||
#[must_use]
|
||||
pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] {
|
||||
return self.filters.as_slice();
|
||||
}
|
||||
|
||||
/// Returns optional server creation timestamp.
|
||||
#[must_use]
|
||||
pub const fn created_at(&self) -> std::option::Option<crate::YellowstoneUpdateTimestamp> {
|
||||
return self.created_at;
|
||||
}
|
||||
|
||||
/// Returns the block slot.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the exact blockhash text validated as a 32-byte Base58 Solana hash.
|
||||
#[must_use]
|
||||
pub fn blockhash(&self) -> &str {
|
||||
return self.blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns optional block rewards.
|
||||
#[must_use]
|
||||
pub const fn rewards(&self) -> std::option::Option<&crate::YellowstoneBlockRewards> {
|
||||
return self.rewards.as_ref();
|
||||
}
|
||||
|
||||
/// Returns optional Unix block time.
|
||||
#[must_use]
|
||||
pub const fn block_time(&self) -> std::option::Option<i64> {
|
||||
return self.block_time;
|
||||
}
|
||||
|
||||
/// Returns optional block height.
|
||||
#[must_use]
|
||||
pub const fn block_height(&self) -> std::option::Option<u64> {
|
||||
return self.block_height;
|
||||
}
|
||||
|
||||
/// Returns the parent slot.
|
||||
#[must_use]
|
||||
pub const fn parent_slot(&self) -> u64 {
|
||||
return self.parent_slot;
|
||||
}
|
||||
|
||||
/// Returns the exact parent blockhash text.
|
||||
#[must_use]
|
||||
pub fn parent_blockhash(&self) -> &str {
|
||||
return self.parent_blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns the server-reported executed transaction count independently of whether transaction payloads were requested.
|
||||
#[must_use]
|
||||
pub const fn executed_transaction_count(&self) -> u64 {
|
||||
return self.executed_transaction_count;
|
||||
}
|
||||
|
||||
/// Returns transaction payloads included by the server.
|
||||
#[must_use]
|
||||
pub fn transactions(&self) -> &[crate::YellowstoneTransactionInfo] {
|
||||
return self.transactions.as_slice();
|
||||
}
|
||||
|
||||
/// Returns the server-reported updated account count independently of whether account payloads were requested.
|
||||
#[must_use]
|
||||
pub const fn updated_account_count(&self) -> u64 {
|
||||
return self.updated_account_count;
|
||||
}
|
||||
|
||||
/// Returns account payloads included by the server.
|
||||
#[must_use]
|
||||
pub fn accounts(&self) -> &[crate::YellowstoneAccountInfo] {
|
||||
return self.accounts.as_slice();
|
||||
}
|
||||
|
||||
/// Returns the server-reported entry count independently of whether entry payloads were requested.
|
||||
#[must_use]
|
||||
pub const fn entries_count(&self) -> u64 {
|
||||
return self.entries_count;
|
||||
}
|
||||
|
||||
/// Returns entry payloads included by the server.
|
||||
#[must_use]
|
||||
pub fn entries(&self) -> &[crate::YellowstoneEntryInfo] {
|
||||
return self.entries.as_slice();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneBlockUpdate {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneBlockUpdate")
|
||||
.field("filter_count", &self.filters.len())
|
||||
.field("created_at", &self.created_at)
|
||||
.field("slot", &self.slot)
|
||||
.field("has_rewards", &self.rewards.is_some())
|
||||
.field("block_time", &self.block_time)
|
||||
.field("block_height", &self.block_height)
|
||||
.field("parent_slot", &self.parent_slot)
|
||||
.field("executed_transaction_count", &self.executed_transaction_count)
|
||||
.field("transaction_payload_count", &self.transactions.len())
|
||||
.field("updated_account_count", &self.updated_account_count)
|
||||
.field("account_payload_count", &self.accounts.len())
|
||||
.field("entries_count", &self.entries_count)
|
||||
.field("entry_payload_count", &self.entries.len())
|
||||
.finish_non_exhaustive();
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata-only standard Yellowstone block update.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneBlockMetaUpdate {
|
||||
filters: std::vec::Vec<crate::YellowstoneSubscribeFilterName>,
|
||||
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
|
||||
slot: u64,
|
||||
blockhash: std::string::String,
|
||||
rewards: std::option::Option<crate::YellowstoneBlockRewards>,
|
||||
block_time: std::option::Option<i64>,
|
||||
block_height: std::option::Option<u64>,
|
||||
parent_slot: u64,
|
||||
parent_blockhash: std::string::String,
|
||||
executed_transaction_count: u64,
|
||||
entries_count: u64,
|
||||
}
|
||||
|
||||
impl YellowstoneBlockMetaUpdate {
|
||||
/// Returns echoed matching filter names in server order.
|
||||
#[must_use]
|
||||
pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] {
|
||||
return self.filters.as_slice();
|
||||
}
|
||||
|
||||
/// Returns optional server creation timestamp.
|
||||
#[must_use]
|
||||
pub const fn created_at(&self) -> std::option::Option<crate::YellowstoneUpdateTimestamp> {
|
||||
return self.created_at;
|
||||
}
|
||||
|
||||
/// Returns the block slot.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the exact blockhash text.
|
||||
#[must_use]
|
||||
pub fn blockhash(&self) -> &str {
|
||||
return self.blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns optional block rewards.
|
||||
#[must_use]
|
||||
pub const fn rewards(&self) -> std::option::Option<&crate::YellowstoneBlockRewards> {
|
||||
return self.rewards.as_ref();
|
||||
}
|
||||
|
||||
/// Returns optional Unix block time.
|
||||
#[must_use]
|
||||
pub const fn block_time(&self) -> std::option::Option<i64> {
|
||||
return self.block_time;
|
||||
}
|
||||
|
||||
/// Returns optional block height.
|
||||
#[must_use]
|
||||
pub const fn block_height(&self) -> std::option::Option<u64> {
|
||||
return self.block_height;
|
||||
}
|
||||
|
||||
/// Returns the parent slot.
|
||||
#[must_use]
|
||||
pub const fn parent_slot(&self) -> u64 {
|
||||
return self.parent_slot;
|
||||
}
|
||||
|
||||
/// Returns the exact parent blockhash text.
|
||||
#[must_use]
|
||||
pub fn parent_blockhash(&self) -> &str {
|
||||
return self.parent_blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns the server-reported executed transaction count.
|
||||
#[must_use]
|
||||
pub const fn executed_transaction_count(&self) -> u64 {
|
||||
return self.executed_transaction_count;
|
||||
}
|
||||
|
||||
/// Returns the server-reported entry count.
|
||||
#[must_use]
|
||||
pub const fn entries_count(&self) -> u64 {
|
||||
return self.entries_count;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneBlockMetaUpdate {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneBlockMetaUpdate")
|
||||
.field("filter_count", &self.filters.len())
|
||||
.field("created_at", &self.created_at)
|
||||
.field("slot", &self.slot)
|
||||
.field("has_rewards", &self.rewards.is_some())
|
||||
.field("block_time", &self.block_time)
|
||||
.field("block_height", &self.block_height)
|
||||
.field("parent_slot", &self.parent_slot)
|
||||
.field("executed_transaction_count", &self.executed_transaction_count)
|
||||
.field("entries_count", &self.entries_count)
|
||||
.finish_non_exhaustive();
|
||||
}
|
||||
}
|
||||
|
||||
/// Standalone standard Yellowstone entry update with its update envelope.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct YellowstoneEntryUpdate {
|
||||
filters: std::vec::Vec<crate::YellowstoneSubscribeFilterName>,
|
||||
created_at: std::option::Option<crate::YellowstoneUpdateTimestamp>,
|
||||
entry: crate::YellowstoneEntryInfo,
|
||||
}
|
||||
|
||||
impl YellowstoneEntryUpdate {
|
||||
/// Returns echoed matching filter names in server order.
|
||||
#[must_use]
|
||||
pub fn filters(&self) -> &[crate::YellowstoneSubscribeFilterName] {
|
||||
return self.filters.as_slice();
|
||||
}
|
||||
|
||||
/// Returns optional server creation timestamp.
|
||||
#[must_use]
|
||||
pub const fn created_at(&self) -> std::option::Option<crate::YellowstoneUpdateTimestamp> {
|
||||
return self.created_at;
|
||||
}
|
||||
|
||||
/// Returns the complete entry payload.
|
||||
#[must_use]
|
||||
pub const fn entry(&self) -> crate::YellowstoneEntryInfo {
|
||||
return self.entry;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for YellowstoneEntryUpdate {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
return formatter
|
||||
.debug_struct("YellowstoneEntryUpdate")
|
||||
.field("filter_count", &self.filters.len())
|
||||
.field("created_at", &self.created_at)
|
||||
.field("entry", &self.entry)
|
||||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2417,6 +2875,192 @@ fn decode_transaction_status_update(wire: yellowstone_grpc_proto::geyser::Subscr
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result<crate::YellowstoneBlockUpdate> {
|
||||
let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let update = match wire.update_oneof {
|
||||
std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(value)) => value,
|
||||
_ => return invalid_subscribe_response("block", "Yellowstone update does not contain a block payload"),
|
||||
};
|
||||
if update.transactions.len() > MAX_GRPC_BLOCK_VECTOR_COUNT
|
||||
|| update.accounts.len() > MAX_GRPC_BLOCK_VECTOR_COUNT
|
||||
|| update.entries.len() > MAX_GRPC_BLOCK_VECTOR_COUNT
|
||||
{
|
||||
return invalid_subscribe_response("block", "Yellowstone block payload collection exceeds the KSP bound");
|
||||
}
|
||||
let blockhash = match decode_blockhash_text("block.blockhash", update.blockhash) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let parent_blockhash = match decode_blockhash_text("block.parent_blockhash", update.parent_blockhash) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rewards = match update.rewards {
|
||||
std::option::Option::Some(value) => match decode_block_rewards(value) {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
let block_time = update.block_time.map(|value| return value.timestamp);
|
||||
let block_height = update.block_height.map(|value| return value.block_height);
|
||||
let mut transactions = std::vec::Vec::with_capacity(update.transactions.len());
|
||||
for value in update.transactions {
|
||||
let value = match decode_transaction_info(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
transactions.push(value);
|
||||
}
|
||||
let mut accounts = std::vec::Vec::with_capacity(update.accounts.len());
|
||||
for value in update.accounts {
|
||||
let value = match decode_account_info(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
accounts.push(value);
|
||||
}
|
||||
let mut entries = std::vec::Vec::with_capacity(update.entries.len());
|
||||
for value in update.entries {
|
||||
let value = match decode_entry_info(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
entries.push(value);
|
||||
}
|
||||
return std::result::Result::Ok(crate::YellowstoneBlockUpdate {
|
||||
filters,
|
||||
created_at,
|
||||
slot: update.slot,
|
||||
blockhash,
|
||||
rewards,
|
||||
block_time,
|
||||
block_height,
|
||||
parent_slot: update.parent_slot,
|
||||
parent_blockhash,
|
||||
executed_transaction_count: update.executed_transaction_count,
|
||||
transactions,
|
||||
updated_account_count: update.updated_account_count,
|
||||
accounts,
|
||||
entries_count: update.entries_count,
|
||||
entries,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_meta_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result<crate::YellowstoneBlockMetaUpdate> {
|
||||
let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let update = match wire.update_oneof {
|
||||
std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::BlockMeta(value)) => value,
|
||||
_ => return invalid_subscribe_response("block_meta", "Yellowstone update does not contain a block-meta payload"),
|
||||
};
|
||||
let blockhash = match decode_blockhash_text("block_meta.blockhash", update.blockhash) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let parent_blockhash = match decode_blockhash_text("block_meta.parent_blockhash", update.parent_blockhash) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rewards = match update.rewards {
|
||||
std::option::Option::Some(value) => match decode_block_rewards(value) {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
return std::result::Result::Ok(crate::YellowstoneBlockMetaUpdate {
|
||||
filters,
|
||||
created_at,
|
||||
slot: update.slot,
|
||||
blockhash,
|
||||
rewards,
|
||||
block_time: update.block_time.map(|value| return value.timestamp),
|
||||
block_height: update.block_height.map(|value| return value.block_height),
|
||||
parent_slot: update.parent_slot,
|
||||
parent_blockhash,
|
||||
executed_transaction_count: update.executed_transaction_count,
|
||||
entries_count: update.entries_count,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_entry_update(wire: yellowstone_grpc_proto::geyser::SubscribeUpdate) -> ksp_core_lib::Result<crate::YellowstoneEntryUpdate> {
|
||||
let (filters, created_at) = match decode_update_envelope(wire.filters, wire.created_at) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let update = match wire.update_oneof {
|
||||
std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(value)) => value,
|
||||
_ => return invalid_subscribe_response("entry", "Yellowstone update does not contain an entry payload"),
|
||||
};
|
||||
let entry = match decode_entry_info(update) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::YellowstoneEntryUpdate { filters, created_at, entry });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_rewards(wire: yellowstone_grpc_proto::solana::storage::confirmed_block::Rewards) -> ksp_core_lib::Result<crate::YellowstoneBlockRewards> {
|
||||
if wire.rewards.len() > MAX_GRPC_BLOCK_VECTOR_COUNT {
|
||||
return invalid_subscribe_response("block.rewards", "Yellowstone block reward count exceeds the KSP bound");
|
||||
}
|
||||
let mut rewards = std::vec::Vec::with_capacity(wire.rewards.len());
|
||||
for value in wire.rewards {
|
||||
let value = match decode_reward(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
rewards.push(value);
|
||||
}
|
||||
return std::result::Result::Ok(crate::YellowstoneBlockRewards { rewards, num_partitions: wire.num_partitions.map(|value| return value.num_partitions) });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_entry_info(wire: yellowstone_grpc_proto::geyser::SubscribeUpdateEntry) -> ksp_core_lib::Result<crate::YellowstoneEntryInfo> {
|
||||
let hash = match decode_entry_hash(wire.hash) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::YellowstoneEntryInfo {
|
||||
slot: wire.slot,
|
||||
index: wire.index,
|
||||
num_hashes: wire.num_hashes,
|
||||
hash,
|
||||
executed_transaction_count: wire.executed_transaction_count,
|
||||
starting_transaction_index: wire.starting_transaction_index,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_entry_hash(bytes: std::vec::Vec<u8>) -> ksp_core_lib::Result<crate::YellowstoneHashBytes> {
|
||||
let bytes: [u8; YELLOWSTONE_HASH_LENGTH_BYTES] = match bytes.try_into() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return invalid_subscribe_response("entry.hash", "Yellowstone entry contains an invalid hash length"),
|
||||
};
|
||||
return std::result::Result::Ok(crate::YellowstoneHashBytes { bytes });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_blockhash_text(field: &'static str, value: std::string::String) -> ksp_core_lib::Result<std::string::String> {
|
||||
if value.is_empty()
|
||||
|| value.len() > MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES
|
||||
|| value.trim() != value
|
||||
|| base58_decoded_length(value.as_str()) != std::option::Option::Some(YELLOWSTONE_HASH_LENGTH_BYTES)
|
||||
{
|
||||
return invalid_subscribe_response(field, "Yellowstone block update contains an invalid Solana blockhash");
|
||||
}
|
||||
return std::result::Result::Ok(value);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_transaction_info(wire: yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo) -> ksp_core_lib::Result<crate::YellowstoneTransactionInfo> {
|
||||
let signature = match decode_transaction_signature("transaction.signature", wire.signature) {
|
||||
@@ -2873,6 +3517,17 @@ fn decode_pubkey_bytes(field: &'static str, bytes: std::vec::Vec<u8>) -> ksp_cor
|
||||
return std::result::Result::Ok(ksp_core_lib::Pubkey::new_from_array(array));
|
||||
}
|
||||
|
||||
fn push_block_selector(target: &mut std::vec::Vec<ksp_core_lib::Pubkey>, value: ksp_core_lib::Pubkey) -> ksp_core_lib::Result<()> {
|
||||
if target.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "Yellowstone block account selector count exceeds KSP bounds")
|
||||
.with_context("field", "grpc_subscribe.block.account_include"),
|
||||
);
|
||||
}
|
||||
target.push(value);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn push_transaction_selector(target: &mut std::vec::Vec<ksp_core_lib::Pubkey>, value: ksp_core_lib::Pubkey, field: &'static str) -> ksp_core_lib::Result<()> {
|
||||
if target.len() >= MAX_GRPC_SUBSCRIBE_ACCOUNT_SELECTOR_COUNT {
|
||||
return std::result::Result::Err(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 40
|
||||
// version: 41
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -143,12 +143,22 @@ pub use self::grpc_subscribe::YellowstoneAccountMemcmpEncoding;
|
||||
pub use self::grpc_subscribe::YellowstoneAccountUpdate;
|
||||
/// One standard Yellowstone account-data slice.
|
||||
pub use self::grpc_subscribe::YellowstoneAccountsDataSlice;
|
||||
/// Metadata-only standard Yellowstone block update.
|
||||
pub use self::grpc_subscribe::YellowstoneBlockMetaUpdate;
|
||||
/// Rewards container carried by Yellowstone block and block-meta updates.
|
||||
pub use self::grpc_subscribe::YellowstoneBlockRewards;
|
||||
/// Full standard Yellowstone block update.
|
||||
pub use self::grpc_subscribe::YellowstoneBlockUpdate;
|
||||
/// One compiled instruction from the Yellowstone Solana-storage transaction wire.
|
||||
pub use self::grpc_subscribe::YellowstoneCompiledInstruction;
|
||||
/// Wire-preserving KSP representation of a standard Yellowstone Cuckoo filter.
|
||||
pub use self::grpc_subscribe::YellowstoneCuckooFilter;
|
||||
/// Hash algorithm carried by a standard Yellowstone Cuckoo filter.
|
||||
pub use self::grpc_subscribe::YellowstoneCuckooHashAlgorithm;
|
||||
/// One Yellowstone block-entry payload reused by block and standalone entry updates.
|
||||
pub use self::grpc_subscribe::YellowstoneEntryInfo;
|
||||
/// Standalone standard Yellowstone entry update.
|
||||
pub use self::grpc_subscribe::YellowstoneEntryUpdate;
|
||||
/// Fixed-width 32-byte hash from the Yellowstone Solana-storage transaction wire.
|
||||
pub use self::grpc_subscribe::YellowstoneHashBytes;
|
||||
/// One inner instruction from Yellowstone transaction status metadata.
|
||||
@@ -171,7 +181,7 @@ pub use self::grpc_subscribe::YellowstoneSlotUpdate;
|
||||
pub use self::grpc_subscribe::YellowstoneStoredTransaction;
|
||||
/// Complete account-family filter group for standard Yellowstone Subscribe.
|
||||
pub use self::grpc_subscribe::YellowstoneSubscribeAccountFilter;
|
||||
/// Block-family filter-group shell for standard Yellowstone Subscribe.
|
||||
/// Complete block-family filter group 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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 44
|
||||
// version: 45
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -925,3 +925,47 @@ fn public_v0_2_9_pre_007_yellowstone_transactions_contract_is_available_from_cra
|
||||
let _return_data = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneReturnData>();
|
||||
let _reward = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneReward>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_v0_2_9_pre_008_yellowstone_blocks_contract_is_available_from_crate_root() {
|
||||
let account = ksp_core_lib::Pubkey::new_from_array([71_u8; 32]);
|
||||
let mut block = ksp_onchain_transport_lib::YellowstoneSubscribeBlockFilter::new();
|
||||
assert!(block.push_account_include(account).is_ok());
|
||||
block.set_include_transactions(std::option::Option::Some(true));
|
||||
block.set_include_accounts(std::option::Option::Some(false));
|
||||
block.set_include_entries(std::option::Option::Some(true));
|
||||
assert_eq!(block.account_include(), &[account]);
|
||||
assert_eq!(block.include_transactions(), std::option::Option::Some(true));
|
||||
assert_eq!(block.include_accounts(), std::option::Option::Some(false));
|
||||
assert_eq!(block.include_entries(), std::option::Option::Some(true));
|
||||
let mut request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
||||
assert!(
|
||||
request
|
||||
.insert_block_filter(ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("blocks").expect("filter name must validate"), block,)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
request
|
||||
.insert_blocks_meta_filter(
|
||||
ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("block-meta").expect("filter name must validate"),
|
||||
ksp_onchain_transport_lib::YellowstoneSubscribeBlocksMetaFilter::new(),
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
request
|
||||
.insert_entry_filter(
|
||||
ksp_onchain_transport_lib::YellowstoneSubscribeFilterName::new("entry").expect("filter name must validate"),
|
||||
ksp_onchain_transport_lib::YellowstoneSubscribeEntryFilter::new(),
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(request.block_filter_count(), 1);
|
||||
assert_eq!(request.blocks_meta_filter_count(), 1);
|
||||
assert_eq!(request.entry_filter_count(), 1);
|
||||
let _block_update = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneBlockUpdate>();
|
||||
let _block_meta = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneBlockMetaUpdate>();
|
||||
let _block_rewards = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneBlockRewards>();
|
||||
let _entry_info = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneEntryInfo>();
|
||||
let _entry_update = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneEntryUpdate>();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 38
|
||||
// version: 39
|
||||
|
||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||
|
||||
@@ -1113,7 +1113,7 @@ fn release_v0_2_9_pre_004_materializes_only_standard_subscribe_common_contract()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_9_pre_005_completes_standard_accounts_and_slots_without_advancing_transactions_or_blocks() {
|
||||
fn release_v0_2_9_pre_005_accounts_and_slots_contract_remains_complete() {
|
||||
let source = include_str!("../src/grpc_subscribe.rs");
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
for required in [
|
||||
@@ -1146,7 +1146,6 @@ fn release_v0_2_9_pre_005_completes_standard_accounts_and_slots_without_advancin
|
||||
assert!(source.contains("MAX_GRPC_SUBSCRIBE_SLOT_DEAD_ERROR_LENGTH_BYTES"));
|
||||
assert!(source.contains("#[cfg(test)]\nfn decode_account_update"));
|
||||
assert!(source.contains("#[cfg(test)]\nfn decode_slot_update"));
|
||||
assert!(!source.contains("YellowstoneBlockUpdate"));
|
||||
assert!(!source.contains("SubscribeDeshred"));
|
||||
assert!(!source.contains("PublicNode"));
|
||||
assert!(!crate_root.contains("pub use tonic"));
|
||||
@@ -1181,7 +1180,7 @@ fn release_v0_2_9_pre_006_namespaces_unambiguously_http_owned_private_modules()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_9_pre_007_completes_standard_transactions_without_advancing_blocks_or_bidi() {
|
||||
fn release_v0_2_9_pre_007_transactions_contract_remains_complete_without_bidi() {
|
||||
let source = include_str!("../src/grpc_subscribe.rs");
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
for required in [
|
||||
@@ -1211,8 +1210,7 @@ fn release_v0_2_9_pre_007_completes_standard_transactions_without_advancing_bloc
|
||||
}
|
||||
assert!(source.contains("base58_decoded_length"));
|
||||
assert!(source.contains("YELLOWSTONE_TRANSACTION_SIGNATURE_WIRE_LENGTH_BYTES"));
|
||||
assert!(!source.contains("YellowstoneBlockUpdate"));
|
||||
assert!(!source.contains("fn decode_block_update"));
|
||||
assert!(!source.contains("pub async fn subscribe("));
|
||||
assert!(!source.contains("SubscribeDeshred"));
|
||||
assert!(!source.contains("PublicNode"));
|
||||
assert!(!crate_root.contains("pub use tonic"));
|
||||
@@ -1221,3 +1219,46 @@ fn release_v0_2_9_pre_007_completes_standard_transactions_without_advancing_bloc
|
||||
let _update = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneTransactionUpdate>();
|
||||
let _status = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneTransactionStatusUpdate>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_9_pre_008_completes_standard_blocks_without_advancing_bidi() {
|
||||
let source = include_str!("../src/grpc_subscribe.rs");
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
for required in [
|
||||
"YellowstoneSubscribeBlockFilter",
|
||||
"account_include",
|
||||
"include_transactions",
|
||||
"include_accounts",
|
||||
"include_entries",
|
||||
"cuckoo_account_include",
|
||||
"YellowstoneBlockRewards",
|
||||
"YellowstoneBlockUpdate",
|
||||
"YellowstoneBlockMetaUpdate",
|
||||
"YellowstoneEntryInfo",
|
||||
"YellowstoneEntryUpdate",
|
||||
"executed_transaction_count",
|
||||
"updated_account_count",
|
||||
"entries_count",
|
||||
"starting_transaction_index",
|
||||
"decode_block_update",
|
||||
"decode_block_meta_update",
|
||||
"decode_entry_update",
|
||||
"decode_block_rewards",
|
||||
] {
|
||||
assert!(source.contains(required), "missing pre.008 Blocks contract token: {required}");
|
||||
}
|
||||
assert!(source.contains("MAX_GRPC_BLOCK_VECTOR_COUNT"));
|
||||
assert!(source.contains("base58_decoded_length(value.as_str())"));
|
||||
assert!(source.contains("std::vec::Vec<crate::YellowstoneTransactionInfo>"));
|
||||
assert!(source.contains("std::vec::Vec<crate::YellowstoneAccountInfo>"));
|
||||
assert!(source.contains("std::vec::Vec<crate::YellowstoneEntryInfo>"));
|
||||
assert!(source.contains("#[cfg(test)]\nfn decode_block_update"));
|
||||
assert!(!source.contains("pub async fn subscribe("));
|
||||
assert!(!source.contains("SubscribeDeshred"));
|
||||
assert!(!source.contains("PublicNode"));
|
||||
assert!(!crate_root.contains("pub use tonic"));
|
||||
assert!(!crate_root.contains("pub use yellowstone_grpc_proto"));
|
||||
let _block = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneBlockUpdate>();
|
||||
let _meta = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneBlockMetaUpdate>();
|
||||
let _entry = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneEntryUpdate>();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
||||
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
||||
}
|
||||
|
||||
fn minimal_transaction_info(signature_byte: u8, index: u64) -> yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo {
|
||||
let mut meta = yellowstone_grpc_proto::solana::storage::confirmed_block::TransactionStatusMeta::default();
|
||||
meta.fee = 5_000;
|
||||
return yellowstone_grpc_proto::geyser::SubscribeUpdateTransactionInfo {
|
||||
signature: vec![signature_byte; 64],
|
||||
is_vote: false,
|
||||
transaction: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Transaction {
|
||||
signatures: vec![vec![signature_byte; 64]],
|
||||
message: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Message {
|
||||
header: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 0,
|
||||
}),
|
||||
account_keys: vec![vec![1_u8; 32]],
|
||||
recent_blockhash: vec![2_u8; 32],
|
||||
instructions: vec![],
|
||||
versioned: false,
|
||||
address_table_lookups: vec![],
|
||||
config: std::option::Option::None,
|
||||
}),
|
||||
}),
|
||||
meta: std::option::Option::Some(meta),
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_subscribe_filter_name_is_bounded_globally_unique_and_debug_redacted() {
|
||||
assert!(crate::YellowstoneSubscribeFilterName::new("").is_err());
|
||||
@@ -553,3 +580,199 @@ fn yellowstone_transaction_status_update_preserves_error_and_rejects_malformed_s
|
||||
};
|
||||
assert!(super::decode_transaction_status_update(malformed).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_block_filter_encodes_complete_current_wire_and_redacts_selectors() {
|
||||
let mut filter = crate::YellowstoneSubscribeBlockFilter::new();
|
||||
let account = ksp_core_lib::Pubkey::new_from_array([31_u8; 32]);
|
||||
assert!(filter.push_account_include(account).is_ok());
|
||||
filter.set_include_transactions(std::option::Option::Some(true));
|
||||
filter.set_include_accounts(std::option::Option::Some(false));
|
||||
filter.set_include_entries(std::option::Option::Some(true));
|
||||
let cuckoo = crate::YellowstoneCuckooFilter::new(vec![0_u8; 16], 4, 4, 8, 9, crate::YellowstoneCuckooHashAlgorithm::SipHash)
|
||||
.expect("block Cuckoo filter must validate");
|
||||
filter.set_cuckoo_account_include(std::option::Option::Some(cuckoo));
|
||||
let wire = filter.to_wire();
|
||||
assert_eq!(wire.account_include, vec![account.to_string()]);
|
||||
assert_eq!(wire.include_transactions, std::option::Option::Some(true));
|
||||
assert_eq!(wire.include_accounts, std::option::Option::Some(false));
|
||||
assert_eq!(wire.include_entries, std::option::Option::Some(true));
|
||||
assert!(wire.cuckoo_account_include.is_some());
|
||||
let debug = format!("{filter:?}");
|
||||
assert!(debug.contains("account_include_count"));
|
||||
assert!(!debug.contains(&account.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts() {
|
||||
let blockhash = ksp_core_lib::Pubkey::new_from_array([21_u8; 32]).to_string();
|
||||
let parent_blockhash = ksp_core_lib::Pubkey::new_from_array([22_u8; 32]).to_string();
|
||||
let reward_pubkey = ksp_core_lib::Pubkey::new_from_array([23_u8; 32]);
|
||||
let account_pubkey = ksp_core_lib::Pubkey::new_from_array([24_u8; 32]);
|
||||
let wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["blocks-main".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateBlock {
|
||||
slot: 500,
|
||||
blockhash: blockhash.clone(),
|
||||
rewards: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::Rewards {
|
||||
rewards: vec![yellowstone_grpc_proto::solana::storage::confirmed_block::Reward {
|
||||
pubkey: reward_pubkey.to_string(),
|
||||
lamports: 10,
|
||||
post_balance: 11,
|
||||
reward_type: yellowstone_grpc_proto::solana::storage::confirmed_block::RewardType::Fee as i32,
|
||||
commission: "".to_owned(),
|
||||
commission_bps: "".to_owned(),
|
||||
}],
|
||||
num_partitions: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::NumPartitions { num_partitions: 3 }),
|
||||
}),
|
||||
block_time: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::UnixTimestamp { timestamp: 1_700_000_000 }),
|
||||
block_height: std::option::Option::Some(yellowstone_grpc_proto::solana::storage::confirmed_block::BlockHeight { block_height: 499 }),
|
||||
transactions: vec![minimal_transaction_info(7, 2)],
|
||||
parent_slot: 499,
|
||||
parent_blockhash: parent_blockhash.clone(),
|
||||
executed_transaction_count: 12,
|
||||
updated_account_count: 34,
|
||||
accounts: vec![yellowstone_grpc_proto::geyser::SubscribeUpdateAccountInfo {
|
||||
pubkey: vec![24_u8; 32],
|
||||
lamports: 77,
|
||||
owner: vec![25_u8; 32],
|
||||
executable: false,
|
||||
rent_epoch: 4,
|
||||
data: vec![1_u8, 2, 3],
|
||||
write_version: 8,
|
||||
txn_signature: std::option::Option::Some(vec![7_u8; 64]),
|
||||
}],
|
||||
entries_count: 56,
|
||||
entries: vec![yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
||||
slot: 500,
|
||||
index: 4,
|
||||
num_hashes: 5,
|
||||
hash: vec![26_u8; 32],
|
||||
executed_transaction_count: 6,
|
||||
starting_transaction_index: 7,
|
||||
}],
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 12, nanos: 34 }),
|
||||
};
|
||||
let update = super::decode_block_update(wire).expect("block update fixture must decode");
|
||||
assert_eq!(update.filters()[0].as_str(), "blocks-main");
|
||||
assert_eq!(update.slot(), 500);
|
||||
assert_eq!(update.blockhash(), blockhash);
|
||||
assert_eq!(update.parent_slot(), 499);
|
||||
assert_eq!(update.parent_blockhash(), parent_blockhash);
|
||||
assert_eq!(update.block_time(), std::option::Option::Some(1_700_000_000));
|
||||
assert_eq!(update.block_height(), std::option::Option::Some(499));
|
||||
assert_eq!(update.executed_transaction_count(), 12);
|
||||
assert_eq!(update.transactions().len(), 1);
|
||||
assert_eq!(update.transactions()[0].index(), 2);
|
||||
assert_eq!(update.updated_account_count(), 34);
|
||||
assert_eq!(update.accounts().len(), 1);
|
||||
assert_eq!(update.accounts()[0].pubkey(), &account_pubkey);
|
||||
assert_eq!(update.entries_count(), 56);
|
||||
assert_eq!(update.entries().len(), 1);
|
||||
assert_eq!(update.entries()[0].starting_transaction_index(), 7);
|
||||
let rewards = update.rewards().expect("block rewards must be present");
|
||||
assert_eq!(rewards.rewards().len(), 1);
|
||||
assert_eq!(rewards.num_partitions(), std::option::Option::Some(3));
|
||||
assert_eq!(rewards.rewards()[0].pubkey(), &reward_pubkey);
|
||||
let debug = format!("{update:?}");
|
||||
assert!(!debug.contains(&blockhash));
|
||||
assert!(!debug.contains(&parent_blockhash));
|
||||
assert!(!debug.contains(&reward_pubkey.to_string()));
|
||||
assert!(!debug.contains(&account_pubkey.to_string()));
|
||||
let malformed = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["blocks-main".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Block(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateBlock {
|
||||
slot: 1,
|
||||
blockhash: "not-a-solana-hash".to_owned(),
|
||||
rewards: std::option::Option::None,
|
||||
block_time: std::option::Option::None,
|
||||
block_height: std::option::Option::None,
|
||||
transactions: vec![],
|
||||
parent_slot: 0,
|
||||
parent_blockhash: ksp_core_lib::Pubkey::new_from_array([1_u8; 32]).to_string(),
|
||||
executed_transaction_count: 0,
|
||||
updated_account_count: 0,
|
||||
accounts: vec![],
|
||||
entries_count: 0,
|
||||
entries: vec![],
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_block_update(malformed).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yellowstone_block_meta_and_entry_updates_preserve_optional_and_legacy_entry_fields() {
|
||||
let blockhash = ksp_core_lib::Pubkey::new_from_array([41_u8; 32]).to_string();
|
||||
let parent_blockhash = ksp_core_lib::Pubkey::new_from_array([42_u8; 32]).to_string();
|
||||
let meta_wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["meta".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::BlockMeta(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateBlockMeta {
|
||||
slot: 900,
|
||||
blockhash: blockhash.clone(),
|
||||
rewards: std::option::Option::None,
|
||||
block_time: std::option::Option::None,
|
||||
block_height: std::option::Option::None,
|
||||
parent_slot: 899,
|
||||
parent_blockhash: parent_blockhash.clone(),
|
||||
executed_transaction_count: 17,
|
||||
entries_count: 18,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
let meta = super::decode_block_meta_update(meta_wire).expect("block-meta update must decode");
|
||||
assert_eq!(meta.slot(), 900);
|
||||
assert_eq!(meta.blockhash(), blockhash);
|
||||
assert_eq!(meta.parent_blockhash(), parent_blockhash);
|
||||
assert_eq!(meta.rewards(), std::option::Option::None);
|
||||
assert_eq!(meta.block_time(), std::option::Option::None);
|
||||
assert_eq!(meta.block_height(), std::option::Option::None);
|
||||
assert_eq!(meta.executed_transaction_count(), 17);
|
||||
assert_eq!(meta.entries_count(), 18);
|
||||
assert!(!format!("{meta:?}").contains(&blockhash));
|
||||
let entry_wire = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["entries".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
||||
slot: 900,
|
||||
index: 3,
|
||||
num_hashes: 4,
|
||||
hash: vec![43_u8; 32],
|
||||
executed_transaction_count: 5,
|
||||
starting_transaction_index: 0,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::Some(yellowstone_grpc_proto::prost_types::Timestamp { seconds: 6, nanos: 7 }),
|
||||
};
|
||||
let entry = super::decode_entry_update(entry_wire).expect("entry update must decode");
|
||||
assert_eq!(entry.filters()[0].as_str(), "entries");
|
||||
assert_eq!(entry.entry().slot(), 900);
|
||||
assert_eq!(entry.entry().index(), 3);
|
||||
assert_eq!(entry.entry().num_hashes(), 4);
|
||||
assert_eq!(entry.entry().hash().as_bytes(), &[43_u8; 32]);
|
||||
assert_eq!(entry.entry().executed_transaction_count(), 5);
|
||||
assert_eq!(entry.entry().starting_transaction_index(), 0);
|
||||
assert!(!format!("{entry:?}").contains("43, 43"));
|
||||
let malformed_entry = yellowstone_grpc_proto::geyser::SubscribeUpdate {
|
||||
filters: vec!["entries".to_owned()],
|
||||
update_oneof: std::option::Option::Some(yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof::Entry(
|
||||
yellowstone_grpc_proto::geyser::SubscribeUpdateEntry {
|
||||
slot: 1,
|
||||
index: 0,
|
||||
num_hashes: 0,
|
||||
hash: vec![0_u8; 31],
|
||||
executed_transaction_count: 0,
|
||||
starting_transaction_index: 0,
|
||||
},
|
||||
)),
|
||||
created_at: std::option::Option::None,
|
||||
};
|
||||
assert!(super::decode_entry_update(malformed_entry).is_err());
|
||||
}
|
||||
|
||||
73
deltas/0.2.9/pre.008.md
Normal file
73
deltas/0.2.9/pre.008.md
Normal file
@@ -0,0 +1,73 @@
|
||||
<!-- file: deltas/0.2.9/pre.008.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.9-pre.008` — Yellowstone Blocks + block_meta + entry
|
||||
|
||||
## 1. Base et signal technique
|
||||
|
||||
Base : `0.2.9-pre.007`, fermée sur gate opérateur intégralement vert.
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.2.9-pre.8
|
||||
```
|
||||
|
||||
## 2. Scope
|
||||
|
||||
La tranche complète uniquement la dernière famille de filtres/updates standard avant l’ouverture bidi : Blocks, `block_meta` et `entry`.
|
||||
|
||||
| Surface | Matérialisation |
|
||||
|----------------------------|-----------------|
|
||||
| filtre Blocks | complet |
|
||||
| `blocks_meta` | marker inchangé |
|
||||
| `entry` | marker inchangé |
|
||||
| `SubscribeUpdateBlock` | DTO + decode |
|
||||
| `SubscribeUpdateBlockMeta` | DTO + decode |
|
||||
| `SubscribeUpdateEntry` | DTO + decode |
|
||||
| stream bidi / lifecycle | OUT |
|
||||
| reconnect / replay | OUT |
|
||||
| PublicNode / Config V3 | OUT |
|
||||
| `SubscribeDeshred` | OUT `0.2.9` |
|
||||
|
||||
## 3. Filtre Blocks
|
||||
|
||||
`YellowstoneSubscribeBlockFilter` matérialise exactement le wire courant : `account_include[]`, `include_transactions?`, `include_accounts?`, `include_entries?` et `cuckoo_account_include?`. Les account selectors utilisent `ksp_core_lib::Pubkey`, restent ordonnés et bornés ; `Debug` ne rend aucune adresse.
|
||||
|
||||
## 4. Updates Blocks
|
||||
|
||||
`YellowstoneBlockUpdate` conserve la metadata de bloc, rewards, temps/hauteur optionnels, parent, compteurs et payloads optionnels. Les transactions réutilisent `YellowstoneTransactionInfo`, les comptes réutilisent `YellowstoneAccountInfo`, et les entries utilisent `YellowstoneEntryInfo`.
|
||||
|
||||
`YellowstoneBlockMetaUpdate` conserve la variante metadata-only. `YellowstoneEntryUpdate` enveloppe la même `YellowstoneEntryInfo` réutilisée dans les blocs. `starting_transaction_index` reste explicite, y compris sa valeur legacy `0`.
|
||||
|
||||
Les compteurs serveur sont indépendants des tailles de vecteurs : aucune égalité artificielle n’est imposée quand les flags `include_*` demandent l’omission d’un payload.
|
||||
|
||||
## 5. Validation et sécurité
|
||||
|
||||
Les blockhash textuels sont bornés, trim-exacts et Base58-décodés vers exactement 32 octets ; les hash d’entrée sont exactement 32 octets. Les collections Block/Rewards sont bornées. `Debug` ne copie aucun blockhash, parent blockhash, reward pubkey, account selector, données account, transaction, instruction ou entry hash.
|
||||
|
||||
Les conversions protobuf et décodeurs restent sous `#[cfg(test)]` jusqu’à l’ouverture du stream runtime en `pre.009`.
|
||||
|
||||
## 6. Tests/canaries ajoutés
|
||||
|
||||
```text
|
||||
block filter exact wire + redaction
|
||||
block update complet avec Transaction/Account/Entry réutilisés
|
||||
server counts distincts des payload-vector lengths
|
||||
block-meta optional states
|
||||
entry starting_transaction_index + malformed hash
|
||||
public API canary pre.008
|
||||
release completeness canary pre.008
|
||||
```
|
||||
|
||||
## 7. Gate opérateur
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Aucune dépendance ni feature Cargo n’est modifiée ; aucun `cargo tree` supplémentaire n’est requis.
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- file: docs/plans/016-V0_2_9_YELLOWSTONE_GRPC_PLAN.md -->
|
||||
<!-- version: 15 -->
|
||||
<!-- version: 16 -->
|
||||
|
||||
# Plan `0.2.9` — moteur Yellowstone gRPC + standard Solana + PublicNode
|
||||
|
||||
> **Statut : `0.2.9-pre.006` est fermée sur gate opérateur intégralement vert : fmt/audit/check/Clippy/workspace PASS, Transport 364 unit + 45 public API + 39 completeness + 4 doctests. `0.2.9-pre.007` est candidate et matérialise uniquement Transactions + `transaction_status` du `Subscribe` standard : filtres complets, signature Base58 décodée exactement sur 64 octets, Cuckoo/token-account expansion, DTOs transaction/meta complets et Transaction V1 `TransactionConfig`. Blocks restent `pre.008`; aucun stream bidi n’est encore ouvert.**
|
||||
> **Statut : `0.2.9-pre.007` est fermée sur gate opérateur intégralement vert : fmt/audit/check/Clippy/workspace PASS, Transport 367 unit + 46 public API + 40 completeness + 4 doctests. `0.2.9-pre.008` est candidate et complète uniquement Blocks + `block_meta` + `entry` du `Subscribe` standard : filtre Blocks courant complet, rewards/time/height, réutilisation des DTOs Transaction/Account, compteurs serveur indépendants des payloads optionnels et `starting_transaction_index`. Aucun stream bidi n’est encore ouvert ; lifecycle reste `pre.009`.**
|
||||
|
||||
## 1. Objet, base et état d'ouverture
|
||||
|
||||
@@ -928,10 +928,10 @@ pre.005 DONE — standard Solana : Accounts + Slots filters/updates
|
||||
pre.006 DONE — structure Transport : namespace privé HTTP explicite
|
||||
budget : 15–20 min ; gate opérateur final PASS 364/45/39/4 + workspace
|
||||
|
||||
pre.007 CANDIDATE — standard Solana : Transactions + transaction_status
|
||||
pre.007 DONE — standard Solana : Transactions + transaction_status
|
||||
budget : 15–20 min ; preuve : include/exclude/required/Cuckoo/token expansion + tx/meta + TransactionConfig V1
|
||||
|
||||
pre.008 standard Solana : Blocks + block_meta + entry
|
||||
pre.008 CANDIDATE — standard Solana : Blocks + block_meta + entry
|
||||
budget : 15–20 min ; preuve : counts/arrays/optional/oneof/payload bounds
|
||||
|
||||
pre.009 moteur partagé : bidi mutation + Ping/Pong + half-close + backpressure + shutdown
|
||||
@@ -1273,3 +1273,65 @@ Les marqueurs `inner_instructions_none`, `log_messages_none` et `return_data_non
|
||||
OUT de `pre.007` : Blocks/block_meta/entry, stream bidi/Ping-Pong, reconnect/replay, Config V3, PublicNode et `SubscribeDeshred`.
|
||||
|
||||
**Gate candidat :** audit statique clean ; compilation/Clippy/tests opérateur requis avant fermeture.
|
||||
|
||||
|
||||
## 23. Gate final `pre.007` — Transactions + `transaction_status`
|
||||
|
||||
Preuve opérateur du `2026-08-24` :
|
||||
|
||||
| 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 | 367/367 PASS |
|
||||
| Transport `public_api` | 46/46 PASS |
|
||||
| Transport `release_completeness` | 40/40 PASS |
|
||||
| Transport doctests | 4/4 PASS |
|
||||
| Core dependency canary | 3/3 PASS |
|
||||
| `cargo test --workspace` | PASS |
|
||||
|
||||
Le gate confirme la projection Transactions complète, incluant `TransactionConfig` V1 et les marqueurs legacy de `TransactionStatusMeta`, sans régression HTTP/WS et sans avancer Blocks ou bidi.
|
||||
|
||||
**Verdict : `pre.007` fermée.**
|
||||
|
||||
## 24. `pre.008` — Blocks + `block_meta` + `entry` candidate
|
||||
|
||||
Le proto `yellowstone-grpc-proto 12.6.0` est réaudité avant implémentation. `SubscribeRequestFilterBlocks` contient exactement `account_include[]`, `include_transactions?`, `include_accounts?`, `include_entries?` et `cuckoo_account_include?`. `blocks_meta` et `entry` conservent leurs filtres marqueurs vides.
|
||||
|
||||
La candidate matérialise :
|
||||
|
||||
```text
|
||||
request filter Blocks
|
||||
account_include[] ordonné et borné
|
||||
include_transactions?
|
||||
include_accounts?
|
||||
include_entries?
|
||||
cuckoo_account_include?
|
||||
|
||||
SubscribeUpdateBlock
|
||||
slot / blockhash / parent_slot / parent_blockhash
|
||||
rewards? + num_partitions?
|
||||
block_time? / block_height?
|
||||
executed_transaction_count
|
||||
transactions[] -> YellowstoneTransactionInfo réutilisé
|
||||
updated_account_count
|
||||
accounts[] -> YellowstoneAccountInfo réutilisé
|
||||
entries_count
|
||||
entries[] -> YellowstoneEntryInfo
|
||||
|
||||
SubscribeUpdateBlockMeta
|
||||
même metadata sans payloads transaction/account/entry
|
||||
|
||||
SubscribeUpdateEntry
|
||||
slot / index / num_hashes / hash[32]
|
||||
executed_transaction_count
|
||||
starting_transaction_index
|
||||
```
|
||||
|
||||
Les compteurs serveur ne sont volontairement pas comparés aux longueurs des vecteurs : les options `include_transactions`, `include_accounts` et `include_entries` permettent au serveur de rapporter les totaux tout en omettant les payloads correspondants. Les `blockhash`/`parent_blockhash` sont validés comme Base58 représentant 32 octets, et les hash d’entrée restent des 32 octets exacts. `Debug` n’expose aucun hash, account selector, reward pubkey ou payload imbriqué.
|
||||
|
||||
Les conversions request protobuf et décodeurs Block/BlockMeta/Entry restent `#[cfg(test)]` jusqu’à l’ouverture du stream runtime en `pre.009`. Aucun `SubscribeDeshred`, provider PublicNode, Config V3, Ping/Pong lifecycle ou reconnect n’entre dans cette tranche.
|
||||
|
||||
**Gate candidat :** audit statique clean ; compilation/Clippy/tests opérateur requis avant fermeture.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- file: docs/validation/012-V0_2_9_YELLOWSTONE_GRPC.md -->
|
||||
<!-- version: 16 -->
|
||||
<!-- version: 17 -->
|
||||
|
||||
# Validation `0.2.9` — moteur Yellowstone + standard Solana + PublicNode
|
||||
|
||||
> **Statut : `pre.006` est fermée sur gate opérateur intégralement vert : fmt/audit/check/Clippy/workspace PASS, Transport 364 unit + 45 public API + 39 completeness + 4 doctests. `0.2.9-pre.007` est candidate Transactions + `transaction_status` : filtre current complet, signature Base58 -> 64 octets, Cuckoo/token expansion, updates transaction/status et projection typée complète du `solana-storage.proto` courant incluant Transaction V1 `TransactionConfig`. Blocks restent `pre.008`; bidi reste `pre.009`.**
|
||||
> **Statut : `pre.007` est fermée sur gate opérateur intégralement vert : fmt/audit/check/Clippy/workspace PASS, Transport 367 unit + 46 public API + 40 completeness + 4 doctests. `0.2.9-pre.008` est candidate Blocks + `block_meta` + `entry` : filtre Blocks courant complet, metadata/rewards/time/height, payloads Transaction/Account réutilisés, Entry complète et compteurs serveur conservés indépendamment des payloads optionnels. Bidi reste `pre.009`.**
|
||||
|
||||
## 1. Autorités du gate
|
||||
|
||||
@@ -55,17 +55,17 @@ crate proto : 12.6.0
|
||||
|
||||
## 3. Matrice service `Geyser`
|
||||
|
||||
| RPC | Forme | Classification | Scope | Preuve cible | État |
|
||||
|-----------------------|-------|------------------------------------------------------|-------|-----------------------|------------------------------------------------------|
|
||||
| `Subscribe` | bidi | standard | IN | fixture locale + live | PARTIAL pre.005 Accounts/Slots / stream TODO pre.009 |
|
||||
| `SubscribeDeshred` | bidi | Triton extension/pré-exécution malgré présence proto | OUT | canari d'absence/API | OUT |
|
||||
| `SubscribeReplayInfo` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `Ping` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetLatestBlockhash` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetBlockHeight` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetSlot` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `IsBlockhashValid` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetVersion` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| RPC | Forme | Classification | Scope | Preuve cible | État |
|
||||
|-----------------------|-------|------------------------------------------------------|-------|-----------------------|-------------------------------------------------------|
|
||||
| `Subscribe` | bidi | standard | IN | fixture locale + live | PARTIAL pre.008 filters/updates / stream TODO pre.009 |
|
||||
| `SubscribeDeshred` | bidi | Triton extension/pré-exécution malgré présence proto | OUT | canari d'absence/API | OUT |
|
||||
| `SubscribeReplayInfo` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `Ping` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetLatestBlockhash` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetBlockHeight` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetSlot` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `IsBlockhashValid` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
| `GetVersion` | unary | standard | IN | fixture unary | DONE pre.003 |
|
||||
|
||||
## 4. `SubscribeRequest` — coverage normative
|
||||
|
||||
@@ -409,8 +409,8 @@ pre.003 DONE TLS/metadata + fixture + 7 unary standard 15
|
||||
pre.004 DONE standard: Subscribe common/from_slot/bounds 15–20 min ; gate final fix.001 PASS
|
||||
pre.005 DONE standard: accounts + slots 15–20 min ; gate final fix.001 PASS
|
||||
pre.006 DONE structure: namespace privé HTTP `http_*` 15–20 min ; gate PASS
|
||||
pre.007 CANDIDATE standard: transactions + transaction_status 15–20 min
|
||||
pre.008 TODO standard: blocks + block_meta + entry 15–20 min
|
||||
pre.007 DONE standard: transactions + transaction_status 15–20 min ; gate PASS
|
||||
pre.008 CANDIDATE standard: blocks + block_meta + entry 15–20 min
|
||||
pre.009 TODO moteur: bidi/backpressure/half-close/shutdown 15–20 min
|
||||
pre.010 TODO moteur: reconnect/replay/gap/duplicate 15–20 min
|
||||
pre.011 TODO Config V3 + protocol/provider + profils PublicNode 15–20 min
|
||||
@@ -813,3 +813,53 @@ Le renommage est limité aux cinq modules dont l'ownership HTTP est sans ambigu
|
||||
Le proto 12.6.0 ajoute à la représentation de transaction le `Message.config` optionnel pour Transaction V1 / SIMD-0385 ; la candidate le conserve explicitement. Les marqueurs legacy `inner_instructions_none`, `log_messages_none` et `return_data_none` ne sont pas fusionnés avec leurs collections/messages.
|
||||
|
||||
**Verdict `pre.007` : candidate source prête ; fermeture après gate Cargo opérateur.**
|
||||
|
||||
|
||||
## 24. Gate final `pre.007`
|
||||
|
||||
| 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 | 367/367 PASS |
|
||||
| Transport `public_api` | 46/46 PASS |
|
||||
| Transport `release_completeness` | 40/40 PASS |
|
||||
| Transport doctests | 4/4 PASS |
|
||||
| Core dependency canary | 3/3 PASS |
|
||||
| `cargo test --workspace` | PASS |
|
||||
|
||||
**Verdict : `pre.007` fermée.**
|
||||
|
||||
## 25. Gate `pre.008` — Blocks + `block_meta` + `entry` candidate
|
||||
|
||||
| Surface / invariant | État candidate |
|
||||
|-------------------------------------------------------|----------------|
|
||||
| workspace version | `0.2.9-pre.8` |
|
||||
| Blocks `account_include[]` | SOURCE+TEST |
|
||||
| `include_transactions?` / `include_accounts?` | SOURCE+TEST |
|
||||
| `include_entries?` | SOURCE+TEST |
|
||||
| Cuckoo block account include | SOURCE+TEST |
|
||||
| `SubscribeUpdateBlock` | SOURCE+TEST |
|
||||
| rewards + `num_partitions?` | SOURCE+TEST |
|
||||
| block time / block height optionnels | SOURCE+TEST |
|
||||
| transactions réutilisent `YellowstoneTransactionInfo` | SOURCE+TEST |
|
||||
| accounts réutilisent `YellowstoneAccountInfo` | SOURCE+TEST |
|
||||
| compteurs serveur indépendants des payload vectors | SOURCE+TEST |
|
||||
| `SubscribeUpdateBlockMeta` | SOURCE+TEST |
|
||||
| `SubscribeUpdateEntry` + `starting_transaction_index` | SOURCE+TEST |
|
||||
| blockhash/parent blockhash Base58 -> 32 octets | SOURCE+TEST |
|
||||
| entry hash exactement 32 octets | SOURCE+TEST |
|
||||
| Debug sans hashes/sélecteurs/payloads imbriqués | SOURCE+TEST |
|
||||
| stream bidi / Ping-Pong / lifecycle | OUT pre.008 |
|
||||
| reconnect / replay | OUT pre.008 |
|
||||
| PublicNode / Config V3 | OUT pre.008 |
|
||||
| audit Rust workspace local | PASS / clean |
|
||||
| fmt/check/Clippy/tests | opérateur TODO |
|
||||
|
||||
Les `executed_transaction_count`, `updated_account_count` et `entries_count` sont conservés tels que fournis par le serveur ; ils ne sont pas forcés à égaler les tailles de `transactions[]`, `accounts[]` ou `entries[]`, car les trois flags `include_*` peuvent omettre ces payloads. Les messages imbriqués Transaction et Account réutilisent strictement les DTOs déjà introduits ; aucune seconde projection n’est créée.
|
||||
|
||||
Les helpers wire/decode restent test-only jusqu’au premier consommateur runtime en `pre.009`.
|
||||
|
||||
**Verdict `pre.008` : candidate source prête ; fermeture après gate Cargo opérateur.**
|
||||
|
||||
Reference in New Issue
Block a user