v0.2.2-pre.006
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -9,8 +9,8 @@
|
||||
//! This crate owns runtime HTTP transport settings, Solana HTTP JSON-RPC envelopes and the audited standard method registry. It deliberately remains
|
||||
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
|
||||
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
|
||||
//! are available. The four typed Solana HTTP foundation canaries plus the `0.2.2` Accounts and Tokens wrappers execute real JSON-RPC requests through
|
||||
//! the shared transport path while the remaining audited methods stay staged by subsequent `0.2.x` prereleases.
|
||||
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
|
||||
//! requests through the shared transport path while the `0.2.3` and `0.2.4` audited families remain staged.
|
||||
|
||||
mod client;
|
||||
mod constants;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
const MAX_GET_SLOT_LEADERS: u64 = 5_000;
|
||||
|
||||
/// Contact information returned for one cluster node.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -295,12 +297,10 @@ impl SolanaLeaderScheduleConfig {
|
||||
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||
return self.commitment;
|
||||
}
|
||||
#[cfg(test)]
|
||||
fn is_empty(&self) -> bool {
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.identity.is_none() && self.commitment.is_none();
|
||||
}
|
||||
#[cfg(test)]
|
||||
fn to_json_value(&self) -> serde_json::Value {
|
||||
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
if let std::option::Option::Some(identity) = self.identity.as_ref() {
|
||||
object.insert("identity".to_owned(), serde_json::Value::String(identity.to_string()));
|
||||
@@ -335,7 +335,6 @@ impl Default for SolanaLeaderScheduleRequest {
|
||||
impl SolanaLeaderScheduleRequest {
|
||||
/// Serializes the typed overload to the exact positional JSON-RPC params.
|
||||
#[must_use]
|
||||
#[cfg(test)]
|
||||
pub(crate) fn to_json_params(&self) -> std::vec::Vec<serde_json::Value> {
|
||||
return match self {
|
||||
Self::CurrentEpoch(std::option::Option::None) => std::vec::Vec::new(),
|
||||
@@ -362,7 +361,6 @@ impl SolanaLeaderSchedule {
|
||||
}
|
||||
|
||||
/// Decodes a leader schedule map from the Solana JSON wire shape.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<std::collections::BTreeMap<std::string::String, std::vec::Vec<usize>>>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -422,9 +420,11 @@ impl SolanaVoteAccountsConfig {
|
||||
pub const fn delinquent_slot_distance(&self) -> std::option::Option<u64> {
|
||||
return self.delinquent_slot_distance;
|
||||
}
|
||||
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]
|
||||
#[cfg(test)]
|
||||
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
if let std::option::Option::Some(commitment) = self.commitment {
|
||||
@@ -531,7 +531,6 @@ impl SolanaVoteAccountInfo {
|
||||
}
|
||||
|
||||
/// Decodes one vote-account record from the Solana JSON wire shape.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireVoteAccountInfo>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -586,7 +585,6 @@ impl SolanaVoteAccountStatus {
|
||||
}
|
||||
|
||||
/// Decodes the complete vote-account status response from the Solana JSON wire shape.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireVoteAccountStatus>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -621,7 +619,7 @@ struct WireIdentity {
|
||||
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_simple_rpc("getClusterNodes", role, std::vec::Vec::new()).await;
|
||||
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),
|
||||
@@ -649,12 +647,8 @@ impl crate::HttpTransportPool {
|
||||
config: std::option::Option<&crate::SolanaContextConfig>,
|
||||
) -> ksp_core_lib::Result<crate::SolanaEpochInfo> {
|
||||
let mut params = std::vec::Vec::new();
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& (config.commitment().is_some() || config.min_context_slot().is_some())
|
||||
{
|
||||
params.push(config.to_json_value());
|
||||
}
|
||||
let value = self.execute_cluster_simple_rpc("getEpochInfo", role, params).await;
|
||||
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),
|
||||
@@ -663,7 +657,7 @@ impl crate::HttpTransportPool {
|
||||
|
||||
/// 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_simple_rpc("getEpochSchedule", role, std::vec::Vec::new()).await;
|
||||
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),
|
||||
@@ -672,7 +666,7 @@ impl crate::HttpTransportPool {
|
||||
|
||||
/// 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_simple_rpc("getHighestSnapshotSlot", role, std::vec::Vec::new()).await;
|
||||
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),
|
||||
@@ -681,7 +675,7 @@ impl crate::HttpTransportPool {
|
||||
|
||||
/// 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_simple_rpc("getIdentity", role, std::vec::Vec::new()).await;
|
||||
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),
|
||||
@@ -704,15 +698,101 @@ impl crate::HttpTransportPool {
|
||||
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_simple_rpc(method_name, role, std::vec::Vec::new()).await;
|
||||
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_simple_rpc(
|
||||
async fn execute_cluster_rpc(
|
||||
&self,
|
||||
method_name: &'static str,
|
||||
role: &crate::HttpRoleName,
|
||||
@@ -727,6 +807,40 @@ impl crate::HttpTransportPool {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -803,7 +917,6 @@ struct WireSnapshotSlotInfo {
|
||||
incremental: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireVoteAccountInfo {
|
||||
@@ -819,7 +932,6 @@ struct WireVoteAccountInfo {
|
||||
root_slot: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
struct WireVoteAccountStatus {
|
||||
current: std::vec::Vec<serde_json::Value>,
|
||||
|
||||
Reference in New Issue
Block a user