v0.2.3-pre.004
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user