v0.2.4-pre.003

This commit is contained in:
2026-08-18 20:51:52 +02:00
parent 033800f897
commit e500024c3a
13 changed files with 679 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 17
// version: 18
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
@@ -12,8 +12,8 @@
//! 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. `0.2.3` exposes its shared Transaction wire/config primitives and all eleven Transaction wrappers through
//! `pre.007`: eight reads, two write submissions with centralized no-resend protection, and retry-safe `simulateTransaction`, including complete
//! modern/legacy `getTransaction` coverage. `0.2.4-pre.002` adds the shared Blocks/Economics wire, config and result primitives without yet advancing
//! any `V0_2_4` method to a typed wrapper.
//! modern/legacy `getTransaction` coverage. `0.2.4-pre.002` adds the shared Blocks/Economics wire, config and result primitives. `0.2.4-pre.003`
//! activates the first five typed Blocks reads: `getBlockCommitment`, `getBlockHeight`, `getBlockTime`, `getFirstAvailableBlock` and `minimumLedgerSlot`.
mod client;
mod constants;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
// version: 2
// version: 3
/// Transaction detail level accepted by modern `getBlock` requests.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
@@ -236,7 +236,6 @@ impl SolanaBlockCommitment {
}
/// Decodes a block-commitment result from its 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::<WireBlockCommitment>(method, value);
return match decoded {
@@ -587,6 +586,94 @@ impl SolanaPerformanceSample {
}
}
impl crate::HttpTransportPool {
/// 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;
}
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;
}
}
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),
),
};
}
#[cfg(test)]
fn decode_block_transaction_version(
method: &str,
@@ -647,7 +734,6 @@ fn decode_block_rewards(
return std::result::Result::Ok(crate::SolanaWireField::Value(rewards));
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireBlockCommitment {