v0.2.2-pre.002-fix.001

This commit is contained in:
2026-08-18 07:17:49 +02:00
parent e68f073505
commit f625ee5979
12 changed files with 606 additions and 137 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 7
// version: 8
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
@@ -91,34 +91,24 @@ pub use self::resilience::HttpRetryCause;
pub use self::resilience::HttpRetryDecision;
/// Evaluates the centralized bounded HTTP retry policy for one audited RPC method.
pub use self::resilience::evaluate_transport_retry;
/// Optional typed configuration for the `getBalance` canary.
pub use self::rpc_canary::GetBalanceConfig;
/// Typed lamport balance returned by the `getBalance` canary.
pub use self::rpc_canary::GetBalanceResult;
/// Typed genesis hash returned by the `getGenesisHash` canary.
pub use self::rpc_canary::SolanaGenesisHash;
/// Typed healthy result returned by the `getHealth` canary.
pub use self::rpc_canary::SolanaNodeHealth;
/// Typed software-version response returned by the `getVersion` canary.
pub use self::rpc_canary::SolanaNodeVersion;
/// Account-data encoding accepted by Solana HTTP account methods.
pub use self::rpc_accounts::SolanaAccountEncoding;
/// Typed transport-level Solana account without Program/SPL decoding.
pub use self::rpc_accounts::SolanaAccount;
/// Address and lamport balance returned by `getLargestAccounts`.
pub use self::rpc_accounts::SolanaAccountBalance;
/// Wire-preserving account data returned by Solana HTTP account methods.
pub use self::rpc_accounts::SolanaAccountData;
/// Account-data encoding accepted by Solana HTTP account methods.
pub use self::rpc_accounts::SolanaAccountEncoding;
/// Shared account configuration used by account-info and token-account list methods.
pub use self::rpc_accounts::SolanaAccountInfoConfig;
/// Byte range requested from account data without decoding it locally.
pub use self::rpc_accounts::SolanaDataSliceConfig;
/// One public key plus its account returned by account-list RPC methods.
pub use self::rpc_accounts::SolanaKeyedAccount;
/// Filter accepted by `getLargestAccounts`.
pub use self::rpc_accounts::SolanaLargestAccountsFilter;
/// Optional configuration for `getLargestAccounts`.
pub use self::rpc_accounts::SolanaLargestAccountsConfig;
/// Filter accepted by `getLargestAccounts`.
pub use self::rpc_accounts::SolanaLargestAccountsFilter;
/// Bytes used by a `memcmp` program-account filter.
pub use self::rpc_accounts::SolanaMemcmpBytes;
/// One `memcmp` filter applied to account data.
@@ -131,6 +121,16 @@ pub use self::rpc_accounts::SolanaProgramAccountFilter;
pub use self::rpc_accounts::SolanaProgramAccountsConfig;
/// Result union returned by `getProgramAccounts` with or without an RPC context.
pub use self::rpc_accounts::SolanaProgramAccountsResult;
/// Optional typed configuration for the `getBalance` canary.
pub use self::rpc_canary::GetBalanceConfig;
/// Typed lamport balance returned by the `getBalance` canary.
pub use self::rpc_canary::GetBalanceResult;
/// Typed genesis hash returned by the `getGenesisHash` canary.
pub use self::rpc_canary::SolanaGenesisHash;
/// Typed healthy result returned by the `getHealth` canary.
pub use self::rpc_canary::SolanaNodeHealth;
/// Typed software-version response returned by the `getVersion` canary.
pub use self::rpc_canary::SolanaNodeVersion;
/// Contact information returned for one cluster node.
pub use self::rpc_cluster::SolanaClusterNode;
/// Epoch-credit history entry returned by `getVoteAccounts`.
@@ -163,15 +163,11 @@ pub use self::rpc_common::SolanaContextConfig;
pub use self::rpc_common::SolanaRpcContext;
/// Generic contextual result returned by typed Solana HTTP RPC adapters.
pub use self::rpc_common::SolanaRpcResponse;
/// Exclusive selector accepted by token-account list RPC methods.
pub use self::rpc_tokens::SolanaTokenAccountSelector;
/// Token-account balance entry returned by `getTokenLargestAccounts`.
pub use self::rpc_tokens::SolanaTokenAccountBalance;
/// Token amount returned by Solana HTTP token RPC methods.
pub use self::rpc_tokens::SolanaTokenAmount;
/// Decodes one private serde wire type into the shared Transport error domain.
#[cfg(test)]
/// Decodes one private serde wire type into the shared Transport error domain for staged DTO tests.
pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics.
#[cfg(test)]
/// Parses a base58 public key without echoing its wire value into diagnostics for staged DTO tests.
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Functional category used by the audited Solana HTTP JSON-RPC registry.
pub use self::rpc_method::HttpRpcCategory;
@@ -195,6 +191,12 @@ pub use self::rpc_method::current_http_rpc_methods;
pub use self::rpc_method::find_http_rpc_method;
/// Returns historically documented deprecated HTTP RPC descriptors retained for compliance history.
pub use self::rpc_method::historical_http_rpc_methods;
/// Token-account balance entry returned by `getTokenLargestAccounts`.
pub use self::rpc_tokens::SolanaTokenAccountBalance;
/// Exclusive selector accepted by token-account list RPC methods.
pub use self::rpc_tokens::SolanaTokenAccountSelector;
/// Token amount returned by Solana HTTP token RPC methods.
pub use self::rpc_tokens::SolanaTokenAmount;
/// Open cluster or network descriptor used by HTTP endpoint settings.
pub use self::settings::HttpClusterName;
/// Runtime settings for one role declared by an HTTP endpoint.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_accounts.rs
// version: 1
// version: 2
/// Account-data encoding accepted by Solana HTTP account methods.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -29,6 +29,7 @@ impl SolanaAccountEncoding {
};
}
#[cfg(test)]
fn from_wire(value: &str) -> std::option::Option<Self> {
return match value {
"binary" => std::option::Option::Some(Self::Binary),
@@ -67,6 +68,7 @@ impl SolanaDataSliceConfig {
return self.length;
}
#[cfg(test)]
fn to_json_value(self) -> serde_json::Value {
return serde_json::json!({"offset": self.offset, "length": self.length});
}
@@ -116,14 +118,9 @@ impl SolanaAccountInfoConfig {
return self.context.min_context_slot();
}
/// Returns whether this config serializes to an empty JSON object.
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
return self.encoding.is_none() && self.data_slice.is_none() && self.context.is_empty();
}
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
let context_value = self.context.to_json_value();
let mut object = match context_value {
@@ -150,6 +147,7 @@ pub enum SolanaLargestAccountsFilter {
}
impl SolanaLargestAccountsFilter {
#[cfg(test)]
fn as_str(self) -> &'static str {
return match self {
Self::Circulating => "circulating",
@@ -197,6 +195,7 @@ impl SolanaLargestAccountsConfig {
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment {
@@ -249,6 +248,7 @@ impl SolanaMemcmpFilter {
return &self.bytes;
}
#[cfg(test)]
fn to_json_value(&self) -> serde_json::Value {
return match &self.bytes {
crate::SolanaMemcmpBytes::Base58(bytes) => serde_json::json!({"offset": self.offset, "bytes": bytes, "encoding": "base58"}),
@@ -270,6 +270,7 @@ pub enum SolanaProgramAccountFilter {
}
impl SolanaProgramAccountFilter {
#[cfg(test)]
fn to_json_value(&self) -> serde_json::Value {
return match self {
Self::DataSize(size) => serde_json::json!({"dataSize": size}),
@@ -326,6 +327,7 @@ impl SolanaProgramAccountsConfig {
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
let account_value = self.account_config.to_json_value();
let mut object = match account_value {
@@ -391,6 +393,7 @@ pub enum SolanaAccountData {
}
impl SolanaAccountData {
#[cfg(test)]
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireAccountData>(method, value);
let wire = match decoded {
@@ -399,11 +402,9 @@ impl SolanaAccountData {
};
return match wire {
WireAccountData::LegacyBinary(value) => std::result::Result::Ok(Self::LegacyBinary(value)),
WireAccountData::JsonParsed(value) => std::result::Result::Ok(Self::JsonParsed(crate::SolanaParsedAccountData {
program: value.program,
parsed: value.parsed,
space: value.space,
})),
WireAccountData::JsonParsed(value) => {
std::result::Result::Ok(Self::JsonParsed(crate::SolanaParsedAccountData { program: value.program, parsed: value.parsed, space: value.space }))
},
WireAccountData::Encoded((data, encoding)) => {
let parsed = crate::SolanaAccountEncoding::from_wire(encoding.as_str());
let encoding = match parsed {
@@ -413,7 +414,7 @@ impl SolanaAccountData {
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "account data tuple uses an unknown encoding")
.with_context("rpc_method", method),
);
}
},
};
if encoding == crate::SolanaAccountEncoding::Binary || encoding == crate::SolanaAccountEncoding::JsonParsed {
return std::result::Result::Err(
@@ -422,7 +423,7 @@ impl SolanaAccountData {
);
}
std::result::Result::Ok(Self::Encoded { data, encoding })
}
},
};
}
}
@@ -476,6 +477,7 @@ impl SolanaAccount {
}
/// Decodes one account DTO from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireAccount>(method, value);
let wire = match decoded {
@@ -524,6 +526,7 @@ impl SolanaKeyedAccount {
}
/// Decodes one keyed account from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireKeyedAccount>(method, value);
let wire = match decoded {
@@ -565,6 +568,7 @@ impl SolanaAccountBalance {
}
/// Decodes one account-balance entry from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireAccountBalance>(method, value);
let wire = match decoded {
@@ -588,6 +592,7 @@ pub enum SolanaProgramAccountsResult {
Context(crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>),
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum WireAccountData {
@@ -596,6 +601,7 @@ enum WireAccountData {
Encoded((std::string::String, std::string::String)),
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireParsedAccountData {
program: std::string::String,
@@ -603,6 +609,7 @@ struct WireParsedAccountData {
space: u64,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireAccount {
lamports: u64,
@@ -615,12 +622,14 @@ struct WireAccount {
space: std::option::Option<u64>,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireKeyedAccount {
pubkey: std::string::String,
account: serde_json::Value,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireAccountBalance {
address: std::string::String,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
// version: 1
// version: 2
/// Contact information returned for one cluster node.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -24,51 +24,82 @@ pub struct SolanaClusterNode {
impl SolanaClusterNode {
/// Returns the node identity public key.
#[must_use]
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey { return &self.pubkey; }
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.pubkey;
}
/// Returns the optional feature-set identifier.
#[must_use]
pub const fn feature_set(&self) -> std::option::Option<u32> { return self.feature_set; }
pub const fn feature_set(&self) -> std::option::Option<u32> {
return self.feature_set;
}
/// Returns the optional gossip endpoint.
#[must_use]
pub fn gossip(&self) -> std::option::Option<&str> { return self.gossip.as_deref(); }
pub fn gossip(&self) -> std::option::Option<&str> {
return self.gossip.as_deref();
}
/// Returns the optional PubSub endpoint.
#[must_use]
pub fn pubsub(&self) -> std::option::Option<&str> { return self.pubsub.as_deref(); }
pub fn pubsub(&self) -> std::option::Option<&str> {
return self.pubsub.as_deref();
}
/// Returns the optional JSON-RPC endpoint.
#[must_use]
pub fn rpc(&self) -> std::option::Option<&str> { return self.rpc.as_deref(); }
pub fn rpc(&self) -> std::option::Option<&str> {
return self.rpc.as_deref();
}
/// Returns the optional repair endpoint.
#[must_use]
pub fn serve_repair(&self) -> std::option::Option<&str> { return self.serve_repair.as_deref(); }
pub fn serve_repair(&self) -> std::option::Option<&str> {
return self.serve_repair.as_deref();
}
/// Returns the optional shred version.
#[must_use]
pub const fn shred_version(&self) -> std::option::Option<u16> { return self.shred_version; }
pub const fn shred_version(&self) -> std::option::Option<u16> {
return self.shred_version;
}
/// Returns the optional TPU endpoint.
#[must_use]
pub fn tpu(&self) -> std::option::Option<&str> { return self.tpu.as_deref(); }
pub fn tpu(&self) -> std::option::Option<&str> {
return self.tpu.as_deref();
}
/// Returns the optional TPU forwards endpoint.
#[must_use]
pub fn tpu_forwards(&self) -> std::option::Option<&str> { return self.tpu_forwards.as_deref(); }
pub fn tpu_forwards(&self) -> std::option::Option<&str> {
return self.tpu_forwards.as_deref();
}
/// Returns the optional TPU forwards QUIC endpoint.
#[must_use]
pub fn tpu_forwards_quic(&self) -> std::option::Option<&str> { return self.tpu_forwards_quic.as_deref(); }
pub fn tpu_forwards_quic(&self) -> std::option::Option<&str> {
return self.tpu_forwards_quic.as_deref();
}
/// Returns the optional TPU QUIC endpoint.
#[must_use]
pub fn tpu_quic(&self) -> std::option::Option<&str> { return self.tpu_quic.as_deref(); }
pub fn tpu_quic(&self) -> std::option::Option<&str> {
return self.tpu_quic.as_deref();
}
/// Returns the optional TPU vote endpoint.
#[must_use]
pub fn tpu_vote(&self) -> std::option::Option<&str> { return self.tpu_vote.as_deref(); }
pub fn tpu_vote(&self) -> std::option::Option<&str> {
return self.tpu_vote.as_deref();
}
/// Returns the optional TVU endpoint.
#[must_use]
pub fn tvu(&self) -> std::option::Option<&str> { return self.tvu.as_deref(); }
pub fn tvu(&self) -> std::option::Option<&str> {
return self.tvu.as_deref();
}
/// Returns the optional software-version string.
#[must_use]
pub fn version(&self) -> std::option::Option<&str> { return self.version.as_deref(); }
pub fn version(&self) -> std::option::Option<&str> {
return self.version.as_deref();
}
/// Returns the optional Agave client identifier extension.
#[must_use]
pub fn client_id(&self) -> std::option::Option<&str> { return self.client_id.as_deref(); }
pub fn client_id(&self) -> std::option::Option<&str> {
return self.client_id.as_deref();
}
/// Decodes one cluster-node contact record from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireClusterNode>(method, value);
let wire = match decoded {
@@ -114,24 +145,37 @@ pub struct SolanaEpochInfo {
impl SolanaEpochInfo {
/// Returns the absolute slot.
#[must_use]
pub const fn absolute_slot(&self) -> u64 { return self.absolute_slot; }
pub const fn absolute_slot(&self) -> u64 {
return self.absolute_slot;
}
/// Returns the block height.
#[must_use]
pub const fn block_height(&self) -> u64 { return self.block_height; }
pub const fn block_height(&self) -> u64 {
return self.block_height;
}
/// Returns the epoch number.
#[must_use]
pub const fn epoch(&self) -> u64 { return self.epoch; }
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
/// Returns the slot index within the epoch.
#[must_use]
pub const fn slot_index(&self) -> u64 { return self.slot_index; }
pub const fn slot_index(&self) -> u64 {
return self.slot_index;
}
/// Returns the number of slots in the epoch.
#[must_use]
pub const fn slots_in_epoch(&self) -> u64 { return self.slots_in_epoch; }
pub const fn slots_in_epoch(&self) -> u64 {
return self.slots_in_epoch;
}
/// Returns the nullable transaction count.
#[must_use]
pub const fn transaction_count(&self) -> std::option::Option<u64> { return self.transaction_count; }
pub const fn transaction_count(&self) -> std::option::Option<u64> {
return self.transaction_count;
}
/// Decodes epoch information from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireEpochInfo>(method, value);
return match decoded {
@@ -161,21 +205,32 @@ pub struct SolanaEpochSchedule {
impl SolanaEpochSchedule {
/// Returns the first normal epoch.
#[must_use]
pub const fn first_normal_epoch(&self) -> u64 { return self.first_normal_epoch; }
pub const fn first_normal_epoch(&self) -> u64 {
return self.first_normal_epoch;
}
/// Returns the first normal slot.
#[must_use]
pub const fn first_normal_slot(&self) -> u64 { return self.first_normal_slot; }
pub const fn first_normal_slot(&self) -> u64 {
return self.first_normal_slot;
}
/// Returns the leader-schedule slot offset.
#[must_use]
pub const fn leader_schedule_slot_offset(&self) -> u64 { return self.leader_schedule_slot_offset; }
pub const fn leader_schedule_slot_offset(&self) -> u64 {
return self.leader_schedule_slot_offset;
}
/// Returns the number of slots per epoch.
#[must_use]
pub const fn slots_per_epoch(&self) -> u64 { return self.slots_per_epoch; }
pub const fn slots_per_epoch(&self) -> u64 {
return self.slots_per_epoch;
}
/// Returns whether epoch warmup is enabled.
#[must_use]
pub const fn warmup(&self) -> bool { return self.warmup; }
pub const fn warmup(&self) -> bool {
return self.warmup;
}
/// Decodes an epoch schedule from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireEpochSchedule>(method, value);
return match decoded {
@@ -201,12 +256,17 @@ pub struct SolanaSnapshotSlotInfo {
impl SolanaSnapshotSlotInfo {
/// Returns the highest full snapshot slot.
#[must_use]
pub const fn full(&self) -> u64 { return self.full; }
pub const fn full(&self) -> u64 {
return self.full;
}
/// Returns the optional highest incremental snapshot slot.
#[must_use]
pub const fn incremental(&self) -> std::option::Option<u64> { return self.incremental; }
pub const fn incremental(&self) -> std::option::Option<u64> {
return self.incremental;
}
/// Decodes snapshot-slot information from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireSnapshotSlotInfo>(method, value);
return match decoded {
@@ -231,11 +291,19 @@ impl SolanaLeaderScheduleConfig {
}
/// Returns the optional validator identity filter.
#[must_use]
pub const fn identity(&self) -> std::option::Option<&ksp_core_lib::Pubkey> { return self.identity.as_ref(); }
pub const fn identity(&self) -> std::option::Option<&ksp_core_lib::Pubkey> {
return self.identity.as_ref();
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> { return self.commitment; }
fn is_empty(&self) -> bool { return self.identity.is_none() && self.commitment.is_none(); }
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
#[cfg(test)]
fn is_empty(&self) -> bool {
return self.identity.is_none() && self.commitment.is_none();
}
#[cfg(test)]
fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(identity) = self.identity.as_ref() {
@@ -263,12 +331,15 @@ pub enum SolanaLeaderScheduleRequest {
}
impl Default for SolanaLeaderScheduleRequest {
fn default() -> Self { return Self::CurrentEpoch(std::option::Option::None); }
fn default() -> Self {
return Self::CurrentEpoch(std::option::Option::None);
}
}
impl SolanaLeaderScheduleRequest {
/// Serializes the typed overload to the exact positional JSON-RPC params.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_params(&self) -> std::vec::Vec<serde_json::Value> {
return match self {
Self::CurrentEpoch(std::option::Option::None) => std::vec::Vec::new(),
@@ -290,9 +361,12 @@ pub struct SolanaLeaderSchedule {
impl SolanaLeaderSchedule {
/// Returns the complete leader schedule map.
#[must_use]
pub const fn entries(&self) -> &std::collections::BTreeMap<ksp_core_lib::Pubkey, std::vec::Vec<usize>> { return &self.entries; }
pub const fn entries(&self) -> &std::collections::BTreeMap<ksp_core_lib::Pubkey, std::vec::Vec<usize>> {
return &self.entries;
}
/// Decodes a leader schedule map from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<std::collections::BTreeMap<std::string::String, std::vec::Vec<usize>>>(method, value);
let wire = match decoded {
@@ -334,18 +408,27 @@ impl SolanaVoteAccountsConfig {
}
/// Returns the optional commitment.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> { return self.commitment; }
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns the optional vote-account public key filter.
#[must_use]
pub const fn vote_pubkey(&self) -> std::option::Option<&ksp_core_lib::Pubkey> { return self.vote_pubkey.as_ref(); }
pub const fn vote_pubkey(&self) -> std::option::Option<&ksp_core_lib::Pubkey> {
return self.vote_pubkey.as_ref();
}
/// Returns whether unstaked delinquent validators should be kept.
#[must_use]
pub const fn keep_unstaked_delinquents(&self) -> std::option::Option<bool> { return self.keep_unstaked_delinquents; }
pub const fn keep_unstaked_delinquents(&self) -> std::option::Option<bool> {
return self.keep_unstaked_delinquents;
}
/// Returns the optional delinquent slot distance.
#[must_use]
pub const fn delinquent_slot_distance(&self) -> std::option::Option<u64> { return self.delinquent_slot_distance; }
pub const fn delinquent_slot_distance(&self) -> std::option::Option<u64> {
return self.delinquent_slot_distance;
}
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment {
@@ -375,13 +458,19 @@ pub struct SolanaEpochCredits {
impl SolanaEpochCredits {
/// Returns the epoch number.
#[must_use]
pub const fn epoch(&self) -> u64 { return self.epoch; }
pub const fn epoch(&self) -> u64 {
return self.epoch;
}
/// Returns cumulative credits at the end of the epoch.
#[must_use]
pub const fn credits(&self) -> u64 { return self.credits; }
pub const fn credits(&self) -> u64 {
return self.credits;
}
/// Returns cumulative credits before the epoch.
#[must_use]
pub const fn previous_credits(&self) -> u64 { return self.previous_credits; }
pub const fn previous_credits(&self) -> u64 {
return self.previous_credits;
}
}
/// One validator vote-account record returned by `getVoteAccounts`.
@@ -401,33 +490,52 @@ pub struct SolanaVoteAccountInfo {
impl SolanaVoteAccountInfo {
/// Returns the vote account public key.
#[must_use]
pub const fn vote_pubkey(&self) -> &ksp_core_lib::Pubkey { return &self.vote_pubkey; }
pub const fn vote_pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.vote_pubkey;
}
/// Returns the validator identity public key.
#[must_use]
pub const fn node_pubkey(&self) -> &ksp_core_lib::Pubkey { return &self.node_pubkey; }
pub const fn node_pubkey(&self) -> &ksp_core_lib::Pubkey {
return &self.node_pubkey;
}
/// Returns the activated stake in lamports.
#[must_use]
pub const fn activated_stake(&self) -> u64 { return self.activated_stake; }
pub const fn activated_stake(&self) -> u64 {
return self.activated_stake;
}
/// Returns the legacy/effective percentage commission field.
#[must_use]
pub const fn commission(&self) -> u8 { return self.commission; }
pub const fn commission(&self) -> u8 {
return self.commission;
}
/// Returns the optional raw inflation-rewards commission in basis points.
#[must_use]
pub const fn inflation_rewards_commission_bps(&self) -> std::option::Option<u16> { return self.inflation_rewards_commission_bps; }
pub const fn inflation_rewards_commission_bps(&self) -> std::option::Option<u16> {
return self.inflation_rewards_commission_bps;
}
/// Returns whether the vote account is staked for the current epoch.
#[must_use]
pub const fn epoch_vote_account(&self) -> bool { return self.epoch_vote_account; }
pub const fn epoch_vote_account(&self) -> bool {
return self.epoch_vote_account;
}
/// Returns the bounded RPC epoch-credit history.
#[must_use]
pub fn epoch_credits(&self) -> &[crate::SolanaEpochCredits] { return self.epoch_credits.as_slice(); }
pub fn epoch_credits(&self) -> &[crate::SolanaEpochCredits] {
return self.epoch_credits.as_slice();
}
/// Returns the latest voted slot or zero when no vote exists.
#[must_use]
pub const fn last_vote(&self) -> u64 { return self.last_vote; }
pub const fn last_vote(&self) -> u64 {
return self.last_vote;
}
/// Returns the current root slot or zero when no root exists.
#[must_use]
pub const fn root_slot(&self) -> u64 { return self.root_slot; }
pub const fn root_slot(&self) -> u64 {
return self.root_slot;
}
/// Decodes one vote-account record from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireVoteAccountInfo>(method, value);
let wire = match decoded {
@@ -472,12 +580,17 @@ pub struct SolanaVoteAccountStatus {
impl SolanaVoteAccountStatus {
/// Returns current vote accounts.
#[must_use]
pub fn current(&self) -> &[crate::SolanaVoteAccountInfo] { return self.current.as_slice(); }
pub fn current(&self) -> &[crate::SolanaVoteAccountInfo] {
return self.current.as_slice();
}
/// Returns delinquent vote accounts.
#[must_use]
pub fn delinquent(&self) -> &[crate::SolanaVoteAccountInfo] { return self.delinquent.as_slice(); }
pub fn delinquent(&self) -> &[crate::SolanaVoteAccountInfo] {
return self.delinquent.as_slice();
}
/// Decodes the complete vote-account status response from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireVoteAccountStatus>(method, value);
let wire = match decoded {
@@ -504,26 +617,42 @@ impl SolanaVoteAccountStatus {
}
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireClusterNode {
pubkey: std::string::String,
#[serde(default)] feature_set: std::option::Option<u32>,
#[serde(default)] gossip: std::option::Option<std::string::String>,
#[serde(default)] pubsub: std::option::Option<std::string::String>,
#[serde(default)] rpc: std::option::Option<std::string::String>,
#[serde(default)] serve_repair: std::option::Option<std::string::String>,
#[serde(default)] shred_version: std::option::Option<u16>,
#[serde(default)] tpu: std::option::Option<std::string::String>,
#[serde(default)] tpu_forwards: std::option::Option<std::string::String>,
#[serde(default)] tpu_forwards_quic: std::option::Option<std::string::String>,
#[serde(default)] tpu_quic: std::option::Option<std::string::String>,
#[serde(default)] tpu_vote: std::option::Option<std::string::String>,
#[serde(default)] tvu: std::option::Option<std::string::String>,
#[serde(default)] version: std::option::Option<std::string::String>,
#[serde(default)] client_id: std::option::Option<std::string::String>,
#[serde(default)]
feature_set: std::option::Option<u32>,
#[serde(default)]
gossip: std::option::Option<std::string::String>,
#[serde(default)]
pubsub: std::option::Option<std::string::String>,
#[serde(default)]
rpc: std::option::Option<std::string::String>,
#[serde(default)]
serve_repair: std::option::Option<std::string::String>,
#[serde(default)]
shred_version: std::option::Option<u16>,
#[serde(default)]
tpu: std::option::Option<std::string::String>,
#[serde(default)]
tpu_forwards: std::option::Option<std::string::String>,
#[serde(default)]
tpu_forwards_quic: std::option::Option<std::string::String>,
#[serde(default)]
tpu_quic: std::option::Option<std::string::String>,
#[serde(default)]
tpu_vote: std::option::Option<std::string::String>,
#[serde(default)]
tvu: std::option::Option<std::string::String>,
#[serde(default)]
version: std::option::Option<std::string::String>,
#[serde(default)]
client_id: std::option::Option<std::string::String>,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireEpochInfo {
@@ -535,6 +664,7 @@ struct WireEpochInfo {
transaction_count: std::option::Option<u64>,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireEpochSchedule {
@@ -545,12 +675,14 @@ struct WireEpochSchedule {
warmup: bool,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireSnapshotSlotInfo {
full: u64,
incremental: std::option::Option<u64>,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireVoteAccountInfo {
@@ -566,6 +698,7 @@ struct WireVoteAccountInfo {
root_slot: u64,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireVoteAccountStatus {
current: std::vec::Vec<serde_json::Value>,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_common.rs
// version: 1
// version: 2
/// Commitment level accepted by typed Solana HTTP RPC adapters.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -43,15 +43,10 @@ impl SolanaCommitmentConfig {
return self.commitment;
}
/// Returns whether this config serializes to an empty JSON object.
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none();
}
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
#[cfg(test)]
pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment {
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
@@ -86,15 +81,9 @@ impl SolanaContextConfig {
return self.min_context_slot;
}
/// Returns whether this config serializes to an empty JSON object.
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.min_context_slot.is_none();
}
/// Serializes this config to the Solana JSON-RPC wire object.
#[must_use]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
pub(crate) fn to_json_value(self) -> serde_json::Value {
let mut object = serde_json::Map::new();
if let std::option::Option::Some(commitment) = self.commitment {
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
@@ -129,7 +118,8 @@ impl SolanaRpcContext {
};
}
/// Decodes one RPC context from a parsed JSON value.
/// Decodes one RPC context from a parsed JSON value for staged DTO tests.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireRpcContext>(method, value);
let context = match decoded {
@@ -160,7 +150,8 @@ impl<T> SolanaRpcResponse<T> {
return &self.value;
}
/// Creates a contextual response after wire decoding and validation.
/// Creates a contextual response after wire decoding and validation for staged DTO tests.
#[cfg(test)]
#[must_use]
pub(crate) const fn new(context: crate::SolanaRpcContext, value: T) -> Self {
return Self { context, value };
@@ -168,6 +159,7 @@ impl<T> SolanaRpcResponse<T> {
}
/// Decodes one private serde wire type and maps shape failures to the shared Transport error domain.
#[cfg(test)]
pub(crate) fn decode_wire_json<T: serde::de::DeserializeOwned>(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<T> {
let decoded = serde_json::from_value::<T>(value);
return match decoded {
@@ -181,6 +173,7 @@ pub(crate) fn decode_wire_json<T: serde::de::DeserializeOwned>(method: &str, val
}
/// Parses a base58 public key from one wire field without echoing its value into diagnostics.
#[cfg(test)]
pub(crate) fn parse_wire_pubkey(method: &str, field: &str, value: &str) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
let parsed = value.parse::<ksp_core_lib::Pubkey>();
return match parsed {
@@ -193,6 +186,7 @@ pub(crate) fn parse_wire_pubkey(method: &str, field: &str, value: &str) -> ksp_c
};
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireRpcContext {
slot: u64,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_tokens.rs
// version: 1
// version: 2
/// Exclusive selector accepted by token-account list RPC methods.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -13,6 +13,7 @@ pub enum SolanaTokenAccountSelector {
impl SolanaTokenAccountSelector {
/// Serializes the exclusive selector to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
return match self {
Self::Mint(pubkey) => serde_json::json!({"mint": pubkey.to_string()}),
@@ -56,6 +57,7 @@ impl SolanaTokenAmount {
}
/// Decodes one token amount from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireTokenAmount>(method, value);
return match decoded {
@@ -91,6 +93,7 @@ impl SolanaTokenAccountBalance {
}
/// Decodes one token-account balance entry from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireTokenAccountBalance>(method, value);
let wire = match decoded {
@@ -112,6 +115,7 @@ impl SolanaTokenAccountBalance {
}
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireTokenAmount {
amount: std::string::String,
@@ -122,6 +126,7 @@ struct WireTokenAmount {
ui_amount_string: std::string::String,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireTokenAccountBalance {
address: std::string::String,