1115 lines
45 KiB
Rust
1115 lines
45 KiB
Rust
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
|
// version: 8
|
|
|
|
const MAX_GET_BLOCKS_RANGE: u64 = 500_000;
|
|
const MAX_GET_RECENT_PERFORMANCE_SAMPLES: u64 = 720;
|
|
|
|
/// Transaction detail level accepted by modern `getBlock` requests.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
|
pub enum SolanaTransactionDetails {
|
|
/// Return complete transaction payloads and metadata.
|
|
#[default]
|
|
Full,
|
|
/// Return transaction signatures only.
|
|
Signatures,
|
|
/// Return no transaction payloads.
|
|
None,
|
|
/// Return the account-list transaction representation.
|
|
Accounts,
|
|
}
|
|
|
|
impl SolanaTransactionDetails {
|
|
/// Returns the Solana JSON-RPC wire string.
|
|
#[must_use]
|
|
pub const fn as_str(self) -> &'static str {
|
|
return match self {
|
|
Self::Full => "full",
|
|
Self::Signatures => "signatures",
|
|
Self::None => "none",
|
|
Self::Accounts => "accounts",
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Modern configuration object accepted by `getBlock`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaGetBlockConfig {
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
|
transaction_details: std::option::Option<crate::SolanaTransactionDetails>,
|
|
max_supported_transaction_version: std::option::Option<u8>,
|
|
rewards: std::option::Option<bool>,
|
|
}
|
|
|
|
impl SolanaGetBlockConfig {
|
|
/// Creates a modern `getBlock` configuration.
|
|
#[must_use]
|
|
pub const fn new(
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
|
transaction_details: std::option::Option<crate::SolanaTransactionDetails>,
|
|
max_supported_transaction_version: std::option::Option<u8>,
|
|
rewards: std::option::Option<bool>,
|
|
) -> Self {
|
|
return Self { commitment, encoding, transaction_details, max_supported_transaction_version, rewards };
|
|
}
|
|
|
|
/// Returns the optional commitment level.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.commitment;
|
|
}
|
|
|
|
/// Returns the optional transaction encoding.
|
|
#[must_use]
|
|
pub const fn encoding(&self) -> std::option::Option<crate::SolanaTransactionEncoding> {
|
|
return self.encoding;
|
|
}
|
|
|
|
/// Returns the optional transaction detail level.
|
|
#[must_use]
|
|
pub const fn transaction_details(&self) -> std::option::Option<crate::SolanaTransactionDetails> {
|
|
return self.transaction_details;
|
|
}
|
|
|
|
/// Returns the highest transaction version the caller declares it can consume.
|
|
#[must_use]
|
|
pub const fn max_supported_transaction_version(&self) -> std::option::Option<u8> {
|
|
return self.max_supported_transaction_version;
|
|
}
|
|
|
|
/// Returns whether rewards were explicitly requested or suppressed.
|
|
#[must_use]
|
|
pub const fn rewards(&self) -> std::option::Option<bool> {
|
|
return self.rewards;
|
|
}
|
|
|
|
/// Returns whether this config would serialize to an empty object.
|
|
#[cfg(test)]
|
|
pub(crate) const fn is_empty(&self) -> bool {
|
|
return self.commitment.is_none()
|
|
&& self.encoding.is_none()
|
|
&& self.transaction_details.is_none()
|
|
&& self.max_supported_transaction_version.is_none()
|
|
&& self.rewards.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the exact Solana JSON-RPC object.
|
|
#[must_use]
|
|
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()));
|
|
}
|
|
if let std::option::Option::Some(encoding) = self.encoding {
|
|
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(details) = self.transaction_details {
|
|
object.insert("transactionDetails".to_owned(), serde_json::Value::String(details.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(version) = self.max_supported_transaction_version {
|
|
object.insert("maxSupportedTransactionVersion".to_owned(), serde_json::Value::Number(version.into()));
|
|
}
|
|
if let std::option::Option::Some(rewards) = self.rewards {
|
|
object.insert("rewards".to_owned(), serde_json::Value::Bool(rewards));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Slot range accepted inside `getBlockProduction` configuration.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct SolanaBlockProductionRange {
|
|
first_slot: u64,
|
|
last_slot: std::option::Option<u64>,
|
|
}
|
|
|
|
impl SolanaBlockProductionRange {
|
|
/// Creates a block-production range.
|
|
#[must_use]
|
|
pub const fn new(first_slot: u64, last_slot: std::option::Option<u64>) -> Self {
|
|
return Self { first_slot, last_slot };
|
|
}
|
|
|
|
/// Returns the first slot in the requested range.
|
|
#[must_use]
|
|
pub const fn first_slot(&self) -> u64 {
|
|
return self.first_slot;
|
|
}
|
|
|
|
/// Returns the optional last slot in the requested range.
|
|
#[must_use]
|
|
pub const fn last_slot(&self) -> std::option::Option<u64> {
|
|
return self.last_slot;
|
|
}
|
|
|
|
/// Serializes this range to the exact Solana JSON-RPC object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
object.insert("firstSlot".to_owned(), serde_json::Value::Number(self.first_slot.into()));
|
|
if let std::option::Option::Some(last_slot) = self.last_slot {
|
|
object.insert("lastSlot".to_owned(), serde_json::Value::Number(last_slot.into()));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Optional configuration accepted by `getBlockProduction`.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaBlockProductionConfig {
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
identity: std::option::Option<ksp_core_lib::Pubkey>,
|
|
range: std::option::Option<crate::SolanaBlockProductionRange>,
|
|
}
|
|
|
|
impl SolanaBlockProductionConfig {
|
|
/// Creates a block-production configuration.
|
|
#[must_use]
|
|
pub const fn new(
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
identity: std::option::Option<ksp_core_lib::Pubkey>,
|
|
range: std::option::Option<crate::SolanaBlockProductionRange>,
|
|
) -> Self {
|
|
return Self { commitment, identity, range };
|
|
}
|
|
|
|
/// Returns the optional commitment level.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.commitment;
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
|
|
/// Returns the optional slot range.
|
|
#[must_use]
|
|
pub const fn range(&self) -> std::option::Option<&crate::SolanaBlockProductionRange> {
|
|
return self.range.as_ref();
|
|
}
|
|
|
|
/// Returns whether this config would serialize to an empty object.
|
|
pub(crate) const fn is_empty(&self) -> bool {
|
|
return self.commitment.is_none() && self.identity.is_none() && self.range.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the exact Solana JSON-RPC object.
|
|
#[must_use]
|
|
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()));
|
|
}
|
|
if let std::option::Option::Some(identity) = self.identity.as_ref() {
|
|
object.insert("identity".to_owned(), serde_json::Value::String(identity.to_string()));
|
|
}
|
|
if let std::option::Option::Some(range) = self.range {
|
|
object.insert("range".to_owned(), range.to_json_value());
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Commitment distribution returned by `getBlockCommitment`.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct SolanaBlockCommitment {
|
|
commitment: std::option::Option<std::vec::Vec<u64>>,
|
|
total_stake: u64,
|
|
}
|
|
|
|
impl SolanaBlockCommitment {
|
|
/// Returns the nullable commitment stake distribution.
|
|
#[must_use]
|
|
pub fn commitment(&self) -> std::option::Option<&[u64]> {
|
|
return self.commitment.as_deref();
|
|
}
|
|
|
|
/// Returns the total active stake considered by the response.
|
|
#[must_use]
|
|
pub const fn total_stake(&self) -> u64 {
|
|
return self.total_stake;
|
|
}
|
|
|
|
/// Decodes a block-commitment result from its Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireBlockCommitment>(method, value);
|
|
return match decoded {
|
|
std::result::Result::Ok(wire) => std::result::Result::Ok(Self { commitment: wire.commitment, total_stake: wire.total_stake }),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Effective range reported inside `getBlockProduction` results.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct SolanaBlockProductionResultRange {
|
|
first_slot: u64,
|
|
last_slot: u64,
|
|
}
|
|
|
|
impl SolanaBlockProductionResultRange {
|
|
/// Returns the first slot in the effective range.
|
|
#[must_use]
|
|
pub const fn first_slot(&self) -> u64 {
|
|
return self.first_slot;
|
|
}
|
|
|
|
/// Returns the last slot in the effective range.
|
|
#[must_use]
|
|
pub const fn last_slot(&self) -> u64 {
|
|
return self.last_slot;
|
|
}
|
|
}
|
|
|
|
/// Block-production counts returned by `getBlockProduction` before the shared RPC context wrapper is applied.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct SolanaBlockProduction {
|
|
by_identity: std::collections::BTreeMap<ksp_core_lib::Pubkey, (usize, usize)>,
|
|
range: crate::SolanaBlockProductionResultRange,
|
|
}
|
|
|
|
impl SolanaBlockProduction {
|
|
/// Returns validator identities mapped to `(leader slots, blocks produced)` counts.
|
|
#[must_use]
|
|
pub const fn by_identity(&self) -> &std::collections::BTreeMap<ksp_core_lib::Pubkey, (usize, usize)> {
|
|
return &self.by_identity;
|
|
}
|
|
|
|
/// Returns the effective slot range reported by the runtime.
|
|
#[must_use]
|
|
pub const fn range(&self) -> &crate::SolanaBlockProductionResultRange {
|
|
return &self.range;
|
|
}
|
|
|
|
/// Decodes a block-production result from its Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireBlockProduction>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut by_identity = std::collections::BTreeMap::new();
|
|
for (identity, counts) in wire.by_identity {
|
|
let parsed = crate::parse_wire_pubkey(method, "byIdentity", identity.as_str());
|
|
let identity = match parsed {
|
|
std::result::Result::Ok(identity) => identity,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
by_identity.insert(identity, counts);
|
|
}
|
|
return std::result::Result::Ok(Self {
|
|
by_identity,
|
|
range: crate::SolanaBlockProductionResultRange { first_slot: wire.range.first_slot, last_slot: wire.range.last_slot },
|
|
});
|
|
}
|
|
}
|
|
|
|
/// One reward entry returned in a confirmed block.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaBlockReward {
|
|
pubkey: ksp_core_lib::Pubkey,
|
|
lamports: i64,
|
|
post_balance: u64,
|
|
reward_type: std::option::Option<std::string::String>,
|
|
commission: std::option::Option<u8>,
|
|
commission_bps: crate::SolanaWireField<u16>,
|
|
}
|
|
|
|
impl SolanaBlockReward {
|
|
/// Returns the rewarded account public key.
|
|
#[must_use]
|
|
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
|
return &self.pubkey;
|
|
}
|
|
|
|
/// Returns the signed lamport delta applied by the reward.
|
|
#[must_use]
|
|
pub const fn lamports(&self) -> i64 {
|
|
return self.lamports;
|
|
}
|
|
|
|
/// Returns the post-reward account balance in lamports.
|
|
#[must_use]
|
|
pub const fn post_balance(&self) -> u64 {
|
|
return self.post_balance;
|
|
}
|
|
|
|
/// Returns the runtime reward type text without imposing a local economic taxonomy.
|
|
#[must_use]
|
|
pub fn reward_type(&self) -> std::option::Option<&str> {
|
|
return self.reward_type.as_deref();
|
|
}
|
|
|
|
/// Returns the nullable legacy/effective commission percentage.
|
|
#[must_use]
|
|
pub const fn commission(&self) -> std::option::Option<u8> {
|
|
return self.commission;
|
|
}
|
|
|
|
/// Returns the basis-point commission while preserving omitted/null/present wire states.
|
|
#[must_use]
|
|
pub const fn commission_bps(&self) -> &crate::SolanaWireField<u16> {
|
|
return &self.commission_bps;
|
|
}
|
|
|
|
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireBlockReward>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let pubkey = crate::parse_wire_pubkey(method, "rewards.pubkey", wire.pubkey.as_str());
|
|
return match pubkey {
|
|
std::result::Result::Ok(pubkey) => std::result::Result::Ok(Self {
|
|
pubkey,
|
|
lamports: wire.lamports,
|
|
post_balance: wire.post_balance,
|
|
reward_type: wire.reward_type,
|
|
commission: wire.commission,
|
|
commission_bps: wire.commission_bps,
|
|
}),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// One transaction entry returned inside a confirmed block.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaBlockTransaction {
|
|
transaction: crate::SolanaEncodedTransaction,
|
|
meta: crate::SolanaWireField<serde_json::Value>,
|
|
version: crate::SolanaWireField<crate::SolanaTransactionVersion>,
|
|
}
|
|
|
|
impl SolanaBlockTransaction {
|
|
/// Returns the encoded or JSON transaction payload without Program-specific decoding.
|
|
#[must_use]
|
|
pub const fn transaction(&self) -> &crate::SolanaEncodedTransaction {
|
|
return &self.transaction;
|
|
}
|
|
|
|
/// Returns transaction metadata while preserving omission versus explicit `null`.
|
|
#[must_use]
|
|
pub const fn meta(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.meta;
|
|
}
|
|
|
|
/// Returns the transaction version while preserving omission versus explicit `null`.
|
|
#[must_use]
|
|
pub const fn version(&self) -> &crate::SolanaWireField<crate::SolanaTransactionVersion> {
|
|
return &self.version;
|
|
}
|
|
|
|
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireBlockTransaction>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let transaction = crate::SolanaEncodedTransaction::decode_wire(method, wire.transaction);
|
|
let transaction = match transaction {
|
|
std::result::Result::Ok(transaction) => transaction,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let version = decode_block_transaction_version(method, wire.version);
|
|
return match version {
|
|
std::result::Result::Ok(version) => std::result::Result::Ok(Self { transaction, meta: wire.meta, version }),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Confirmed block wire result returned by `getBlock` when the RPC result is non-null.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaConfirmedBlock {
|
|
previous_blockhash: std::string::String,
|
|
blockhash: std::string::String,
|
|
parent_slot: u64,
|
|
transactions: crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>>,
|
|
signatures: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
|
rewards: crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>>,
|
|
num_reward_partitions: crate::SolanaWireField<u64>,
|
|
block_time: std::option::Option<i64>,
|
|
block_height: std::option::Option<u64>,
|
|
}
|
|
|
|
impl SolanaConfirmedBlock {
|
|
/// Returns the previous blockhash exactly as reported by the RPC wire.
|
|
#[must_use]
|
|
pub fn previous_blockhash(&self) -> &str {
|
|
return self.previous_blockhash.as_str();
|
|
}
|
|
|
|
/// Returns this block's blockhash exactly as reported by the RPC wire.
|
|
#[must_use]
|
|
pub fn blockhash(&self) -> &str {
|
|
return self.blockhash.as_str();
|
|
}
|
|
|
|
/// Returns the parent slot.
|
|
#[must_use]
|
|
pub const fn parent_slot(&self) -> u64 {
|
|
return self.parent_slot;
|
|
}
|
|
|
|
/// Returns block transactions while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn transactions(&self) -> &crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>> {
|
|
return &self.transactions;
|
|
}
|
|
|
|
/// Returns block signatures while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn signatures(&self) -> &crate::SolanaWireField<std::vec::Vec<std::string::String>> {
|
|
return &self.signatures;
|
|
}
|
|
|
|
/// Returns block rewards while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn rewards(&self) -> &crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>> {
|
|
return &self.rewards;
|
|
}
|
|
|
|
/// Returns the SIMD-0118 reward-partition count while preserving omission versus explicit `null`.
|
|
#[must_use]
|
|
pub const fn num_reward_partitions(&self) -> &crate::SolanaWireField<u64> {
|
|
return &self.num_reward_partitions;
|
|
}
|
|
|
|
/// Returns the nullable block time as a Unix timestamp.
|
|
#[must_use]
|
|
pub const fn block_time(&self) -> std::option::Option<i64> {
|
|
return self.block_time;
|
|
}
|
|
|
|
/// Returns the nullable block height.
|
|
#[must_use]
|
|
pub const fn block_height(&self) -> std::option::Option<u64> {
|
|
return self.block_height;
|
|
}
|
|
|
|
/// Decodes a confirmed block from its Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireConfirmedBlock>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let transactions = decode_block_transactions(method, wire.transactions);
|
|
let transactions = match transactions {
|
|
std::result::Result::Ok(transactions) => transactions,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let rewards = decode_block_rewards(method, wire.rewards);
|
|
let rewards = match rewards {
|
|
std::result::Result::Ok(rewards) => rewards,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(Self {
|
|
previous_blockhash: wire.previous_blockhash,
|
|
blockhash: wire.blockhash,
|
|
parent_slot: wire.parent_slot,
|
|
transactions,
|
|
signatures: wire.signatures,
|
|
rewards,
|
|
num_reward_partitions: wire.num_reward_partitions,
|
|
block_time: wire.block_time,
|
|
block_height: wire.block_height,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// One entry returned by `getRecentPerformanceSamples`.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct SolanaPerformanceSample {
|
|
slot: u64,
|
|
num_transactions: u64,
|
|
num_non_vote_transactions: std::option::Option<u64>,
|
|
num_slots: u64,
|
|
sample_period_secs: u16,
|
|
}
|
|
|
|
impl SolanaPerformanceSample {
|
|
/// Returns the sample's highest slot.
|
|
#[must_use]
|
|
pub const fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
/// Returns the total transaction count in the sample.
|
|
#[must_use]
|
|
pub const fn num_transactions(&self) -> u64 {
|
|
return self.num_transactions;
|
|
}
|
|
|
|
/// Returns the optional non-vote transaction count exposed by newer runtimes.
|
|
#[must_use]
|
|
pub const fn num_non_vote_transactions(&self) -> std::option::Option<u64> {
|
|
return self.num_non_vote_transactions;
|
|
}
|
|
|
|
/// Returns the number of slots covered by the sample.
|
|
#[must_use]
|
|
pub const fn num_slots(&self) -> u64 {
|
|
return self.num_slots;
|
|
}
|
|
|
|
/// Returns the sample duration in seconds.
|
|
#[must_use]
|
|
pub const fn sample_period_secs(&self) -> u16 {
|
|
return self.sample_period_secs;
|
|
}
|
|
|
|
/// Decodes one performance sample from its Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WirePerformanceSample>(method, value);
|
|
return match decoded {
|
|
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
|
|
slot: wire.slot,
|
|
num_transactions: wire.num_transactions,
|
|
num_non_vote_transactions: wire.num_non_vote_transactions,
|
|
num_slots: wire.num_slots,
|
|
sample_period_secs: wire.sample_period_secs,
|
|
}),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
impl crate::HttpTransportPool {
|
|
/// Executes the current object-form `getBlock` request through the common KSP HTTP transport path.
|
|
///
|
|
/// `None` omits the optional second parameter. `Some(config)` sends the modern object form exactly, including an empty `{}` when the caller
|
|
/// explicitly supplies an empty modern configuration. The runtime requires an explicitly supplied commitment to be at least `confirmed`.
|
|
pub async fn get_block(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
slot: u64,
|
|
config: std::option::Option<&crate::SolanaGetBlockConfig>,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
|
let params = get_block_params(slot, config);
|
|
let params = match params {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return self.execute_get_block(role, params).await;
|
|
}
|
|
|
|
/// Executes the current object-form `getBlock` request and reports the safe identity of the endpoint that produced the successful response.
|
|
///
|
|
/// Routing, admission, timeout and retry behavior are identical to [`Self::get_block`]. The returned observation never contains an endpoint URL,
|
|
/// HTTP headers or a raw HTTP body.
|
|
pub async fn get_block_observed(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
slot: u64,
|
|
config: std::option::Option<&crate::SolanaGetBlockConfig>,
|
|
) -> ksp_core_lib::Result<crate::HttpObservedValue<std::option::Option<crate::SolanaConfirmedBlock>>> {
|
|
let params = get_block_params(slot, config);
|
|
let params = match params {
|
|
std::result::Result::Ok(params) => params,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let observed = self.execute_blocks_rpc_observed("getBlock", role, params).await;
|
|
let observed = match observed {
|
|
std::result::Result::Ok(observed) => observed,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let (value, endpoint_name, provider) = observed.into_parts();
|
|
let block = decode_get_block(value);
|
|
return match block {
|
|
std::result::Result::Ok(block) => std::result::Result::Ok(crate::HttpObservedValue::new(block, endpoint_name, provider)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes the deprecated bare-encoding `getBlock` request form retained by Solana RPC for backwards compatibility.
|
|
#[deprecated(note = "use HttpTransportPool::get_block with SolanaGetBlockConfig; the bare encoding request form is deprecated")]
|
|
pub async fn get_block_legacy(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
slot: u64,
|
|
encoding: crate::SolanaTransactionEncoding,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
rpc_method = "getBlock",
|
|
request_form = "bare_encoding",
|
|
encoding = encoding.as_str(),
|
|
"deprecated Solana HTTP RPC request form used"
|
|
);
|
|
let params = std::vec![serde_json::json!(slot), serde_json::Value::String(encoding.as_str().to_owned())];
|
|
return self.execute_get_block(role, params).await;
|
|
}
|
|
|
|
async fn execute_get_block(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
|
let value = self.execute_blocks_rpc("getBlock", role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => decode_get_block(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getBlockCommitment` through the common KSP HTTP transport path.
|
|
pub async fn get_block_commitment(&self, role: &crate::HttpRoleName, slot: u64) -> ksp_core_lib::Result<crate::SolanaBlockCommitment> {
|
|
let value = self.execute_blocks_rpc("getBlockCommitment", role, std::vec![serde_json::json!(slot)]).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::SolanaBlockCommitment::decode_wire("getBlockCommitment", value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getBlockHeight` through the common KSP HTTP transport path.
|
|
pub async fn get_block_height(&self, role: &crate::HttpRoleName, config: std::option::Option<&crate::SolanaContextConfig>) -> ksp_core_lib::Result<u64> {
|
|
let mut params = std::vec::Vec::new();
|
|
push_blocks_context_config(&mut params, config);
|
|
return self.get_blocks_u64("getBlockHeight", role, params).await;
|
|
}
|
|
|
|
/// Executes typed `getBlockTime` and preserves a runtime `null` as `None`.
|
|
pub async fn get_block_time(&self, role: &crate::HttpRoleName, slot: u64) -> ksp_core_lib::Result<std::option::Option<i64>> {
|
|
let value = self.execute_blocks_rpc("getBlockTime", role, std::vec![serde_json::json!(slot)]).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<std::option::Option<i64>>("getBlockTime", value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getFirstAvailableBlock` through the common KSP HTTP transport path.
|
|
pub async fn get_first_available_block(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<u64> {
|
|
return self.get_blocks_u64("getFirstAvailableBlock", role, std::vec::Vec::new()).await;
|
|
}
|
|
|
|
/// Executes typed `minimumLedgerSlot` through the common KSP HTTP transport path.
|
|
pub async fn minimum_ledger_slot(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<u64> {
|
|
return self.get_blocks_u64("minimumLedgerSlot", role, std::vec::Vec::new()).await;
|
|
}
|
|
|
|
/// Executes typed `getBlocks` while preserving all four supported parameter overloads.
|
|
pub async fn get_blocks(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
start_slot: u64,
|
|
end_slot: std::option::Option<u64>,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
|
let validated = validate_blocks_context_commitment("getBlocks", config);
|
|
if let std::result::Result::Err(error) = validated {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::option::Option::Some(end_slot) = end_slot
|
|
&& end_slot >= start_slot
|
|
&& end_slot - start_slot > MAX_GET_BLOCKS_RANGE
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "getBlocks range must not exceed 500000 slots")
|
|
.with_context("rpc_method", "getBlocks")
|
|
.with_context("start_slot", start_slot.to_string())
|
|
.with_context("end_slot", end_slot.to_string()),
|
|
);
|
|
}
|
|
let mut params = std::vec![serde_json::json!(start_slot)];
|
|
if let std::option::Option::Some(end_slot) = end_slot {
|
|
params.push(serde_json::json!(end_slot));
|
|
}
|
|
push_blocks_explicit_context_config(&mut params, config);
|
|
return self.get_blocks_u64_list("getBlocks", role, params).await;
|
|
}
|
|
|
|
/// Executes typed `getBlocksWithLimit` with the runtime-supported inclusive upper bound.
|
|
pub async fn get_blocks_with_limit(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
start_slot: u64,
|
|
limit: u64,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
|
let validated = validate_blocks_context_commitment("getBlocksWithLimit", config);
|
|
if let std::result::Result::Err(error) = validated {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if limit > MAX_GET_BLOCKS_RANGE {
|
|
return invalid_blocks_limit("getBlocksWithLimit", "getBlocksWithLimit limit must not exceed 500000", limit);
|
|
}
|
|
let mut params = std::vec![serde_json::json!(start_slot), serde_json::json!(limit)];
|
|
push_blocks_explicit_context_config(&mut params, config);
|
|
return self.get_blocks_u64_list("getBlocksWithLimit", role, params).await;
|
|
}
|
|
|
|
/// Executes typed `getBlockProduction` with optional identity, range, and commitment filters.
|
|
pub async fn get_block_production(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
config: std::option::Option<&crate::SolanaBlockProductionConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaBlockProduction>> {
|
|
if let std::option::Option::Some(config) = config
|
|
&& let std::option::Option::Some(range) = config.range()
|
|
&& let std::option::Option::Some(last_slot) = range.last_slot()
|
|
&& last_slot < range.first_slot()
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "getBlockProduction lastSlot must not be less than firstSlot")
|
|
.with_context("rpc_method", "getBlockProduction")
|
|
.with_context("first_slot", range.first_slot().to_string())
|
|
.with_context("last_slot", last_slot.to_string()),
|
|
);
|
|
}
|
|
let mut params = std::vec::Vec::new();
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push(config.to_json_value());
|
|
}
|
|
let value = self.execute_blocks_rpc("getBlockProduction", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return decode_block_production_response("getBlockProduction", value);
|
|
}
|
|
|
|
/// Executes typed `getRecentPerformanceSamples`, preserving runtime order and older sample shapes.
|
|
pub async fn get_recent_performance_samples(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
limit: std::option::Option<u64>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPerformanceSample>> {
|
|
if let std::option::Option::Some(limit) = limit
|
|
&& limit > MAX_GET_RECENT_PERFORMANCE_SAMPLES
|
|
{
|
|
return invalid_blocks_limit("getRecentPerformanceSamples", "getRecentPerformanceSamples limit must not exceed 720", limit);
|
|
}
|
|
let mut params = std::vec::Vec::new();
|
|
if let std::option::Option::Some(limit) = limit {
|
|
params.push(serde_json::json!(limit));
|
|
}
|
|
let value = self.execute_blocks_rpc("getRecentPerformanceSamples", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return decode_performance_samples("getRecentPerformanceSamples", value);
|
|
}
|
|
|
|
async fn get_blocks_u64_list(
|
|
&self,
|
|
method_name: &'static str,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
|
let value = self.execute_blocks_rpc(method_name, role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<std::vec::Vec<u64>>(method_name, value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
async fn get_blocks_u64(
|
|
&self,
|
|
method_name: &'static str,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<u64> {
|
|
let value = self.execute_blocks_rpc(method_name, role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<u64>(method_name, value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
async fn execute_blocks_rpc(
|
|
&self,
|
|
method_name: &'static str,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<serde_json::Value> {
|
|
let method = blocks_descriptor(method_name);
|
|
let method = match method {
|
|
std::result::Result::Ok(method) => method,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return self.execute_standard_rpc(role, method, params).await;
|
|
}
|
|
|
|
async fn execute_blocks_rpc_observed(
|
|
&self,
|
|
method_name: &'static str,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<crate::HttpObservedValue<serde_json::Value>> {
|
|
let method = blocks_descriptor(method_name);
|
|
let method = match method {
|
|
std::result::Result::Ok(method) => method,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return self.execute_standard_rpc_observed(role, method, params).await;
|
|
}
|
|
}
|
|
|
|
fn decode_get_block(value: serde_json::Value) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
|
if value.is_null() {
|
|
return std::result::Result::Ok(std::option::Option::None);
|
|
}
|
|
let block = crate::SolanaConfirmedBlock::decode_wire("getBlock", value);
|
|
return match block {
|
|
std::result::Result::Ok(block) => std::result::Result::Ok(std::option::Option::Some(block)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
fn get_block_params(slot: u64, config: std::option::Option<&crate::SolanaGetBlockConfig>) -> ksp_core_lib::Result<std::vec::Vec<serde_json::Value>> {
|
|
if let std::option::Option::Some(config) = config
|
|
&& config.commitment() == std::option::Option::Some(crate::SolanaCommitment::Processed)
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "getBlock commitment must be confirmed or finalized when explicitly provided")
|
|
.with_context("rpc_method", "getBlock")
|
|
.with_context("commitment", "processed"),
|
|
);
|
|
}
|
|
let mut params = std::vec![serde_json::json!(slot)];
|
|
if let std::option::Option::Some(config) = config {
|
|
params.push((*config).to_json_value());
|
|
}
|
|
return std::result::Result::Ok(params);
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireBlockCommitment {
|
|
commitment: std::option::Option<std::vec::Vec<u64>>,
|
|
total_stake: u64,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireBlockProductionRange {
|
|
first_slot: u64,
|
|
last_slot: u64,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireBlockProduction {
|
|
by_identity: std::collections::BTreeMap<std::string::String, (usize, usize)>,
|
|
range: WireBlockProductionRange,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct WireBlockProductionRpcResponse {
|
|
context: serde_json::Value,
|
|
value: serde_json::Value,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireBlockReward {
|
|
pubkey: std::string::String,
|
|
lamports: i64,
|
|
post_balance: u64,
|
|
#[serde(default)]
|
|
reward_type: std::option::Option<std::string::String>,
|
|
#[serde(default)]
|
|
commission: std::option::Option<u8>,
|
|
#[serde(default)]
|
|
commission_bps: crate::SolanaWireField<u16>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireBlockTransaction {
|
|
transaction: serde_json::Value,
|
|
#[serde(default)]
|
|
meta: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
version: crate::SolanaWireField<serde_json::Value>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireConfirmedBlock {
|
|
previous_blockhash: std::string::String,
|
|
blockhash: std::string::String,
|
|
parent_slot: u64,
|
|
#[serde(default)]
|
|
transactions: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
|
#[serde(default)]
|
|
signatures: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
|
#[serde(default)]
|
|
rewards: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
|
#[serde(default)]
|
|
num_reward_partitions: crate::SolanaWireField<u64>,
|
|
block_time: std::option::Option<i64>,
|
|
block_height: std::option::Option<u64>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WirePerformanceSample {
|
|
slot: u64,
|
|
num_transactions: u64,
|
|
#[serde(default)]
|
|
num_non_vote_transactions: std::option::Option<u64>,
|
|
num_slots: u64,
|
|
sample_period_secs: u16,
|
|
}
|
|
|
|
fn validate_blocks_context_commitment(method: &'static str, config: std::option::Option<&crate::SolanaContextConfig>) -> ksp_core_lib::Result<()> {
|
|
if let std::option::Option::Some(config) = config
|
|
&& config.commitment() == std::option::Option::Some(crate::SolanaCommitment::Processed)
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(
|
|
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
|
|
"block-range commitment must be confirmed or finalized when explicitly provided",
|
|
)
|
|
.with_context("rpc_method", method)
|
|
.with_context("commitment", "processed"),
|
|
);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn push_blocks_explicit_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
|
if let std::option::Option::Some(config) = config {
|
|
params.push((*config).to_json_value());
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn invalid_blocks_limit<T>(method: &'static str, message: &'static str, limit: u64) -> ksp_core_lib::Result<T> {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, message)
|
|
.with_context("rpc_method", method)
|
|
.with_context("limit", limit.to_string()),
|
|
);
|
|
}
|
|
|
|
fn decode_block_production_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaBlockProduction>> {
|
|
let decoded = crate::decode_wire_json::<WireBlockProductionRpcResponse>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
|
let context = match context {
|
|
std::result::Result::Ok(context) => context,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let value = crate::SolanaBlockProduction::decode_wire(method, wire.value);
|
|
return match value {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, value)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
fn decode_performance_samples(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPerformanceSample>> {
|
|
let values = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(method, value);
|
|
let values = match values {
|
|
std::result::Result::Ok(values) => values,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut samples = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let sample = crate::SolanaPerformanceSample::decode_wire(method, value);
|
|
match sample {
|
|
std::result::Result::Ok(sample) => samples.push(sample),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(samples);
|
|
}
|
|
|
|
fn push_blocks_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
|
if let std::option::Option::Some(config) = config
|
|
&& (config.commitment().is_some() || config.min_context_slot().is_some())
|
|
{
|
|
params.push((*config).to_json_value());
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn blocks_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpRpcMethodDescriptor> {
|
|
let descriptor = crate::find_http_rpc_method(method);
|
|
return match descriptor {
|
|
std::option::Option::Some(descriptor)
|
|
if descriptor.category() == crate::HttpRpcCategory::Blocks && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_4 =>
|
|
{
|
|
std::result::Result::Ok(descriptor)
|
|
},
|
|
_ => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Blocks descriptor is missing from the audited 0.2.4 registry")
|
|
.with_context("rpc_method", method),
|
|
),
|
|
};
|
|
}
|
|
|
|
fn decode_block_transaction_version(
|
|
method: &str,
|
|
field: crate::SolanaWireField<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<crate::SolanaTransactionVersion>> {
|
|
return match field {
|
|
crate::SolanaWireField::Omitted => std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(value) => {
|
|
let decoded = crate::SolanaTransactionVersion::decode_wire(method, value);
|
|
match decoded {
|
|
std::result::Result::Ok(version) => std::result::Result::Ok(crate::SolanaWireField::Value(version)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
fn decode_block_transactions(
|
|
method: &str,
|
|
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>>> {
|
|
let values = match field {
|
|
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(values) => values,
|
|
};
|
|
let mut transactions = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let decoded = crate::SolanaBlockTransaction::decode_wire(method, value);
|
|
match decoded {
|
|
std::result::Result::Ok(transaction) => transactions.push(transaction),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(crate::SolanaWireField::Value(transactions));
|
|
}
|
|
|
|
fn decode_block_rewards(
|
|
method: &str,
|
|
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>>> {
|
|
let values = match field {
|
|
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(values) => values,
|
|
};
|
|
let mut rewards = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let decoded = crate::SolanaBlockReward::decode_wire(method, value);
|
|
match decoded {
|
|
std::result::Result::Ok(reward) => rewards.push(reward),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(crate::SolanaWireField::Value(rewards));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/rpc_blocks.rs"]
|
|
mod tests;
|