v0.2.3-pre.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 123
|
||||
# version: 124
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.3-pre.3"
|
||||
version = "0.2.3-pre.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[{"slot":431000030,"prioritizationFee":1200},{"slot":431000029,"prioritizationFee":0}],"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":431000052},"value":[null]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":431000051},"value":[]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":431000053},"value":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":431000050},"value":[null,{"slot":431000049,"confirmations":null,"status":{"Ok":null},"err":null,"confirmationStatus":"finalized"},{"slot":431000048,"confirmations":2,"status":{"Err":{"InstructionError":[0,"Custom"]}},"err":{"InstructionError":[0,"Custom"]},"confirmationStatus":"confirmed"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32015,"message":"Transaction history is not available from this node"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[{"signature":"newest-signature","slot":431000040,"err":null,"memo":"memo-newest","blockTime":1787000100,"confirmationStatus":"finalized","transactionIndex":9},{"signature":"older-signature","slot":431000039,"err":{"InstructionError":[0,"Custom"]},"memo":null,"blockTime":null,"confirmationStatus":"confirmed"}],"id":1}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -10,9 +10,10 @@
|
||||
//! 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 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 its first four read wrappers:
|
||||
//! `getFeeForMessage`, `getLatestBlockhash`, `getTransactionCount` and `isBlockhashValid`. The remaining seven Transaction wrappers and the `0.2.4` family
|
||||
//! remain staged.
|
||||
//! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives and seven read wrappers through `pre.004`,
|
||||
//! including bounded prioritization-fee, address-signature and signature-status queries. `getTransaction`, both write submissions and
|
||||
//! `simulateTransaction`, plus the
|
||||
//! `0.2.4` family remain staged.
|
||||
|
||||
mod client;
|
||||
mod constants;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Binary encoding accepted for serialized transaction input payloads.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -236,14 +236,12 @@ impl SolanaSignaturesForAddressConfig {
|
||||
}
|
||||
|
||||
/// Returns whether the pagination config would serialize to an empty object.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
return self.before.is_none() && self.until.is_none() && self.limit.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
|
||||
}
|
||||
|
||||
/// Serializes this pagination config to the Solana JSON-RPC wire object.
|
||||
#[must_use]
|
||||
#[cfg(test)]
|
||||
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
||||
let context = self.context.to_json_value();
|
||||
let mut object = match context {
|
||||
@@ -283,14 +281,12 @@ impl SolanaSignatureStatusesConfig {
|
||||
}
|
||||
|
||||
/// Returns whether the status config would serialize to an empty object.
|
||||
#[cfg(test)]
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.search_transaction_history.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(value) = self.search_transaction_history {
|
||||
@@ -651,7 +647,6 @@ impl SolanaPrioritizationFee {
|
||||
}
|
||||
|
||||
/// Decodes one prioritization-fee sample 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::<WirePrioritizationFee>(method, value);
|
||||
return match decoded {
|
||||
@@ -728,7 +723,6 @@ impl SolanaSignatureInfo {
|
||||
}
|
||||
|
||||
/// Decodes one signature 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::<WireSignatureInfo>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -794,7 +788,6 @@ impl SolanaSignatureStatus {
|
||||
}
|
||||
|
||||
/// Decodes one present signature status 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::<WireSignatureStatus>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -1102,6 +1095,10 @@ impl SolanaSimulateTransactionResult {
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS: usize = 128;
|
||||
const MAX_SIGNATURES_FOR_ADDRESS_LIMIT: usize = 1_000;
|
||||
const MAX_SIGNATURE_STATUSES: usize = 256;
|
||||
|
||||
impl crate::HttpTransportPool {
|
||||
/// Executes typed `getFeeForMessage` through the common KSP HTTP transport path.
|
||||
pub async fn get_fee_for_message(
|
||||
@@ -1154,6 +1151,106 @@ impl crate::HttpTransportPool {
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `getRecentPrioritizationFees` through the common KSP HTTP transport path.
|
||||
pub async fn get_recent_prioritization_fees(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
writable_accounts: std::option::Option<&[ksp_core_lib::Pubkey]>,
|
||||
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPrioritizationFee>> {
|
||||
if let std::option::Option::Some(writable_accounts) = writable_accounts
|
||||
&& writable_accounts.len() > MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS
|
||||
{
|
||||
return invalid_transaction_parameters(
|
||||
"getRecentPrioritizationFees",
|
||||
"getRecentPrioritizationFees accepts at most 128 account addresses",
|
||||
"account_count",
|
||||
writable_accounts.len(),
|
||||
);
|
||||
}
|
||||
let mut params = std::vec::Vec::new();
|
||||
if let std::option::Option::Some(writable_accounts) = writable_accounts {
|
||||
let mut addresses = std::vec::Vec::with_capacity(writable_accounts.len());
|
||||
for account in writable_accounts {
|
||||
addresses.push(serde_json::Value::String(account.to_string()));
|
||||
}
|
||||
params.push(serde_json::Value::Array(addresses));
|
||||
}
|
||||
let value = self.execute_transaction_rpc("getRecentPrioritizationFees", 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_prioritization_fees("getRecentPrioritizationFees", value);
|
||||
}
|
||||
|
||||
/// Executes typed `getSignaturesForAddress` through the common KSP HTTP transport path.
|
||||
pub async fn get_signatures_for_address(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
address: &ksp_core_lib::Pubkey,
|
||||
config: std::option::Option<&crate::SolanaSignaturesForAddressConfig>,
|
||||
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaSignatureInfo>> {
|
||||
if let std::option::Option::Some(limit) = config.and_then(crate::SolanaSignaturesForAddressConfig::limit)
|
||||
&& (limit == 0 || limit > MAX_SIGNATURES_FOR_ADDRESS_LIMIT)
|
||||
{
|
||||
return invalid_transaction_parameters("getSignaturesForAddress", "getSignaturesForAddress limit must be between 1 and 1000", "limit", limit);
|
||||
}
|
||||
let mut params = std::vec![serde_json::Value::String(address.to_string())];
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& !config.is_empty()
|
||||
{
|
||||
params.push(config.to_json_value());
|
||||
}
|
||||
let value = self.execute_transaction_rpc("getSignaturesForAddress", 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_signature_infos("getSignaturesForAddress", value);
|
||||
}
|
||||
|
||||
/// Executes typed `getSignatureStatuses` through the common KSP HTTP transport path.
|
||||
pub async fn get_signature_statuses(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
signatures: &[std::string::String],
|
||||
config: std::option::Option<&crate::SolanaSignatureStatusesConfig>,
|
||||
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<std::option::Option<crate::SolanaSignatureStatus>>>> {
|
||||
if signatures.len() > MAX_SIGNATURE_STATUSES {
|
||||
return invalid_transaction_parameters(
|
||||
"getSignatureStatuses",
|
||||
"getSignatureStatuses accepts at most 256 signatures",
|
||||
"signature_count",
|
||||
signatures.len(),
|
||||
);
|
||||
}
|
||||
let mut signature_values = std::vec::Vec::with_capacity(signatures.len());
|
||||
for signature in signatures {
|
||||
signature_values.push(serde_json::Value::String(signature.clone()));
|
||||
}
|
||||
let mut params = std::vec![serde_json::Value::Array(signature_values)];
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& !config.is_empty()
|
||||
{
|
||||
params.push((*config).to_json_value());
|
||||
}
|
||||
let value = self.execute_transaction_rpc("getSignatureStatuses", 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 contextual = decode_transaction_contextual_wire("getSignatureStatuses", value);
|
||||
let (context, value) = match contextual {
|
||||
std::result::Result::Ok(contextual) => contextual,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let statuses = decode_signature_statuses("getSignatureStatuses", value, signatures.len());
|
||||
return match statuses {
|
||||
std::result::Result::Ok(statuses) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, statuses)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `getTransactionCount` through the common KSP HTTP transport path.
|
||||
pub async fn get_transaction_count(
|
||||
&self,
|
||||
@@ -1232,6 +1329,84 @@ fn decode_transaction_contextual_wire(method: &str, value: serde_json::Value) ->
|
||||
};
|
||||
}
|
||||
|
||||
fn decode_prioritization_fees(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPrioritizationFee>> {
|
||||
let decoded = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(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 fees = std::vec::Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let fee = crate::SolanaPrioritizationFee::decode_wire(method, value);
|
||||
match fee {
|
||||
std::result::Result::Ok(fee) => fees.push(fee),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(fees);
|
||||
}
|
||||
|
||||
fn decode_signature_infos(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaSignatureInfo>> {
|
||||
let decoded = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(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 infos = std::vec::Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let info = crate::SolanaSignatureInfo::decode_wire(method, value);
|
||||
match info {
|
||||
std::result::Result::Ok(info) => infos.push(info),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(infos);
|
||||
}
|
||||
|
||||
fn decode_signature_statuses(
|
||||
method: &str,
|
||||
value: serde_json::Value,
|
||||
expected_count: usize,
|
||||
) -> ksp_core_lib::Result<std::vec::Vec<std::option::Option<crate::SolanaSignatureStatus>>> {
|
||||
let decoded = crate::decode_wire_json::<std::vec::Vec<std::option::Option<serde_json::Value>>>(method, value);
|
||||
let values = match decoded {
|
||||
std::result::Result::Ok(values) => values,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if values.len() != expected_count {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "getSignatureStatuses result count does not match the requested signature count")
|
||||
.with_context("rpc_method", method)
|
||||
.with_context("expected_count", expected_count.to_string())
|
||||
.with_context("actual_count", values.len().to_string()),
|
||||
);
|
||||
}
|
||||
let mut statuses = std::vec::Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let value = match value {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
statuses.push(std::option::Option::None);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let status = crate::SolanaSignatureStatus::decode_wire(method, value);
|
||||
match status {
|
||||
std::result::Result::Ok(status) => statuses.push(std::option::Option::Some(status)),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(statuses);
|
||||
}
|
||||
|
||||
fn invalid_transaction_parameters<T>(method: &str, message: &str, field: &'static str, value: usize) -> 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 transaction_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpRpcMethodDescriptor> {
|
||||
let descriptor = crate::find_http_rpc_method(method);
|
||||
return match descriptor {
|
||||
@@ -1257,7 +1432,6 @@ fn invalid_transaction_wire(method: &str, field: &str, message: &'static str) ->
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, message).with_context("rpc_method", method).with_context("field", field);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_confirmation_status(
|
||||
method: &str,
|
||||
field: &str,
|
||||
@@ -1386,7 +1560,6 @@ struct WireLatestBlockhash {
|
||||
last_valid_block_height: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WirePrioritizationFee {
|
||||
@@ -1394,7 +1567,6 @@ struct WirePrioritizationFee {
|
||||
prioritization_fee: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireSignatureInfo {
|
||||
@@ -1412,7 +1584,6 @@ struct WireSignatureInfo {
|
||||
transaction_index: std::option::Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireSignatureStatus {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -278,3 +278,10 @@ fn public_transaction_pre_003_read_wrappers_are_available_from_crate_root() {
|
||||
let _get_transaction_count = ksp_onchain_transport_lib::HttpTransportPool::get_transaction_count;
|
||||
let _is_blockhash_valid = ksp_onchain_transport_lib::HttpTransportPool::is_blockhash_valid;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_transaction_pre_004_additional_read_wrappers_are_available_from_crate_root() {
|
||||
let _get_recent_prioritization_fees = ksp_onchain_transport_lib::HttpTransportPool::get_recent_prioritization_fees;
|
||||
let _get_signatures_for_address = ksp_onchain_transport_lib::HttpTransportPool::get_signatures_for_address;
|
||||
let _get_signature_statuses = ksp_onchain_transport_lib::HttpTransportPool::get_signature_statuses;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||
|
||||
@@ -259,3 +259,35 @@ fn release_pre_003_transaction_read_subset_is_exact_and_retry_safe() {
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(actual.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_pre_004_transaction_read_subset_is_exact_and_retry_safe() {
|
||||
let mut expected = std::vec![
|
||||
"getFeeForMessage",
|
||||
"getLatestBlockhash",
|
||||
"getRecentPrioritizationFees",
|
||||
"getSignaturesForAddress",
|
||||
"getSignatureStatuses",
|
||||
"getTransactionCount",
|
||||
"isBlockhashValid",
|
||||
];
|
||||
let deferred = std::vec!["getTransaction", "requestAirdrop", "sendTransaction", "simulateTransaction"];
|
||||
let mut actual = std::vec::Vec::new();
|
||||
for method_name in &expected {
|
||||
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("pre.004 Transaction descriptor must exist");
|
||||
actual.push(descriptor.method());
|
||||
assert_eq!(descriptor.category(), ksp_onchain_transport_lib::HttpRpcCategory::Transactions);
|
||||
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3);
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||
}
|
||||
for method_name in &deferred {
|
||||
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("deferred Transaction descriptor must remain registered");
|
||||
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3);
|
||||
}
|
||||
actual.sort_unstable();
|
||||
expected.sort_unstable();
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(actual.len(), 7);
|
||||
assert_eq!(deferred.len(), 4);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
|
||||
@@ -470,3 +470,219 @@ async fn typed_is_blockhash_valid_preserves_opaque_blockhash_and_contextual_bool
|
||||
assert_eq!(body["method"], serde_json::json!("isBlockhashValid"));
|
||||
assert_eq!(body["params"], serde_json::json!(["opaque-blockhash", {"commitment":"processed","minContextSlot":431000004}]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_recent_prioritization_fees_serializes_addresses_and_preserves_server_order() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_recent_prioritization_fees.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let first = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let second = "SysvarRent111111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let first_text = first.to_string();
|
||||
let second_text = second.to_string();
|
||||
let accounts = std::vec![first, second];
|
||||
let fees = pool
|
||||
.get_recent_prioritization_fees(&crate::HttpRoleName::new("default"), std::option::Option::Some(accounts.as_slice()))
|
||||
.await
|
||||
.expect("getRecentPrioritizationFees fixture must succeed");
|
||||
assert_eq!(fees.len(), 2);
|
||||
assert_eq!(fees[0].slot(), 431_000_030);
|
||||
assert_eq!(fees[0].prioritization_fee(), 1_200);
|
||||
assert_eq!(fees[1].slot(), 431_000_029);
|
||||
assert_eq!(fees[1].prioritization_fee(), 0);
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
let body = transaction_request_body(request.as_str());
|
||||
assert_eq!(body["method"], serde_json::json!("getRecentPrioritizationFees"));
|
||||
assert_eq!(body["params"], serde_json::json!([[first_text, second_text]]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_recent_prioritization_fees_omits_optional_accounts_and_rejects_more_than_128_before_io() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_recent_prioritization_fees.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let fees = pool
|
||||
.get_recent_prioritization_fees(&crate::HttpRoleName::new("default"), std::option::Option::None)
|
||||
.await
|
||||
.expect("getRecentPrioritizationFees without addresses must succeed");
|
||||
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"));
|
||||
let max_pool = transaction_pool_for_url(url.as_str());
|
||||
let fees = max_pool
|
||||
.get_recent_prioritization_fees(&crate::HttpRoleName::new("default"), std::option::Option::Some(max_accounts.as_slice()))
|
||||
.await
|
||||
.expect("exactly 128 prioritization-fee accounts must remain accepted");
|
||||
assert_eq!(fees.len(), 2);
|
||||
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;
|
||||
let error = result.expect_err("more than 128 prioritization-fee accounts must reject before I/O");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
|
||||
assert_eq!(error.context()[1].key(), "account_count");
|
||||
assert_eq!(error.context()[1].value(), "129");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signatures_for_address_serializes_pagination_and_preserves_newest_first_result() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signatures_for_address.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let config = crate::SolanaSignaturesForAddressConfig::new(
|
||||
std::option::Option::Some("before-signature".to_owned()),
|
||||
std::option::Option::Some("until-signature".to_owned()),
|
||||
std::option::Option::Some(1_000),
|
||||
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||
std::option::Option::Some(431_000_000),
|
||||
);
|
||||
let signatures = pool
|
||||
.get_signatures_for_address(&crate::HttpRoleName::new("default"), &address, std::option::Option::Some(&config))
|
||||
.await
|
||||
.expect("getSignaturesForAddress fixture must succeed");
|
||||
assert_eq!(signatures.len(), 2);
|
||||
assert_eq!(signatures[0].signature(), "newest-signature");
|
||||
assert_eq!(signatures[0].slot(), 431_000_040);
|
||||
assert_eq!(signatures[0].memo(), std::option::Option::Some("memo-newest"));
|
||||
assert_eq!(signatures[0].block_time(), std::option::Option::Some(1_787_000_100));
|
||||
assert_eq!(signatures[0].confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
|
||||
assert_eq!(signatures[0].transaction_index(), std::option::Option::Some(9));
|
||||
assert_eq!(signatures[1].signature(), "older-signature");
|
||||
assert!(signatures[1].err().is_some());
|
||||
assert_eq!(signatures[1].memo(), std::option::Option::None);
|
||||
assert_eq!(signatures[1].block_time(), std::option::Option::None);
|
||||
assert_eq!(signatures[1].transaction_index(), std::option::Option::None);
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
let body = transaction_request_body(request.as_str());
|
||||
assert_eq!(body["method"], serde_json::json!("getSignaturesForAddress"));
|
||||
assert_eq!(
|
||||
body["params"],
|
||||
serde_json::json!([
|
||||
address.to_string(),
|
||||
{
|
||||
"before":"before-signature",
|
||||
"until":"until-signature",
|
||||
"limit":1000,
|
||||
"commitment":"finalized",
|
||||
"minContextSlot":431000000
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signatures_for_address_rejects_limit_outside_1_to_1000_before_io() {
|
||||
let pool = transaction_pool_for_url("http://127.0.0.1:9");
|
||||
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
for limit in [0_usize, 1_001_usize] {
|
||||
let config = crate::SolanaSignaturesForAddressConfig::new(
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(limit),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let result = pool.get_signatures_for_address(&crate::HttpRoleName::new("default"), &address, std::option::Option::Some(&config)).await;
|
||||
let error = result.expect_err("out-of-range getSignaturesForAddress limit must reject before I/O");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
|
||||
assert_eq!(error.context()[1].key(), "limit");
|
||||
assert_eq!(error.context()[1].value(), limit.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signatures_for_address_preserves_rpc_application_error() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signatures_for_address.error.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let result = pool.get_signatures_for_address(&crate::HttpRoleName::new("default"), &address, std::option::Option::None).await;
|
||||
let error = result.expect_err("remote transaction-history error must remain an RPC application error");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
|
||||
handle.join().expect("fixture server must join");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signature_statuses_preserves_positional_nulls_and_history_config() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signature_statuses.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let signatures = std::vec!["missing-signature".to_owned(), "finalized-signature".to_owned(), "confirmed-signature".to_owned()];
|
||||
let config = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(true));
|
||||
let response = pool
|
||||
.get_signature_statuses(&crate::HttpRoleName::new("default"), signatures.as_slice(), std::option::Option::Some(&config))
|
||||
.await
|
||||
.expect("getSignatureStatuses fixture must succeed");
|
||||
assert_eq!(response.context().slot(), 431_000_050);
|
||||
assert_eq!(response.context().api_version(), std::option::Option::Some("4.2.1"));
|
||||
assert_eq!(response.value().len(), 3);
|
||||
assert!(response.value()[0].is_none());
|
||||
let finalized = response.value()[1].as_ref().expect("second positional status must be present");
|
||||
assert_eq!(finalized.slot(), 431_000_049);
|
||||
assert_eq!(finalized.confirmations(), std::option::Option::None);
|
||||
assert_eq!(finalized.status(), &serde_json::json!({"Ok":null}));
|
||||
assert_eq!(finalized.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
|
||||
let confirmed = response.value()[2].as_ref().expect("third positional status must be present");
|
||||
assert_eq!(confirmed.confirmations(), std::option::Option::Some(2));
|
||||
assert!(confirmed.err().is_some());
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
let body = transaction_request_body(request.as_str());
|
||||
assert_eq!(body["method"], serde_json::json!("getSignatureStatuses"));
|
||||
assert_eq!(body["params"], serde_json::json!([signatures, {"searchTransactionHistory":true}]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signature_statuses_accepts_empty_input_and_omits_empty_config() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signature_statuses.empty.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let signatures = std::vec::Vec::<std::string::String>::new();
|
||||
let config = crate::SolanaSignatureStatusesConfig::default();
|
||||
let response = pool
|
||||
.get_signature_statuses(&crate::HttpRoleName::new("default"), signatures.as_slice(), std::option::Option::Some(&config))
|
||||
.await
|
||||
.expect("empty getSignatureStatuses input must remain accepted");
|
||||
assert!(response.value().is_empty());
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!([[]]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signature_statuses_accepts_256_and_rejects_more_than_256_before_io() {
|
||||
let signatures = std::vec!["signature".to_owned(); 256];
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signature_statuses.max_256.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let response = pool
|
||||
.get_signature_statuses(&crate::HttpRoleName::new("default"), signatures.as_slice(), std::option::Option::None)
|
||||
.await
|
||||
.expect("exactly 256 signatures must remain accepted");
|
||||
assert_eq!(response.value().len(), 256);
|
||||
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;
|
||||
let error = result.expect_err("more than 256 signatures must reject before I/O");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
|
||||
assert_eq!(error.context()[1].key(), "signature_count");
|
||||
assert_eq!(error.context()[1].value(), "257");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_get_signature_statuses_rejects_response_count_mismatch() {
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_signature_statuses.count_mismatch.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let requested = std::vec!["first".to_owned(), "second".to_owned()];
|
||||
let result = pool.get_signature_statuses(&crate::HttpRoleName::new("default"), requested.as_slice(), std::option::Option::None).await;
|
||||
let error = result.expect_err("status response count mismatch must reject typed decoding");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||
assert_eq!(error.context()[1].key(), "expected_count");
|
||||
assert_eq!(error.context()[1].value(), "2");
|
||||
assert_eq!(error.context()[2].key(), "actual_count");
|
||||
assert_eq!(error.context()[2].value(), "1");
|
||||
handle.join().expect("fixture server must join");
|
||||
}
|
||||
|
||||
346
deltas/0.2.3/pre.004.md
Normal file
346
deltas/0.2.3/pre.004.md
Normal file
@@ -0,0 +1,346 @@
|
||||
<!-- file: deltas/0.2.3/pre.004.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.3-pre.004` — lectures Transaction avec cardinalités et statuts positionnels
|
||||
|
||||
## Base requise
|
||||
|
||||
Livraison précédente validée localement par l'opérateur :
|
||||
|
||||
```text
|
||||
0.2.3-pre.003
|
||||
workspace.package.version = "0.2.3-pre.3"
|
||||
```
|
||||
|
||||
La validation opérateur du 2026-08-18 a confirmé :
|
||||
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
cargo check --workspace OK
|
||||
cargo clippy --workspace --all-targets OK
|
||||
cargo test -p ksp-onchain-transport-lib OK
|
||||
```
|
||||
|
||||
Résultats Transport de cette base :
|
||||
|
||||
```text
|
||||
152 unit tests
|
||||
15 public API tests
|
||||
9 release completeness tests
|
||||
0 warning signalé par check/clippy
|
||||
```
|
||||
|
||||
Le plan canonique reste `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md` version 3.
|
||||
|
||||
## Objectif
|
||||
|
||||
Implémenter les trois wrappers Transaction `Read / RetrySafe` prévus par la tranche `pre.004` :
|
||||
|
||||
```text
|
||||
getRecentPrioritizationFees
|
||||
getSignaturesForAddress
|
||||
getSignatureStatuses
|
||||
```
|
||||
|
||||
Ils rejoignent les quatre reads de `pre.003`, portant la surface Transaction typée exécutée à :
|
||||
|
||||
```text
|
||||
7 / 11 wrappers
|
||||
```
|
||||
|
||||
Les quatre méthodes restantes restent volontairement différées :
|
||||
|
||||
```text
|
||||
getTransaction
|
||||
requestAirdrop
|
||||
sendTransaction
|
||||
simulateTransaction
|
||||
```
|
||||
|
||||
## Version Cargo
|
||||
|
||||
Conformément à `VER-ID-009` :
|
||||
|
||||
```text
|
||||
0.2.3-pre.3 -> 0.2.3-pre.4
|
||||
```
|
||||
|
||||
Aucune dépendance ni feature Cargo n'est ajoutée.
|
||||
|
||||
## `getRecentPrioritizationFees`
|
||||
|
||||
Surface publique conceptuelle :
|
||||
|
||||
```text
|
||||
role + Option<&[Pubkey]>
|
||||
-> Vec<SolanaPrioritizationFee>
|
||||
```
|
||||
|
||||
Le tableau d'adresses reste optionnel :
|
||||
|
||||
- `None` omet entièrement le paramètre ;
|
||||
- `Some(slice)` sérialise exactement un tableau d'adresses ;
|
||||
- l'ordre des adresses caller est conservé ;
|
||||
- les résultats restent dans l'ordre renvoyé par le provider.
|
||||
|
||||
La cardinalité runtime documentée est appliquée avant I/O :
|
||||
|
||||
```text
|
||||
0..=128 accepté
|
||||
129+ ERROR_CODE_INVALID_RPC_PARAMETERS
|
||||
```
|
||||
|
||||
Le test couvre explicitement la borne `128` puis le rejet de `129`.
|
||||
|
||||
Aucune règle locale n'est créée à partir de la profondeur actuelle de cache des prioritization fees : la valeur d'environ 150 blocs est une
|
||||
caractéristique runtime du noeud et non une cardinalité client.
|
||||
|
||||
## `getSignaturesForAddress`
|
||||
|
||||
Surface publique conceptuelle :
|
||||
|
||||
```text
|
||||
role + Pubkey + Option<SolanaSignaturesForAddressConfig>
|
||||
-> Vec<SolanaSignatureInfo>
|
||||
```
|
||||
|
||||
La config préparée en `pre.002` devient production-live pour cette méthode :
|
||||
|
||||
```text
|
||||
before
|
||||
until
|
||||
limit
|
||||
commitment
|
||||
minContextSlot
|
||||
```
|
||||
|
||||
Une config absente ou entièrement vide est omise. La limite est validée avant I/O exactement selon le runtime audité :
|
||||
|
||||
```text
|
||||
1..=1000 accepté quand limit est fourni
|
||||
0 rejeté
|
||||
1001+ rejeté
|
||||
```
|
||||
|
||||
La fixture de succès utilise explicitement `limit = 1000`, puis les tests rejettent `0` et `1001`.
|
||||
|
||||
Le résultat conserve notamment :
|
||||
|
||||
```text
|
||||
signature
|
||||
slot
|
||||
err nullable
|
||||
memo nullable
|
||||
blockTime nullable
|
||||
confirmationStatus nullable
|
||||
transactionIndex optionnel
|
||||
```
|
||||
|
||||
L'ordre `newest -> oldest` du provider n'est jamais retrié côté KSP. Les signatures restent des chaînes wire opaques dans cette release.
|
||||
|
||||
Une fixture JSON-RPC d'erreur vérifie qu'une indisponibilité de transaction history reste une `RPC_APPLICATION_ERROR`, sans être transformée en
|
||||
retry Transport.
|
||||
|
||||
## `getSignatureStatuses`
|
||||
|
||||
Surface publique conceptuelle :
|
||||
|
||||
```text
|
||||
role + &[String] + Option<SolanaSignatureStatusesConfig>
|
||||
-> SolanaRpcResponse<Vec<Option<SolanaSignatureStatus>>>
|
||||
```
|
||||
|
||||
La config `searchTransactionHistory` préparée en `pre.002` devient production-live. Une config vide est omise.
|
||||
|
||||
Le tableau d'entrée suit exactement la borne Agave auditée :
|
||||
|
||||
```text
|
||||
0..=256 accepté
|
||||
257+ ERROR_CODE_INVALID_RPC_PARAMETERS
|
||||
```
|
||||
|
||||
Le tableau vide reste volontairement accepté, conformément au runtime `v4.2.1` audité en `pre.001`. La borne `256` est exercée par une fixture
|
||||
contenant 256 positions `null` et `257` est rejeté avant I/O.
|
||||
|
||||
Le résultat conserve strictement la correspondance positionnelle :
|
||||
|
||||
```text
|
||||
entrée[i] <-> value[i]
|
||||
null -> None
|
||||
objet -> Some(SolanaSignatureStatus)
|
||||
```
|
||||
|
||||
KSP vérifie maintenant que le provider retourne exactement autant de positions que de signatures demandées. Une longueur différente produit
|
||||
`ERROR_CODE_INVALID_RESPONSE`, car continuer masquerait la correspondance signature/statut.
|
||||
|
||||
Pour un statut présent, les champs suivants restent préservés :
|
||||
|
||||
```text
|
||||
slot
|
||||
confirmations nullable
|
||||
status legacy lossless JSON
|
||||
err nullable
|
||||
confirmationStatus nullable
|
||||
```
|
||||
|
||||
La forme `status` demeure un champ legacy du wire courant ; elle n'est pas confondue avec les anciennes méthodes RPC Deprecated.
|
||||
|
||||
## Helpers activés en production
|
||||
|
||||
Seuls les helpers nécessaires à cette tranche quittent `#[cfg(test)]` :
|
||||
|
||||
```text
|
||||
SolanaSignaturesForAddressConfig::{is_empty,to_json_value}
|
||||
SolanaSignatureStatusesConfig::{is_empty,to_json_value}
|
||||
SolanaPrioritizationFee::decode_wire
|
||||
SolanaSignatureInfo::decode_wire
|
||||
SolanaSignatureStatus::decode_wire
|
||||
decode_confirmation_status
|
||||
WirePrioritizationFee
|
||||
WireSignatureInfo
|
||||
WireSignatureStatus
|
||||
```
|
||||
|
||||
Les helpers de `getTransaction`, `requestAirdrop`, `sendTransaction` et `simulateTransaction` restent staged/test-only jusqu'à leur tranche
|
||||
propriétaire. Aucun `#[allow(dead_code)]` n'est introduit.
|
||||
|
||||
## Fixtures HTTP déterministes ajoutées
|
||||
|
||||
```text
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_prioritization_fees.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signatures_for_address.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signatures_for_address.error.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.empty.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.max_256.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.count_mismatch.json
|
||||
```
|
||||
|
||||
Les tests HTTP utilisent uniquement un serveur loopback local.
|
||||
|
||||
## Couverture de tests ajoutée
|
||||
|
||||
Neuf tests unitaires HTTP supplémentaires couvrent :
|
||||
|
||||
- paramètres exacts de `getRecentPrioritizationFees` et ordre serveur ;
|
||||
- omission du paramètre optionnel ;
|
||||
- acceptation de 128 adresses et rejet local de 129 ;
|
||||
- pagination/config complète de `getSignaturesForAddress` avec `limit = 1000` ;
|
||||
- rejet local de `limit = 0` et `limit = 1001` ;
|
||||
- propagation d'une erreur JSON-RPC applicative ;
|
||||
- `getSignatureStatuses` avec `searchTransactionHistory = true` et positions `null` ;
|
||||
- tableau vide + config vide omise ;
|
||||
- borne 256 acceptée, 257 rejetée et longueur de réponse incohérente rejetée.
|
||||
|
||||
Un test public compile les trois nouveaux wrappers depuis la crate root.
|
||||
|
||||
Une nouvelle canarie release fige le sous-ensemble Read exécuté cumulé de `pre.003 + pre.004` :
|
||||
|
||||
```text
|
||||
getFeeForMessage
|
||||
getLatestBlockhash
|
||||
getRecentPrioritizationFees
|
||||
getSignaturesForAddress
|
||||
getSignatureStatuses
|
||||
getTransactionCount
|
||||
isBlockhashValid
|
||||
```
|
||||
|
||||
Chaque descriptor reste :
|
||||
|
||||
```text
|
||||
Transactions / V0_2_3 / Read / RetrySafe
|
||||
```
|
||||
|
||||
La canarie confirme aussi que les quatre méthodes différées restent enregistrées dans `V0_2_3` sans les compter comme surface typée achevée.
|
||||
|
||||
Après application, la cible Transport attendue devient :
|
||||
|
||||
```text
|
||||
161 unit tests
|
||||
16 public API tests
|
||||
10 release completeness tests
|
||||
```
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_prioritization_fees.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signatures_for_address.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signatures_for_address.error.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.empty.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.max_256.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/get_signature_statuses.count_mismatch.json
|
||||
deltas/0.2.3/pre.004.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
```
|
||||
|
||||
Restent inchangés dans cette tranche :
|
||||
|
||||
```text
|
||||
CHANGELOG.md
|
||||
ROADMAP.md
|
||||
docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md
|
||||
crates/ksp-onchain-transport-lib/Cargo.toml
|
||||
crates/ksp-onchain-transport-lib/src/executor.rs
|
||||
crates/ksp-onchain-transport-lib/src/resilience.rs
|
||||
crates/ksp-onchain-transport-lib/src/rpc_method.rs
|
||||
```
|
||||
|
||||
## Contrôles statiques effectués dans l'environnement d'échange
|
||||
|
||||
- les sept nouvelles fixtures sont du JSON valide ;
|
||||
- aucune dépendance n'est ajoutée ;
|
||||
- aucun `reqwest`, `tracing`, retry local ou client parallèle n'est introduit par le diff ;
|
||||
- aucun `#[allow(dead_code)]` n'est introduit ;
|
||||
- les helpers des tranches futures restent `#[cfg(test)]` ;
|
||||
- aucune ligne Rust modifiée ne dépasse 160 colonnes ;
|
||||
- `CHANGELOG.md`, `ROADMAP.md`, le plan `010`, `executor.rs`, `resilience.rs` et `rpc_method.rs` restent identiques à `pre.003`.
|
||||
|
||||
`cargo`, `rustc` et `rustfmt` ne sont pas installés dans l'environnement d'échange. Aucune validation Cargo de `pre.004` n'est donc déclarée
|
||||
réussie ici.
|
||||
|
||||
## Validation opérateur requise
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-onchain-transport-lib
|
||||
```
|
||||
|
||||
La validation doit notamment confirmer :
|
||||
|
||||
```text
|
||||
0 warning check/clippy
|
||||
161 unit tests
|
||||
16 public API tests
|
||||
10 release completeness tests
|
||||
```
|
||||
|
||||
Le smoke Devnet reste opt-in et n'est pas requis pour cette tranche locale déterministe.
|
||||
|
||||
## Hors scope maintenu
|
||||
|
||||
`pre.004` ne traite pas :
|
||||
|
||||
- `getTransaction` moderne/legacy ;
|
||||
- `requestAirdrop` ;
|
||||
- `sendTransaction` ;
|
||||
- la preuve end-to-end no-resend des writes ;
|
||||
- `simulateTransaction` ;
|
||||
- les 15 méthodes `0.2.4` Blocks/Economics ;
|
||||
- un nouveau smoke cross-crates.
|
||||
|
||||
La prochaine tranche reste `pre.005` : `getTransaction` moderne, compatibilité legacy et wire transaction/meta/version.
|
||||
Reference in New Issue
Block a user