Files
khadhroony-solana-project/crates/ksp-onchain-transport-lib/src/rpc_cluster.rs

992 lines
38 KiB
Rust

// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
// version: 7
const MAX_GET_SLOT_LEADERS: u64 = 5_000;
/// Contact information returned for one cluster node.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaClusterNode {
pubkey: ksp_core_lib::Pubkey,
feature_set: std::option::Option<u32>,
gossip: std::option::Option<std::string::String>,
pubsub: std::option::Option<std::string::String>,
rpc: std::option::Option<std::string::String>,
serve_repair: std::option::Option<std::string::String>,
shred_version: std::option::Option<u16>,
tpu: std::option::Option<std::string::String>,
tpu_forwards: std::option::Option<std::string::String>,
tpu_forwards_quic: std::option::Option<std::string::String>,
tpu_quic: std::option::Option<std::string::String>,
tpu_vote: std::option::Option<std::string::String>,
tvu: std::option::Option<std::string::String>,
version: std::option::Option<std::string::String>,
client_id: std::option::Option<std::string::String>,
}
impl SolanaClusterNode {
/// Returns the node identity public key.
#[must_use]
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;
}
/// Returns the optional gossip endpoint.
#[must_use]
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();
}
/// Returns the optional JSON-RPC endpoint.
#[must_use]
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();
}
/// Returns the optional shred version.
#[must_use]
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();
}
/// Returns the optional TPU forwards endpoint.
#[must_use]
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();
}
/// Returns the optional TPU QUIC endpoint.
#[must_use]
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();
}
/// Returns the optional TVU endpoint.
#[must_use]
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();
}
/// Returns the optional Agave client identifier extension.
#[must_use]
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.
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 {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pubkey = crate::parse_wire_pubkey(method, "pubkey", wire.pubkey.as_str());
let pubkey = match pubkey {
std::result::Result::Ok(pubkey) => pubkey,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(Self {
pubkey,
feature_set: wire.feature_set,
gossip: wire.gossip,
pubsub: wire.pubsub,
rpc: wire.rpc,
serve_repair: wire.serve_repair,
shred_version: wire.shred_version,
tpu: wire.tpu,
tpu_forwards: wire.tpu_forwards,
tpu_forwards_quic: wire.tpu_forwards_quic,
tpu_quic: wire.tpu_quic,
tpu_vote: wire.tpu_vote,
tvu: wire.tvu,
version: wire.version,
client_id: wire.client_id,
});
}
}
/// Epoch information returned by `getEpochInfo`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaEpochInfo {
absolute_slot: u64,
block_height: u64,
epoch: u64,
slot_index: u64,
slots_in_epoch: u64,
transaction_count: std::option::Option<u64>,
}
impl SolanaEpochInfo {
/// Returns the absolute slot.
#[must_use]
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;
}
/// Returns the epoch number.
#[must_use]
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;
}
/// Returns the number of slots in the epoch.
#[must_use]
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;
}
/// Decodes epoch information from the 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::<WireEpochInfo>(method, value);
return match decoded {
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
absolute_slot: wire.absolute_slot,
block_height: wire.block_height,
epoch: wire.epoch,
slot_index: wire.slot_index,
slots_in_epoch: wire.slots_in_epoch,
transaction_count: wire.transaction_count,
}),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Epoch schedule returned by `getEpochSchedule`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaEpochSchedule {
first_normal_epoch: u64,
first_normal_slot: u64,
leader_schedule_slot_offset: u64,
slots_per_epoch: u64,
warmup: bool,
}
impl SolanaEpochSchedule {
/// Returns the first normal epoch.
#[must_use]
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;
}
/// Returns the leader-schedule slot offset.
#[must_use]
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;
}
/// Returns whether epoch warmup is enabled.
#[must_use]
pub const fn warmup(&self) -> bool {
return self.warmup;
}
/// Decodes an epoch schedule from the 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::<WireEpochSchedule>(method, value);
return match decoded {
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
first_normal_epoch: wire.first_normal_epoch,
first_normal_slot: wire.first_normal_slot,
leader_schedule_slot_offset: wire.leader_schedule_slot_offset,
slots_per_epoch: wire.slots_per_epoch,
warmup: wire.warmup,
}),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Highest full and optional incremental snapshot slots returned by `getHighestSnapshotSlot`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaSnapshotSlotInfo {
full: u64,
incremental: std::option::Option<u64>,
}
impl SolanaSnapshotSlotInfo {
/// Returns the highest full snapshot slot.
#[must_use]
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;
}
/// Decodes snapshot-slot information from the 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::<WireSnapshotSlotInfo>(method, value);
return match decoded {
std::result::Result::Ok(wire) => std::result::Result::Ok(Self { full: wire.full, incremental: wire.incremental }),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
}
/// Optional configuration accepted by `getLeaderSchedule`.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SolanaLeaderScheduleConfig {
identity: std::option::Option<ksp_core_lib::Pubkey>,
commitment: std::option::Option<crate::SolanaCommitment>,
}
impl SolanaLeaderScheduleConfig {
/// Creates a leader-schedule configuration.
#[must_use]
pub const fn new(identity: std::option::Option<ksp_core_lib::Pubkey>, commitment: std::option::Option<crate::SolanaCommitment>) -> Self {
return Self { identity, 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 commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns whether empty.
pub(crate) const fn is_empty(&self) -> bool {
return self.identity.is_none() && self.commitment.is_none();
}
/// Executes the crate-internal to json value operation for `SolanaLeaderScheduleConfig`.
pub(crate) 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() {
object.insert("identity".to_owned(), serde_json::Value::String(identity.to_string()));
}
if let std::option::Option::Some(commitment) = self.commitment {
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
}
return serde_json::Value::Object(object);
}
}
/// Typed parameter overload for `getLeaderSchedule`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SolanaLeaderScheduleRequest {
/// Query the current epoch, optionally with a config object.
CurrentEpoch(std::option::Option<crate::SolanaLeaderScheduleConfig>),
/// Query the epoch containing one slot, optionally with a config object.
Slot {
/// Slot whose epoch should be queried.
slot: u64,
/// Optional leader-schedule config sent as the second positional parameter.
config: std::option::Option<crate::SolanaLeaderScheduleConfig>,
},
}
impl Default for SolanaLeaderScheduleRequest {
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]
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(),
Self::CurrentEpoch(std::option::Option::Some(config)) if config.is_empty() => std::vec::Vec::new(),
Self::CurrentEpoch(std::option::Option::Some(config)) => std::vec![config.to_json_value()],
Self::Slot { slot, config: std::option::Option::None } => std::vec![serde_json::json!(slot)],
Self::Slot { slot, config: std::option::Option::Some(config) } if config.is_empty() => std::vec![serde_json::json!(slot)],
Self::Slot { slot, config: std::option::Option::Some(config) } => std::vec![serde_json::json!(slot), config.to_json_value()],
};
}
}
/// Leader schedule mapping validator identities to relative epoch slot indices.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaLeaderSchedule {
entries: std::collections::BTreeMap<ksp_core_lib::Pubkey, std::vec::Vec<usize>>,
}
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;
}
/// Decodes a leader schedule map from the 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::<std::collections::BTreeMap<std::string::String, std::vec::Vec<usize>>>(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 entries = std::collections::BTreeMap::new();
for (identity, slots) in wire {
let pubkey = crate::parse_wire_pubkey(method, "leader_identity", identity.as_str());
let pubkey = match pubkey {
std::result::Result::Ok(pubkey) => pubkey,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
entries.insert(pubkey, slots);
}
return std::result::Result::Ok(Self { entries });
}
}
/// Configuration accepted by `getVoteAccounts`.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SolanaVoteAccountsConfig {
commitment: std::option::Option<crate::SolanaCommitment>,
vote_pubkey: std::option::Option<ksp_core_lib::Pubkey>,
keep_unstaked_delinquents: std::option::Option<bool>,
delinquent_slot_distance: std::option::Option<u64>,
}
impl SolanaVoteAccountsConfig {
/// Creates a vote-accounts configuration.
#[must_use]
pub const fn new(
commitment: std::option::Option<crate::SolanaCommitment>,
vote_pubkey: std::option::Option<ksp_core_lib::Pubkey>,
keep_unstaked_delinquents: std::option::Option<bool>,
delinquent_slot_distance: std::option::Option<u64>,
) -> Self {
return Self { commitment, vote_pubkey, keep_unstaked_delinquents, delinquent_slot_distance };
}
/// Returns the optional commitment.
#[must_use]
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();
}
/// 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;
}
/// Returns the optional delinquent slot distance.
#[must_use]
pub const fn delinquent_slot_distance(&self) -> std::option::Option<u64> {
return self.delinquent_slot_distance;
}
/// Returns whether empty.
pub(crate) const fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.vote_pubkey.is_none() && self.keep_unstaked_delinquents.is_none() && self.delinquent_slot_distance.is_none();
}
/// Serializes this config to the Solana JSON-RPC wire 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(vote_pubkey) = self.vote_pubkey.as_ref() {
object.insert("votePubkey".to_owned(), serde_json::Value::String(vote_pubkey.to_string()));
}
if let std::option::Option::Some(value) = self.keep_unstaked_delinquents {
object.insert("keepUnstakedDelinquents".to_owned(), serde_json::Value::Bool(value));
}
if let std::option::Option::Some(value) = self.delinquent_slot_distance {
object.insert("delinquentSlotDistance".to_owned(), serde_json::Value::Number(value.into()));
}
return serde_json::Value::Object(object);
}
}
/// One epoch-credit history entry returned by `getVoteAccounts`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SolanaEpochCredits {
epoch: u64,
credits: u64,
previous_credits: u64,
}
impl SolanaEpochCredits {
/// Returns the epoch number.
#[must_use]
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;
}
/// Returns cumulative credits before the epoch.
#[must_use]
pub const fn previous_credits(&self) -> u64 {
return self.previous_credits;
}
}
/// One validator vote-account record returned by `getVoteAccounts`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaVoteAccountInfo {
vote_pubkey: ksp_core_lib::Pubkey,
node_pubkey: ksp_core_lib::Pubkey,
activated_stake: u64,
commission: u8,
inflation_rewards_commission_bps: std::option::Option<u16>,
epoch_vote_account: bool,
epoch_credits: std::vec::Vec<crate::SolanaEpochCredits>,
last_vote: u64,
root_slot: u64,
}
impl SolanaVoteAccountInfo {
/// Returns the vote account public key.
#[must_use]
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;
}
/// Returns the activated stake in lamports.
#[must_use]
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;
}
/// 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;
}
/// 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;
}
/// Returns the bounded RPC epoch-credit history.
#[must_use]
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;
}
/// Returns the current root slot or zero when no root exists.
#[must_use]
pub const fn root_slot(&self) -> u64 {
return self.root_slot;
}
/// Decodes one vote-account record from the 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::<WireVoteAccountInfo>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let vote_pubkey = crate::parse_wire_pubkey(method, "votePubkey", wire.vote_pubkey.as_str());
let vote_pubkey = match vote_pubkey {
std::result::Result::Ok(pubkey) => pubkey,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let node_pubkey = crate::parse_wire_pubkey(method, "nodePubkey", wire.node_pubkey.as_str());
let node_pubkey = match node_pubkey {
std::result::Result::Ok(pubkey) => pubkey,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut epoch_credits = std::vec::Vec::with_capacity(wire.epoch_credits.len());
for entry in wire.epoch_credits {
epoch_credits.push(crate::SolanaEpochCredits { epoch: entry[0], credits: entry[1], previous_credits: entry[2] });
}
return std::result::Result::Ok(Self {
vote_pubkey,
node_pubkey,
activated_stake: wire.activated_stake,
commission: wire.commission,
inflation_rewards_commission_bps: wire.inflation_rewards_commission_bps,
epoch_vote_account: wire.epoch_vote_account,
epoch_credits,
last_vote: wire.last_vote,
root_slot: wire.root_slot,
});
}
}
/// Current and delinquent validator vote-account groups returned by `getVoteAccounts`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaVoteAccountStatus {
current: std::vec::Vec<crate::SolanaVoteAccountInfo>,
delinquent: std::vec::Vec<crate::SolanaVoteAccountInfo>,
}
impl SolanaVoteAccountStatus {
/// Returns current vote accounts.
#[must_use]
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();
}
/// Decodes the complete vote-account status response from the 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::<WireVoteAccountStatus>(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 current = std::vec::Vec::with_capacity(wire.current.len());
for value in wire.current {
let decoded = crate::SolanaVoteAccountInfo::decode_wire(method, value);
match decoded {
std::result::Result::Ok(info) => current.push(info),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
let mut delinquent = std::vec::Vec::with_capacity(wire.delinquent.len());
for value in wire.delinquent {
let decoded = crate::SolanaVoteAccountInfo::decode_wire(method, value);
match decoded {
std::result::Result::Ok(info) => delinquent.push(info),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(Self { current, delinquent });
}
}
#[derive(serde::Deserialize)]
struct WireIdentity {
identity: std::string::String,
}
impl crate::HttpTransportPool {
/// Executes typed `getClusterNodes` through the common KSP HTTP transport path.
pub async fn get_cluster_nodes(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaClusterNode>> {
let value = self.execute_cluster_rpc("getClusterNodes", role, std::vec::Vec::new()).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decoded = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>("getClusterNodes", value);
let values = match decoded {
std::result::Result::Ok(values) => values,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut nodes = std::vec::Vec::with_capacity(values.len());
for value in values {
let node = crate::SolanaClusterNode::decode_wire("getClusterNodes", value);
match node {
std::result::Result::Ok(node) => nodes.push(node),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(nodes);
}
/// Executes typed `getEpochInfo` through the common KSP HTTP transport path.
pub async fn get_epoch_info(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaContextConfig>,
) -> ksp_core_lib::Result<crate::SolanaEpochInfo> {
let mut params = std::vec::Vec::new();
push_context_config(&mut params, config);
let value = self.execute_cluster_rpc("getEpochInfo", role, params).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaEpochInfo::decode_wire("getEpochInfo", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getEpochSchedule` through the common KSP HTTP transport path.
pub async fn get_epoch_schedule(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<crate::SolanaEpochSchedule> {
let value = self.execute_cluster_rpc("getEpochSchedule", role, std::vec::Vec::new()).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaEpochSchedule::decode_wire("getEpochSchedule", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getHighestSnapshotSlot` through the common KSP HTTP transport path.
pub async fn get_highest_snapshot_slot(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<crate::SolanaSnapshotSlotInfo> {
let value = self.execute_cluster_rpc("getHighestSnapshotSlot", role, std::vec::Vec::new()).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaSnapshotSlotInfo::decode_wire("getHighestSnapshotSlot", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getIdentity` through the common KSP HTTP transport path.
pub async fn get_identity(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
let value = self.execute_cluster_rpc("getIdentity", role, std::vec::Vec::new()).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decoded = crate::decode_wire_json::<WireIdentity>("getIdentity", value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::parse_wire_pubkey("getIdentity", "identity", wire.identity.as_str());
}
/// Executes typed `getMaxRetransmitSlot` through the common KSP HTTP transport path.
pub async fn get_max_retransmit_slot(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<u64> {
return self.get_cluster_simple_slot("getMaxRetransmitSlot", role).await;
}
/// Executes typed `getMaxShredInsertSlot` through the common KSP HTTP transport path.
pub async fn get_max_shred_insert_slot(&self, role: &crate::HttpRoleName) -> ksp_core_lib::Result<u64> {
return self.get_cluster_simple_slot("getMaxShredInsertSlot", role).await;
}
/// Executes typed `getLeaderSchedule` through the common KSP HTTP transport path.
pub async fn get_leader_schedule(
&self,
role: &crate::HttpRoleName,
request: &crate::SolanaLeaderScheduleRequest,
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaLeaderSchedule>> {
let value = self.execute_cluster_rpc("getLeaderSchedule", role, request.to_json_params()).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if value.is_null() {
return std::result::Result::Ok(std::option::Option::None);
}
let schedule = crate::SolanaLeaderSchedule::decode_wire("getLeaderSchedule", value);
return match schedule {
std::result::Result::Ok(schedule) => std::result::Result::Ok(std::option::Option::Some(schedule)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getSlot` through the common KSP HTTP transport path.
pub async fn get_slot(&self, role: &crate::HttpRoleName, config: std::option::Option<&crate::SolanaContextConfig>) -> ksp_core_lib::Result<u64> {
let mut params = std::vec::Vec::new();
push_context_config(&mut params, config);
let value = self.execute_cluster_rpc("getSlot", role, params).await;
return match value {
std::result::Result::Ok(value) => crate::decode_wire_json::<u64>("getSlot", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
/// Executes typed `getSlotLeader` through the common KSP HTTP transport path.
pub async fn get_slot_leader(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaContextConfig>,
) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
let mut params = std::vec::Vec::new();
push_context_config(&mut params, config);
let value = self.execute_cluster_rpc("getSlotLeader", role, params).await;
let value = match value {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decoded = crate::decode_wire_json::<std::string::String>("getSlotLeader", value);
let leader = match decoded {
std::result::Result::Ok(leader) => leader,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::parse_wire_pubkey("getSlotLeader", "leader", leader.as_str());
}
/// Executes typed `getSlotLeaders` through the common KSP HTTP transport path.
pub async fn get_slot_leaders(&self, role: &crate::HttpRoleName, start_slot: u64, limit: u64) -> ksp_core_lib::Result<std::vec::Vec<ksp_core_lib::Pubkey>> {
if limit == 0 || limit > MAX_GET_SLOT_LEADERS {
return invalid_cluster_parameters("getSlotLeaders", "getSlotLeaders limit must be between 1 and 5000", "limit", limit);
}
let params = std::vec![serde_json::json!(start_slot), serde_json::json!(limit)];
let value = self.execute_cluster_rpc("getSlotLeaders", 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_pubkey_list("getSlotLeaders", "leader", value);
}
/// Executes typed `getVoteAccounts` through the common KSP HTTP transport path.
pub async fn get_vote_accounts(
&self,
role: &crate::HttpRoleName,
config: std::option::Option<&crate::SolanaVoteAccountsConfig>,
) -> ksp_core_lib::Result<crate::SolanaVoteAccountStatus> {
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_cluster_rpc("getVoteAccounts", role, params).await;
return match value {
std::result::Result::Ok(value) => crate::SolanaVoteAccountStatus::decode_wire("getVoteAccounts", value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
async fn get_cluster_simple_slot(&self, method_name: &'static str, role: &crate::HttpRoleName) -> ksp_core_lib::Result<u64> {
let value = self.execute_cluster_rpc(method_name, role, std::vec::Vec::new()).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_cluster_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 = cluster_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;
}
}
#[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>,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireEpochInfo {
absolute_slot: u64,
block_height: u64,
epoch: u64,
slot_index: u64,
slots_in_epoch: u64,
transaction_count: std::option::Option<u64>,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireEpochSchedule {
first_normal_epoch: u64,
first_normal_slot: u64,
leader_schedule_slot_offset: u64,
slots_per_epoch: u64,
warmup: bool,
}
#[derive(serde::Deserialize)]
struct WireSnapshotSlotInfo {
full: u64,
incremental: std::option::Option<u64>,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireVoteAccountInfo {
vote_pubkey: std::string::String,
node_pubkey: std::string::String,
activated_stake: u64,
commission: u8,
#[serde(default)]
inflation_rewards_commission_bps: std::option::Option<u16>,
epoch_vote_account: bool,
epoch_credits: std::vec::Vec<[u64; 3]>,
last_vote: u64,
root_slot: u64,
}
#[derive(serde::Deserialize)]
struct WireVoteAccountStatus {
current: std::vec::Vec<serde_json::Value>,
delinquent: std::vec::Vec<serde_json::Value>,
}
fn push_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 decode_pubkey_list(method: &str, field: &'static str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<ksp_core_lib::Pubkey>> {
let decoded = crate::decode_wire_json::<std::vec::Vec<std::string::String>>(method, value);
let values = match decoded {
std::result::Result::Ok(values) => values,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut pubkeys = std::vec::Vec::with_capacity(values.len());
for value in values {
let pubkey = crate::parse_wire_pubkey(method, field, value.as_str());
match pubkey {
std::result::Result::Ok(pubkey) => pubkeys.push(pubkey),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(pubkeys);
}
fn invalid_cluster_parameters<T>(method: &str, message: &str, field: &'static str, value: 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(field, value.to_string()),
);
}
fn cluster_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::Cluster && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_2 =>
{
std::result::Result::Ok(descriptor)
},
_ => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Cluster descriptor is missing from the audited 0.2.2 registry")
.with_context("rpc_method", method),
),
};
}
#[cfg(test)]
#[path = "../unit_tests/rpc_cluster.rs"]
mod tests;