// 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, gossip: std::option::Option, pubsub: std::option::Option, rpc: std::option::Option, serve_repair: std::option::Option, shred_version: std::option::Option, tpu: std::option::Option, tpu_forwards: std::option::Option, tpu_forwards_quic: std::option::Option, tpu_quic: std::option::Option, tpu_vote: std::option::Option, tvu: std::option::Option, version: std::option::Option, client_id: std::option::Option, } 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 { 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 { 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 { let decoded = crate::decode_wire_json::(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, } 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 { 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 { let decoded = crate::decode_wire_json::(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 { let decoded = crate::decode_wire_json::(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, } 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 { 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 { let decoded = crate::decode_wire_json::(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, commitment: std::option::Option, } impl SolanaLeaderScheduleConfig { /// Creates a leader-schedule configuration. #[must_use] pub const fn new(identity: std::option::Option, commitment: std::option::Option) -> 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 { 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), /// 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, }, } 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 { 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>, } impl SolanaLeaderSchedule { /// Returns the complete leader schedule map. #[must_use] pub const fn entries(&self) -> &std::collections::BTreeMap> { 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 { let decoded = crate::decode_wire_json::>>(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, vote_pubkey: std::option::Option, keep_unstaked_delinquents: std::option::Option, delinquent_slot_distance: std::option::Option, } impl SolanaVoteAccountsConfig { /// Creates a vote-accounts configuration. #[must_use] pub const fn new( commitment: std::option::Option, vote_pubkey: std::option::Option, keep_unstaked_delinquents: std::option::Option, delinquent_slot_distance: std::option::Option, ) -> 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 { 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 { return self.keep_unstaked_delinquents; } /// Returns the optional delinquent slot distance. #[must_use] pub const fn delinquent_slot_distance(&self) -> std::option::Option { 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, epoch_vote_account: bool, epoch_credits: std::vec::Vec, 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 { 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 { let decoded = crate::decode_wire_json::(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, delinquent: std::vec::Vec, } 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 { let decoded = crate::decode_wire_json::(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> { 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::>("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 { 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 { 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 { 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 { 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::("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 { 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 { 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> { 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 { 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::("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 { 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::("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> { 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 { 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 { 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::(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, ) -> ksp_core_lib::Result { 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, #[serde(default)] gossip: std::option::Option, #[serde(default)] pubsub: std::option::Option, #[serde(default)] rpc: std::option::Option, #[serde(default)] serve_repair: std::option::Option, #[serde(default)] shred_version: std::option::Option, #[serde(default)] tpu: std::option::Option, #[serde(default)] tpu_forwards: std::option::Option, #[serde(default)] tpu_forwards_quic: std::option::Option, #[serde(default)] tpu_quic: std::option::Option, #[serde(default)] tpu_vote: std::option::Option, #[serde(default)] tvu: std::option::Option, #[serde(default)] version: std::option::Option, #[serde(default)] client_id: std::option::Option, } #[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, } #[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, } #[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, 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, delinquent: std::vec::Vec, } fn push_context_config(params: &mut std::vec::Vec, 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> { let decoded = crate::decode_wire_json::>(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(method: &str, message: &str, field: &'static str, value: u64) -> ksp_core_lib::Result { 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;