v0.2.3-pre.004

This commit is contained in:
2026-08-18 12:14:59 +02:00
parent 2a7f3e8f40
commit bd26d4f636
14 changed files with 801 additions and 21 deletions

View File

@@ -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");
}