v0.2.5-pre.010.fix.001

This commit is contained in:
2026-08-20 11:17:12 +02:00
parent 5bf9651038
commit c0b131bf6f
97 changed files with 2277 additions and 655 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/client.rs
// version: 6
// version: 7
/// Passive runtime availability reported for one logical HTTP endpoint or role.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -177,8 +177,9 @@ impl HttpEndpointClient {
return Self::new_with_notify(settings, std::sync::Arc::new(tokio::sync::Notify::new()));
}
/// Creates a new with notify value for `HttpEndpointClient`.
pub(crate) fn new_with_notify(settings: crate::HttpEndpointSettings, notify: std::sync::Arc<tokio::sync::Notify>) -> ksp_core_lib::Result<Self> {
let validation = crate::settings::validate_endpoint_settings(&settings);
let validation = crate::validate_endpoint_settings(&settings);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -239,6 +240,7 @@ impl HttpEndpointClient {
return self.inner.settings.request_timeout();
}
/// Executes the crate-internal post json rpc operation for `HttpEndpointClient`.
pub(crate) async fn post_json_rpc(&self, payload: &str, timeout: std::time::Duration) -> ksp_core_lib::Result<HttpEndpointHttpResponse> {
if timeout.is_zero() {
return std::result::Result::Err(
@@ -301,6 +303,7 @@ impl HttpEndpointClient {
};
}
/// Executes the crate-internal matching role operation for `HttpEndpointClient`.
pub(crate) fn matching_role<'a>(
&'a self,
role: &crate::HttpRoleName,
@@ -322,6 +325,7 @@ impl HttpEndpointClient {
return std::option::Option::None;
}
/// Executes the crate-internal matching role runtime operation for `HttpEndpointClient`.
pub(crate) fn matching_role_runtime(
&self,
role: &crate::HttpRoleName,
@@ -352,6 +356,7 @@ impl HttpEndpointClient {
return std::option::Option::None;
}
/// Returns the current availability.
pub(crate) fn availability(&self) -> crate::HttpEndpointAvailability {
if !self.enabled() {
return crate::HttpEndpointAvailability::Disabled;
@@ -393,6 +398,7 @@ impl std::fmt::Debug for HttpEndpointClient {
}
}
/// Crate-internal `HttpEndpointHttpResponse` state shared across the owning crate.
pub(crate) struct HttpEndpointHttpResponse {
status: u16,
retry_after: std::option::Option<std::time::Duration>,
@@ -400,14 +406,17 @@ pub(crate) struct HttpEndpointHttpResponse {
}
impl HttpEndpointHttpResponse {
/// Returns the current status.
pub(crate) const fn status(&self) -> u16 {
return self.status;
}
/// Returns the current retry after.
pub(crate) const fn retry_after(&self) -> std::option::Option<std::time::Duration> {
return self.retry_after;
}
/// Returns the current body.
pub(crate) fn body(&self) -> &[u8] {
return self.body.as_slice();
}

View File

@@ -1,12 +1,12 @@
// file: crates/ksp-onchain-transport-lib/src/executor.rs
// version: 2
// version: 3
const HTTP_REQUEST_TIMEOUT: u16 = 408;
const HTTP_TOO_MANY_REQUESTS: u16 = 429;
const HTTP_INTERNAL_SERVER_ERROR: u16 = 500;
const HTTP_BAD_GATEWAY: u16 = 502;
const HTTP_SERVICE_UNAVAILABLE: u16 = 503;
const HTTP_GATEWAY_TIMEOUT: u16 = 504;
const HTTP_INTERNAL_SERVER_ERROR: u16 = 500;
const HTTP_REQUEST_TIMEOUT: u16 = 408;
const HTTP_SERVICE_UNAVAILABLE: u16 = 503;
const HTTP_TOO_MANY_REQUESTS: u16 = 429;
impl crate::HttpTransportPool {
/// Executes one audited standard Solana HTTP JSON-RPC method through KSP routing, admission and bounded retry policy.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 20
// version: 21
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -193,10 +193,6 @@ pub use self::rpc_common::SolanaContextConfig;
pub use self::rpc_common::SolanaRpcContext;
/// Generic contextual result returned by typed Solana HTTP RPC adapters.
pub use self::rpc_common::SolanaRpcResponse;
/// Decodes one private serde wire type into the shared Transport error domain for typed RPC adapters.
pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Inflation-governor values returned by `getInflationGovernor`.
pub use self::rpc_economics::SolanaInflationGovernor;
/// Current inflation-rate values returned by `getInflationRate`.
@@ -296,7 +292,17 @@ pub use self::settings::HttpRoleName;
/// Complete runtime settings consumed by the Solana HTTP transport foundation.
pub use self::settings::HttpTransportSettings;
/// Owning tracing target for events emitted by the on-chain transport crate.
pub(crate) use self::constants::TRACING_TARGET;
/// Crate-internal `HttpConcurrencyPermit` state shared across the owning crate.
pub(crate) use self::resilience::HttpConcurrencyPermit;
/// Crate-internal `HttpRoleRuntime` state shared across the owning crate.
pub(crate) use self::resilience::HttpRoleRuntime;
/// Crate-internal `RoleAdmissionAttempt` variants used by the owning crate.
pub(crate) use self::resilience::RoleAdmissionAttempt;
/// Decodes one private serde wire type into the shared Transport error domain for typed RPC adapters.
pub(crate) use self::rpc_common::decode_wire_json;
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
pub(crate) use self::rpc_common::parse_wire_pubkey;
/// Validates endpoint settings.
pub(crate) use self::settings::validate_endpoint_settings;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/pool.rs
// version: 6
// version: 7
/// Safe snapshot of the logical HTTP endpoint pool.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -207,6 +207,7 @@ impl HttpTransportPool {
return &self.inner.retry;
}
/// Executes the crate-internal next request id operation for `HttpTransportPool`.
pub(crate) fn next_request_id(&self) -> u64 {
let id = self.inner.request_ids.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if id == 0 {
@@ -426,6 +427,7 @@ impl HttpTransportPool {
return std::time::Instant::now() < deadline;
}
/// Executes the crate-internal common request timeout operation for `HttpTransportPool`.
pub(crate) fn common_request_timeout(
&self,
role: &crate::HttpRoleName,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/resilience.rs
// version: 2
// version: 3
const DEFAULT_RATE_LIMIT_COOLDOWN: std::time::Duration = std::time::Duration::from_secs(1);
const MAX_PROVIDER_RETRY_AFTER: std::time::Duration = std::time::Duration::from_secs(60);
@@ -71,6 +71,7 @@ impl HttpRetryDecision {
}
}
/// Crate-internal `HttpRoleRuntime` state shared across the owning crate.
pub(crate) struct HttpRoleRuntime {
limits: crate::HttpRoleLimits,
bucket: std::sync::Mutex<std::option::Option<HttpTokenBucketState>>,
@@ -84,6 +85,7 @@ pub(crate) struct HttpRoleRuntime {
}
impl HttpRoleRuntime {
/// Creates a new `HttpRoleRuntime` value.
pub(crate) fn new(settings: &crate::HttpEndpointRoleSettings, notify: std::sync::Arc<tokio::sync::Notify>) -> Self {
let bucket = match settings.limits().requests_per_second() {
std::option::Option::Some(requests_per_second) => {
@@ -114,6 +116,7 @@ impl HttpRoleRuntime {
};
}
/// Returns the current availability.
pub(crate) fn availability(&self, now: std::time::Instant) -> crate::HttpEndpointAvailability {
if self.cooldown_remaining_at(now).is_some() {
return crate::HttpEndpointAvailability::RateLimited;
@@ -124,14 +127,17 @@ impl HttpRoleRuntime {
return crate::HttpEndpointAvailability::Available;
}
/// Returns the current cooldown remaining.
pub(crate) fn cooldown_remaining(&self) -> std::option::Option<std::time::Duration> {
return self.cooldown_remaining_at(std::time::Instant::now());
}
/// Returns the current max concurrent requests.
pub(crate) fn max_concurrent_requests(&self) -> std::option::Option<u32> {
return self.limits.max_concurrent_requests().map(|value| return value.get());
}
/// Returns the current in flight requests.
pub(crate) fn in_flight_requests(&self) -> std::option::Option<u32> {
let semaphore = match &self.semaphore {
std::option::Option::Some(value) => value,
@@ -149,18 +155,22 @@ impl HttpRoleRuntime {
return std::option::Option::Some(maximum.saturating_sub(available_u32));
}
/// Returns the current success count.
pub(crate) fn success_count(&self) -> u64 {
return self.success_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Returns the current failure count.
pub(crate) fn failure_count(&self) -> u64 {
return self.failure_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Returns the current rate limit count.
pub(crate) fn rate_limit_count(&self) -> u64 {
return self.rate_limit_count.load(std::sync::atomic::Ordering::Relaxed);
}
/// Attempts to acquire.
pub(crate) fn try_acquire(self: &std::sync::Arc<Self>, now: std::time::Instant) -> crate::RoleAdmissionAttempt {
if let std::option::Option::Some(remaining) = self.cooldown_remaining_at(now) {
let ready_at = match now.checked_add(remaining) {
@@ -189,6 +199,7 @@ impl HttpRoleRuntime {
return crate::RoleAdmissionAttempt::Ready(HttpConcurrencyPermit { semaphore_permit, notify: std::sync::Arc::clone(&self.notify) });
}
/// Records success.
pub(crate) fn record_success(&self) {
self.success_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.degraded.store(false, std::sync::atomic::Ordering::Relaxed);
@@ -196,6 +207,7 @@ impl HttpRoleRuntime {
return;
}
/// Records failure.
pub(crate) fn record_failure(&self) {
self.failure_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.degraded.store(true, std::sync::atomic::Ordering::Relaxed);
@@ -203,6 +215,7 @@ impl HttpRoleRuntime {
return;
}
/// Records rate limited.
pub(crate) fn record_rate_limited(&self, provider_retry_after: std::option::Option<std::time::Duration>) -> std::time::Duration {
self.failure_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.rate_limit_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
@@ -269,6 +282,7 @@ impl HttpRoleRuntime {
}
}
/// Crate-internal `RoleAdmissionAttempt` variants used by the owning crate.
pub(crate) enum RoleAdmissionAttempt {
Ready(HttpConcurrencyPermit),
BlockedUntil(std::time::Instant),
@@ -276,6 +290,7 @@ pub(crate) enum RoleAdmissionAttempt {
Unavailable,
}
/// Crate-internal `HttpConcurrencyPermit` state shared across the owning crate.
pub(crate) struct HttpConcurrencyPermit {
semaphore_permit: std::option::Option<tokio::sync::OwnedSemaphorePermit>,
notify: std::sync::Arc<tokio::sync::Notify>,

View File

@@ -1,9 +1,9 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_accounts.rs
// version: 5
// version: 6
const MAX_MEMCMP_BYTES: usize = 128;
const MAX_MULTIPLE_ACCOUNTS: usize = 100;
const MAX_PROGRAM_ACCOUNT_FILTERS: usize = 4;
const MAX_MEMCMP_BYTES: usize = 128;
/// Account-data encoding accepted by Solana HTTP account methods.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -120,6 +120,7 @@ impl SolanaAccountInfoConfig {
return self.context.min_context_slot();
}
/// Returns whether empty.
pub(crate) fn is_empty(&self) -> bool {
return self.encoding.is_none() && self.data_slice.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_cluster.rs
// version: 5
// version: 6
const MAX_GET_SLOT_LEADERS: u64 = 5_000;
@@ -297,9 +297,11 @@ impl SolanaLeaderScheduleConfig {
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() {
@@ -420,6 +422,7 @@ impl SolanaVoteAccountsConfig {
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();
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_method.rs
// version: 3
// version: 4
const CURRENT_HTTP_RPC_METHODS: [crate::HttpRpcMethodDescriptor; 52] = [
crate::HttpRpcMethodDescriptor::new(
@@ -1080,7 +1080,6 @@ impl HttpRpcMethodDescriptor {
pub const fn current_http_rpc_methods() -> &'static [crate::HttpRpcMethodDescriptor] {
return &CURRENT_HTTP_RPC_METHODS;
}
/// Returns the 14 historically documented deprecated HTTP RPC descriptors retained for compliance history.
#[must_use]
pub const fn historical_http_rpc_methods() -> &'static [crate::HttpRpcMethodDescriptor] {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
// version: 9
// version: 10
const MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS: usize = 128;
const MAX_SIGNATURES_FOR_ADDRESS_LIMIT: usize = 1_000;
@@ -1389,7 +1389,6 @@ impl crate::HttpTransportPool {
);
}
}
let mut params = std::vec![serde_json::Value::String(transaction.to_owned())];
if let std::option::Option::Some(config) = config
&& !config.is_empty()

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/settings.rs
// version: 5
// version: 6
/// Runtime HTTP endpoint URL owned by Transport.
///
@@ -446,6 +446,7 @@ impl HttpTransportSettings {
}
}
/// Validates endpoint settings.
pub(crate) fn validate_endpoint_settings(endpoint: &crate::HttpEndpointSettings) -> ksp_core_lib::Result<()> {
return validate_endpoint(endpoint, 0);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 23
// version: 24
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -442,7 +442,6 @@ fn public_v0_2_4_pre_007_simple_economics_wrappers_are_available_from_crate_root
let _get_inflation_rate = ksp_onchain_transport_lib::HttpTransportPool::get_inflation_rate;
let _get_stake_minimum_delegation = ksp_onchain_transport_lib::HttpTransportPool::get_stake_minimum_delegation;
let _get_supply = ksp_onchain_transport_lib::HttpTransportPool::get_supply;
let supply_config = ksp_onchain_transport_lib::SolanaSupplyConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
std::option::Option::Some(false),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 21
// version: 22
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
@@ -681,7 +681,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
"getSnapshotSlot",
"getStakeActivation",
];
let current = ksp_onchain_transport_lib::current_http_rpc_methods();
let historical = ksp_onchain_transport_lib::historical_http_rpc_methods();
let mut actual_current = std::vec::Vec::with_capacity(current.len());
@@ -691,7 +690,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
let mut v0_2_3 = 0_usize;
let mut v0_2_4 = 0_usize;
let mut historical_current = 0_usize;
for descriptor in current {
actual_current.push(descriptor.method());
assert_eq!(descriptor.runtime_status(), ksp_onchain_transport_lib::RpcRuntimeStatus::Supported);
@@ -719,7 +717,6 @@ fn release_v0_2_4_pre_009_final_http_inventory_and_coverage_partition_are_exact(
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::Historical);
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NotApplicable);
}
actual_current.sort_unstable();
actual_historical.sort_unstable();
let mut expected_current = expected_current;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/transport_devnet_smoke.rs
// version: 5
// version: 6
//! Opt-in live Devnet smoke for representative pure Transport reads across the complete HTTP typed surface.
@@ -45,7 +45,6 @@ fn devnet_pool() -> ksp_core_lib::Result<ksp_onchain_transport_lib::HttpTranspor
async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transactions_blocks_and_economics_reads() {
let pool = devnet_pool().expect("programmatic Devnet Transport settings must construct a pool");
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
let account_config = ksp_onchain_transport_lib::SolanaAccountInfoConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaAccountEncoding::Base64),
std::option::Option::None,
@@ -58,7 +57,6 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.expect("Devnet getAccountInfo smoke must succeed");
assert!(account.context().slot() > 0);
assert!(account.value().is_some());
// Follow the current official Devnet example shape with an ordinary owner, the canonical SPL Token program selector,
// and an explicit finalized/jsonParsed config. The owner need not retain any token account; an empty list remains valid.
let token_owner = "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd"
@@ -79,7 +77,6 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.await
.expect("Devnet getTokenAccountsByOwner smoke must succeed with the documented finalized/jsonParsed request shape");
assert!(token_accounts.context().slot() > 0);
let epoch = pool
.get_epoch_info(
&role,
@@ -91,10 +88,8 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.await
.expect("Devnet getEpochInfo smoke must succeed");
assert!(epoch.absolute_slot() > 0);
let vote_accounts = pool.get_vote_accounts(&role, std::option::Option::None).await.expect("Devnet getVoteAccounts smoke must succeed");
assert!(!vote_accounts.current().is_empty() || !vote_accounts.delinquent().is_empty());
let transaction_context = ksp_onchain_transport_lib::SolanaContextConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
std::option::Option::None,
@@ -105,27 +100,22 @@ async fn programmatic_devnet_transport_reaches_accounts_tokens_cluster_transacti
.expect("Devnet getLatestBlockhash smoke must succeed");
assert!(!latest_blockhash.value().blockhash().is_empty());
assert!(latest_blockhash.value().last_valid_block_height() > 0);
let blockhash_valid = pool
.is_blockhash_valid(&role, latest_blockhash.value().blockhash(), std::option::Option::Some(&transaction_context))
.await
.expect("Devnet isBlockhashValid smoke must succeed");
assert_eq!(blockhash_valid.value(), &true);
let transaction_count = pool
.get_transaction_count(&role, std::option::Option::Some(&transaction_context))
.await
.expect("Devnet getTransactionCount smoke must succeed");
assert!(transaction_count > 0);
let block_height = pool.get_block_height(&role, std::option::Option::Some(&transaction_context)).await.expect("Devnet getBlockHeight smoke must succeed");
assert!(block_height > 0);
let inflation_rate = pool.get_inflation_rate(&role).await.expect("Devnet getInflationRate smoke must succeed");
assert!(inflation_rate.total().is_finite());
assert!(inflation_rate.validator().is_finite());
assert!(inflation_rate.foundation().is_finite());
let stake_minimum_delegation = pool
.get_stake_minimum_delegation(&role, std::option::Option::Some(&transaction_context))
.await

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_accounts.rs
// version: 3
// version: 4
#[test]
fn account_config_serializes_all_common_fields() {
@@ -64,7 +64,6 @@ fn staged_largest_and_keyed_account_helpers_match_wire_shapes() {
std::option::Option::Some(true),
);
assert_eq!(config.to_json_value(), serde_json::json!({"commitment":"finalized","filter":"nonCirculating","sortResults":true}));
let keyed = crate::SolanaKeyedAccount::decode_wire(
"fixture",
serde_json::json!({
@@ -82,7 +81,6 @@ fn staged_largest_and_keyed_account_helpers_match_wire_shapes() {
.expect("keyed account must decode");
assert_eq!(keyed.pubkey().to_string(), "11111111111111111111111111111111");
assert_eq!(keyed.account().lamports(), 42);
let balance = crate::SolanaAccountBalance::decode_wire("fixture", serde_json::json!({"address":"11111111111111111111111111111111","lamports":99}))
.expect("account balance must decode");
assert_eq!(balance.address().to_string(), "11111111111111111111111111111111");
@@ -417,7 +415,6 @@ async fn typed_get_program_accounts_rejects_filter_cardinality_and_oversized_raw
let error = result.expect_err("more than four program-account filters must be rejected locally");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].value(), "5");
let oversized = crate::SolanaProgramAccountsConfig::new(
crate::SolanaAccountInfoConfig::default(),
std::vec![crate::SolanaProgramAccountFilter::Memcmp(crate::SolanaMemcmpFilter::new(0, crate::SolanaMemcmpBytes::Bytes(std::vec![0_u8; 129]),))],

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
// version: 5
// version: 6
#[test]
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
@@ -280,7 +280,6 @@ async fn typed_get_block_time_preserves_timestamp_and_null() {
assert_eq!(timestamp, std::option::Option::Some(1_787_072_400));
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000123]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_time.null.json"));
let pool = pool_for_url(url.as_str());
let timestamp = pool.get_block_time(&crate::HttpRoleName::new("default"), 430_000_124).await.expect("null block time fixture must succeed");
@@ -356,7 +355,6 @@ async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversi
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000100, 430000099]));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
let pool = pool_for_url(url.as_str());
let blocks = pool
@@ -366,7 +364,6 @@ async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversi
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([10, 500010]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks(&role, 10, std::option::Option::Some(500_011), std::option::Option::None).await;
let error = result.expect_err("range above 500000 must reject before I/O");
@@ -387,7 +384,6 @@ async fn typed_block_range_wrappers_reject_processed_commitment_before_io() {
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
let get_blocks_with_limit = pool.get_blocks_with_limit(&role, 1, 1, std::option::Option::Some(&config)).await;
let error = get_blocks_with_limit.expect_err("getBlocksWithLimit processed commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
@@ -404,7 +400,6 @@ async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_
assert!(blocks.is_empty());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200, 0]));
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(430_000_000));
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.success.json"));
let pool = pool_for_url(url.as_str());
@@ -415,7 +410,6 @@ async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_
assert_eq!(blocks, std::vec![430_000_200, 430_000_201, 430_000_205]);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200,500000,{"commitment":"finalized","minContextSlot":430000000}]));
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_blocks_with_limit(&role, 1, 500_001, std::option::Option::None).await;
let error = result.expect_err("getBlocksWithLimit above 500000 must reject before I/O");
@@ -497,7 +491,6 @@ async fn typed_get_block_production_omits_empty_config_and_supports_open_range_w
assert_eq!(response.value().range().last_slot(), 430_000_099);
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::None);
let config = crate::SolanaBlockProductionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Processed),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_cluster.rs
// version: 4
// version: 5
#[test]
fn cluster_node_fixture_preserves_v4_client_id_and_optional_fields() {
@@ -59,19 +59,16 @@ fn epoch_snapshot_and_leader_helpers_preserve_wire_shapes() {
)
.expect("epoch info must decode");
assert_eq!(epoch.transaction_count(), std::option::Option::None);
let schedule = crate::SolanaEpochSchedule::decode_wire(
"getEpochSchedule",
serde_json::json!({"firstNormalEpoch":1,"firstNormalSlot":32,"leaderScheduleSlotOffset":32,"slotsPerEpoch":64,"warmup":false}),
)
.expect("epoch schedule must decode");
assert_eq!(schedule.slots_per_epoch(), 64);
let snapshot = crate::SolanaSnapshotSlotInfo::decode_wire("getHighestSnapshotSlot", serde_json::json!({"full":100,"incremental":null}))
.expect("snapshot info must decode");
assert_eq!(snapshot.full(), 100);
assert_eq!(snapshot.incremental(), std::option::Option::None);
let leader = crate::SolanaLeaderSchedule::decode_wire("getLeaderSchedule", serde_json::json!({"11111111111111111111111111111111":[0,2,4]}))
.expect("leader schedule must decode");
assert_eq!(leader.entries().len(), 1);
@@ -90,7 +87,6 @@ fn vote_status_and_config_helpers_preserve_wire_shapes() {
config.to_json_value(),
serde_json::json!({"commitment":"finalized","votePubkey":"11111111111111111111111111111111","keepUnstakedDelinquents":true,"delinquentSlotDistance":128})
);
let status = crate::SolanaVoteAccountStatus::decode_wire(
"getVoteAccounts",
serde_json::json!({
@@ -363,7 +359,6 @@ async fn typed_get_slot_serializes_context_config_and_omits_empty_config() {
assert_eq!(slot, 430_000_020);
let request = configured_handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"confirmed","minContextSlot":429999999}]));
let (empty_url, empty_handle) = serve_once(include_str!("../fixtures/http/get_slot.success.json"));
let empty_pool = pool_for_url(empty_url.as_str());
let empty = crate::SolanaContextConfig::default();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
// version: 3
// version: 4
#[test]
fn inflation_reward_config_preserves_epoch_commitment_and_min_context_slot() {
@@ -346,7 +346,6 @@ async fn typed_get_inflation_reward_serializes_full_config_and_preserves_positio
let fourth = rewards[3].as_ref().expect("fourth reward must be present");
assert_eq!(fourth.commission(), std::option::Option::Some(3));
assert!(fourth.commission_bps().is_null());
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getInflationReward"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
// version: 8
// version: 9
#[test]
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
@@ -19,7 +19,6 @@ fn wire_field_preserves_omitted_null_and_value_states() {
#[serde(default)]
field: crate::SolanaWireField<u64>,
}
let omitted = serde_json::from_value::<Holder>(serde_json::json!({})).expect("omitted fixture must decode");
let null = serde_json::from_value::<Holder>(serde_json::json!({"field": null})).expect("null fixture must decode");
let value = serde_json::from_value::<Holder>(serde_json::json!({"field": 7})).expect("value fixture must decode");
@@ -402,7 +401,6 @@ fn serve_transaction_status_then_body(
let first_request = read_transaction_request(&mut first_stream);
let first_response = format!("HTTP/1.1 {status_line}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
std::io::Write::write_all(&mut first_stream, first_response.as_bytes()).expect("fixture first response must write");
listener.set_nonblocking(true).expect("fixture listener must become nonblocking");
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(500);
return loop {
@@ -612,7 +610,6 @@ async fn typed_get_recent_prioritization_fees_omits_optional_accounts_and_reject
assert_eq!(fees.len(), 2);
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!([]));
let account = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
let max_accounts = std::vec![account; 128];
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_recent_prioritization_fees.success.json"));
@@ -625,7 +622,6 @@ async fn typed_get_recent_prioritization_fees_omits_optional_accounts_and_reject
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["params"][0].as_array().expect("address parameter must be an array").len(), 128);
let invalid_pool = transaction_pool_for_url("http://127.0.0.1:9");
let accounts = std::vec![account; 129];
let result = invalid_pool.get_recent_prioritization_fees(&crate::HttpRoleName::new("default"), std::option::Option::Some(accounts.as_slice())).await;
@@ -768,7 +764,6 @@ async fn typed_get_signature_statuses_accepts_256_and_rejects_more_than_256_befo
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["params"][0].as_array().expect("signature parameter must be an array").len(), 256);
let invalid_pool = transaction_pool_for_url("http://127.0.0.1:9");
let signatures = std::vec!["signature".to_owned(); 257];
let result = invalid_pool.get_signature_statuses(&crate::HttpRoleName::new("default"), signatures.as_slice(), std::option::Option::None).await;
@@ -804,7 +799,6 @@ async fn typed_get_transaction_modern_supports_omitted_and_explicit_empty_config
assert!(result.is_none());
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["fixture-signature"]));
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.null.json"));
let pool = transaction_pool_for_url(url.as_str());
let config = crate::SolanaGetTransactionConfig::default();
@@ -1006,7 +1000,6 @@ async fn typed_request_airdrop_exposes_runtime_config_and_canonicalizes_empty_co
{"recentBlockhash":"recent-blockhash-fixture","commitment":"finalized"}
])
);
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.success.json"));
let pool = transaction_pool_for_url(url.as_str());
let empty = crate::SolanaRequestAirdropConfig::default();
@@ -1050,7 +1043,6 @@ async fn typed_send_transaction_exposes_all_current_options_and_both_binary_enco
}
])
);
let base58 = crate::SolanaSendTransactionConfig::new(
std::option::Option::None,
std::option::Option::None,
@@ -1066,7 +1058,6 @@ async fn typed_send_transaction_exposes_all_current_options_and_both_binary_enco
.expect("sendTransaction base58 fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-base58-transaction", {"encoding":"base58"}]));
let empty = crate::SolanaSendTransactionConfig::default();
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
let pool = transaction_pool_for_url(url.as_str());
@@ -1086,7 +1077,6 @@ async fn typed_write_wrappers_preserve_rpc_application_errors_without_transport_
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
assert_eq!(result.expect_err("requestAirdrop RPC error must propagate").code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
handle.join().expect("fixture server must join");
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.preflight_error.json"));
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1104,7 +1094,6 @@ async fn typed_write_wrappers_never_resend_after_http_429_dispatch() {
let (count, request) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
let (url, handle) = serve_transaction_status_and_count("429 Too Many Requests");
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1123,7 +1112,6 @@ async fn typed_write_wrappers_never_resend_after_temporary_http_dispatch() {
assert_eq!(result.expect_err("airdrop 503 must stop after dispatch").code(), crate::ERROR_CODE_HTTP_REQUEST_FAILED);
let (count, _) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
let (url, handle) = serve_transaction_status_and_count("503 Service Unavailable");
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1141,7 +1129,6 @@ async fn typed_write_wrappers_never_resend_after_ambiguous_timeout_dispatch() {
assert_eq!(result.expect_err("airdrop timeout must stop after ambiguous dispatch").code(), crate::ERROR_CODE_TIMEOUT);
let (count, _) = handle.join().expect("fixture server must join");
assert_eq!(count, 1);
let (url, handle) = serve_transaction_timeout_and_count(std::time::Duration::from_millis(80));
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(20), 3);
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
@@ -1163,7 +1150,6 @@ async fn typed_write_wrappers_can_retry_when_connection_failure_proves_not_dispa
assert_eq!(result, "airdrop-fixture-signature-111111111111111111111111111111111111111111111111");
let request = handle.join().expect("secondary fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
let closed = unused_local_url();
let (secondary, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
let pool = transaction_pool_for_urls(&[(closed.as_str(), 10), (secondary.as_str(), 10)], std::time::Duration::from_millis(500), 1);
@@ -1210,7 +1196,6 @@ async fn typed_simulate_transaction_serializes_complete_config_and_preserves_ric
let replacement = response.value().replacement_blockhash().value().expect("replacement blockhash must be present");
assert_eq!(replacement.blockhash(), "ComputeBudget111111111111111111111111111111");
assert_eq!(replacement.last_valid_block_height(), 430_123_999);
let request = handle.join().expect("fixture server must join");
let body = transaction_request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("simulateTransaction"));
@@ -1250,7 +1235,6 @@ async fn typed_simulate_transaction_supports_base58_and_omits_empty_config() {
.expect("simulateTransaction base58 fixture must succeed");
let request = handle.join().expect("fixture server must join");
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-base58-transaction", {"encoding":"base58"}]));
let empty = crate::SolanaSimulateTransactionConfig::default();
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/simulate_transaction.partial.json"));
let pool = transaction_pool_for_url(url.as_str());
@@ -1303,7 +1287,6 @@ async fn typed_simulate_transaction_rejects_deterministic_invalid_configs_before
);
let result = pool.simulate_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::Some(&conflicting)).await;
assert_eq!(result.expect_err("conflicting simulation flags must fail before I/O").code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
let rejected_encodings = [crate::SolanaAccountEncoding::Binary, crate::SolanaAccountEncoding::Base58];
for encoding in rejected_encodings {