Files
khadhroony-solana-project/crates/ksp-onchain-transport-lib/src/rpc_common.rs
2026-08-24 11:10:59 +02:00

194 lines
7.0 KiB
Rust

// file: crates/ksp-onchain-transport-lib/src/rpc_common.rs
// version: 7
/// Commitment level accepted by typed Solana HTTP, WebSocket and Yellowstone gRPC adapters.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SolanaCommitment {
/// Query the most recent processed bank.
Processed,
/// Query a bank confirmed by cluster vote.
Confirmed,
/// Query a finalized bank.
Finalized,
}
impl SolanaCommitment {
/// Returns the Solana JSON-RPC commitment string.
#[must_use]
pub const fn as_str(self) -> &'static str {
return match self {
Self::Processed => "processed",
Self::Confirmed => "confirmed",
Self::Finalized => "finalized",
};
}
}
/// Optional commitment-only configuration shared by typed Solana RPC methods.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SolanaCommitmentConfig {
commitment: std::option::Option<crate::SolanaCommitment>,
}
impl SolanaCommitmentConfig {
/// Creates an explicit commitment-only configuration.
#[must_use]
pub const fn new(commitment: std::option::Option<crate::SolanaCommitment>) -> Self {
return Self { commitment };
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// 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()));
}
return serde_json::Value::Object(object);
}
}
/// Optional commitment and minimum-context configuration shared by typed Solana HTTP RPC methods.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SolanaContextConfig {
commitment: std::option::Option<crate::SolanaCommitment>,
min_context_slot: std::option::Option<u64>,
}
impl SolanaContextConfig {
/// Creates an explicit context-aware RPC configuration.
#[must_use]
pub const fn new(commitment: std::option::Option<crate::SolanaCommitment>, min_context_slot: std::option::Option<u64>) -> Self {
return Self { commitment, min_context_slot };
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns the optional minimum context slot.
#[must_use]
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
return self.min_context_slot;
}
/// 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(min_context_slot) = self.min_context_slot {
object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into()));
}
return serde_json::Value::Object(object);
}
}
/// Typed Solana RPC context shared by contextual HTTP and WebSocket responses.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SolanaRpcContext {
slot: u64,
api_version: std::option::Option<std::string::String>,
}
impl SolanaRpcContext {
/// Returns the context slot reported by the RPC node.
#[must_use]
pub const fn slot(&self) -> u64 {
return self.slot;
}
/// Returns the optional RPC API version reported by the node.
#[must_use]
pub fn api_version(&self) -> std::option::Option<&str> {
return match self.api_version.as_ref() {
std::option::Option::Some(value) => std::option::Option::Some(value.as_str()),
std::option::Option::None => std::option::Option::None,
};
}
/// Decodes one RPC context from a parsed JSON value for typed RPC adapters.
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = decode_wire_json::<WireRpcContext>(method, value);
let context = match decoded {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(Self { slot: context.slot, api_version: context.api_version });
}
}
/// Generic contextual result returned by typed Solana HTTP and WebSocket adapters.
#[derive(Clone, Debug, PartialEq)]
pub struct SolanaRpcResponse<T> {
context: crate::SolanaRpcContext,
value: T,
}
impl<T> SolanaRpcResponse<T> {
/// Returns the Solana response context.
#[must_use]
pub const fn context(&self) -> &crate::SolanaRpcContext {
return &self.context;
}
/// Returns the typed response value.
#[must_use]
pub const fn value(&self) -> &T {
return &self.value;
}
/// Creates a contextual response after wire decoding and validation.
#[must_use]
pub(crate) const fn new(context: crate::SolanaRpcContext, value: T) -> Self {
return Self { context, value };
}
}
#[derive(serde::Deserialize)]
struct WireRpcContext {
slot: u64,
#[serde(rename = "apiVersion", default)]
api_version: std::option::Option<std::string::String>,
}
/// Decodes one private serde wire type and maps shape failures to the shared Transport error domain.
pub(crate) fn decode_wire_json<T: serde::de::DeserializeOwned>(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<T> {
let decoded = serde_json::from_value::<T>(value);
return match decoded {
std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded),
std::result::Result::Err(error) => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Solana RPC response has an invalid wire shape")
.with_context("rpc_method", method)
.with_source(error),
),
};
}
/// Parses a base58 public key from one wire field without echoing its value into diagnostics.
pub(crate) fn parse_wire_pubkey(method: &str, field: &str, value: &str) -> ksp_core_lib::Result<ksp_core_lib::Pubkey> {
let parsed = value.parse::<ksp_core_lib::Pubkey>();
return match parsed {
std::result::Result::Ok(pubkey) => std::result::Result::Ok(pubkey),
std::result::Result::Err(_) => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Solana RPC response contains an invalid public key")
.with_context("rpc_method", method)
.with_context("field", field),
),
};
}
#[cfg(test)]
#[path = "../unit_tests/rpc_common.rs"]
mod tests;