v0.2.4-pre.006

This commit is contained in:
2026-08-18 21:21:02 +02:00
parent d3cc7c93f0
commit 7a889dd466
22 changed files with 949 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
// version: 4
// version: 5
#[test]
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
@@ -535,3 +535,259 @@ async fn typed_get_block_production_rejects_invalid_wire_identity_without_echoin
assert!(!error.to_string().contains("not-a-pubkey"));
handle.join().expect("fixture server must join");
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_modern_full_config_preserves_rich_wire_and_simd_fields() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.full.success.json"));
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config))
.await
.expect("modern getBlock fixture must succeed")
.expect("modern getBlock fixture must contain a block");
assert_eq!(block.parent_slot(), 430_000_122);
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
let transactions = block.transactions().value().expect("full block transactions must be present");
assert_eq!(transactions.len(), 2);
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
let first_meta = transactions[0].meta().value().expect("full transaction metadata must be present");
assert_eq!(first_meta["rewards"][0]["commissionBps"], serde_json::json!(1234));
let rewards = block.rewards().value().expect("block rewards must be present");
assert_eq!(rewards[0].commission(), std::option::Option::None);
assert_eq!(rewards[0].commission_bps().value(), std::option::Option::Some(&1_234));
assert!(rewards[1].commission_bps().is_omitted());
let request = handle.join().expect("fixture server must join");
let body = request_body(request.as_str());
assert_eq!(body["method"], serde_json::json!("getBlock"));
assert_eq!(
body["params"],
serde_json::json!([430000123,{"commitment":"finalized","encoding":"jsonParsed","transactionDetails":"full","maxSupportedTransactionVersion":1,"rewards":true}])
);
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_absent_and_explicit_empty_modern_config() {
let role = crate::HttpRoleName::new("default");
for (config, expected_params) in [
(std::option::Option::None, serde_json::json!([430000123])),
(std::option::Option::Some(crate::SolanaGetBlockConfig::default()), serde_json::json!([430000123, {}])),
] {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.null.json"));
let pool = pool_for_url(url.as_str());
let block = pool.get_block(&role, 430_000_123, config.as_ref()).await.expect("nullable getBlock request must succeed");
assert!(block.is_none());
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], expected_params);
}
}
#[allow(deprecated)]
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_legacy_covers_all_bare_encoding_labels_and_response_shapes() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionEncoding::Binary, "binary", include_str!("../fixtures/http/get_block.binary.success.json")),
(crate::SolanaTransactionEncoding::Base58, "base58", include_str!("../fixtures/http/get_block.base58.success.json")),
(crate::SolanaTransactionEncoding::Base64, "base64", include_str!("../fixtures/http/get_block.base64.success.json")),
(crate::SolanaTransactionEncoding::Json, "json", include_str!("../fixtures/http/get_block.json.success.json")),
(crate::SolanaTransactionEncoding::JsonParsed, "jsonParsed", include_str!("../fixtures/http/get_block.json_parsed.success.json")),
];
for (encoding, wire_label, response) in cases {
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block_legacy(&role, 430_000_123, encoding)
.await
.expect("legacy getBlock encoding must succeed")
.expect("legacy getBlock encoding must return a block");
let transactions = block.transactions().value().expect("legacy full response must contain transactions");
assert_eq!(transactions.len(), 1);
match encoding {
crate::SolanaTransactionEncoding::Binary => assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::LegacyBinary(_))),
crate::SolanaTransactionEncoding::Base58 => assert!(matches!(
transactions[0].transaction(),
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base58, .. }
)),
crate::SolanaTransactionEncoding::Base64 => assert!(matches!(
transactions[0].transaction(),
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. }
)),
crate::SolanaTransactionEncoding::Json | crate::SolanaTransactionEncoding::JsonParsed => {
assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::Json(_)));
},
}
let request = handle.join().expect("fixture server must join");
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000123, wire_label]));
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_modern_covers_all_agave_v4_2_1_encoding_labels() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionEncoding::Binary, "binary", include_str!("../fixtures/http/get_block.binary.success.json")),
(crate::SolanaTransactionEncoding::Base58, "base58", include_str!("../fixtures/http/get_block.base58.success.json")),
(crate::SolanaTransactionEncoding::Base64, "base64", include_str!("../fixtures/http/get_block.base64.success.json")),
(crate::SolanaTransactionEncoding::Json, "json", include_str!("../fixtures/http/get_block.json.success.json")),
(crate::SolanaTransactionEncoding::JsonParsed, "jsonParsed", include_str!("../fixtures/http/get_block.json_parsed.success.json")),
];
for (encoding, wire_label, response) in cases {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(encoding),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(1),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
pool.get_block(&role, 430_000_123, std::option::Option::Some(&config))
.await
.expect("modern getBlock encoding must succeed")
.expect("modern getBlock encoding must return a block");
let request = handle.join().expect("fixture server must join");
assert_eq!(
request_body(request.as_str())["params"],
serde_json::json!([430000123,{"commitment":"confirmed","encoding":wire_label,"transactionDetails":"full","maxSupportedTransactionVersion":1,"rewards":true}])
);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_covers_full_signatures_none_and_accounts_transaction_details() {
let role = crate::HttpRoleName::new("default");
let cases = [
(crate::SolanaTransactionDetails::Full, "full", include_str!("../fixtures/http/get_block.full_no_rewards.success.json")),
(crate::SolanaTransactionDetails::Signatures, "signatures", include_str!("../fixtures/http/get_block.signatures.success.json")),
(crate::SolanaTransactionDetails::None, "none", include_str!("../fixtures/http/get_block.none.success.json")),
(crate::SolanaTransactionDetails::Accounts, "accounts", include_str!("../fixtures/http/get_block.accounts.success.json")),
];
for (details, wire_label, response) in cases {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(details),
std::option::Option::Some(1),
std::option::Option::Some(false),
);
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&role, 430_000_123, std::option::Option::Some(&config))
.await
.expect("transactionDetails getBlock fixture must succeed")
.expect("transactionDetails getBlock fixture must return a block");
match details {
crate::SolanaTransactionDetails::Full => {
let transactions = block.transactions().value().expect("full transaction list must be present");
assert_eq!(transactions.len(), 1);
assert!(block.signatures().is_omitted());
assert_eq!(transactions[0].meta().value().expect("full transaction metadata must be present")["rewards"], serde_json::Value::Null);
},
crate::SolanaTransactionDetails::Signatures => {
assert!(block.transactions().is_omitted());
assert_eq!(block.signatures().value().expect("signatures must be present"), &std::vec!["sig-a".to_owned(), "sig-b".to_owned()]);
},
crate::SolanaTransactionDetails::None => {
assert!(block.transactions().is_omitted());
assert!(block.signatures().is_omitted());
},
crate::SolanaTransactionDetails::Accounts => {
let transactions = block.transactions().value().expect("accounts transaction list must be present");
assert_eq!(transactions.len(), 1);
assert!(matches!(transactions[0].transaction(), crate::SolanaEncodedTransaction::Json(_)));
if let crate::SolanaEncodedTransaction::Json(transaction) = transactions[0].transaction() {
assert_eq!(transaction["accountKeys"][0]["pubkey"], serde_json::json!("11111111111111111111111111111111"));
}
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
},
}
assert!(block.rewards().is_omitted());
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
let request = handle.join().expect("fixture server must join");
assert_eq!(
request_body(request.as_str())["params"],
serde_json::json!([430000123,{"commitment":"confirmed","encoding":"jsonParsed","transactionDetails":wire_label,"maxSupportedTransactionVersion":1,"rewards":false}])
);
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_null_and_omitted_top_level_wire_states() {
let role = crate::HttpRoleName::new("default");
for (response, expect_null) in [
(include_str!("../fixtures/http/get_block.omitted_fields.success.json"), false),
(include_str!("../fixtures/http/get_block.null_fields.success.json"), true),
] {
let (url, handle) = serve_once(response);
let pool = pool_for_url(url.as_str());
let block = pool
.get_block(&role, 430_000_123, std::option::Option::None)
.await
.expect("wire-state getBlock fixture must succeed")
.expect("wire-state getBlock fixture must return a block");
if expect_null {
assert!(block.transactions().is_null());
assert!(block.signatures().is_null());
assert!(block.rewards().is_null());
assert!(block.num_reward_partitions().is_null());
} else {
assert!(block.transactions().is_omitted());
assert!(block.signatures().is_omitted());
assert!(block.rewards().is_omitted());
assert!(block.num_reward_partitions().is_omitted());
}
handle.join().expect("fixture server must join");
}
}
#[tokio::test(flavor = "current_thread")]
async fn typed_get_block_preserves_unsupported_version_rpc_error() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(crate::SolanaTransactionEncoding::Base64),
std::option::Option::Some(crate::SolanaTransactionDetails::Full),
std::option::Option::Some(0),
std::option::Option::Some(true),
);
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.error_unsupported_version.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config)).await;
let error = result.expect_err("unsupported transaction version 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_block_preserves_block_not_available_rpc_error() {
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block.error_block_not_available.json"));
let pool = pool_for_url(url.as_str());
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::None).await;
let error = result.expect_err("unavailable block 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_block_rejects_processed_commitment_before_io() {
let config = crate::SolanaGetBlockConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Processed),
std::option::Option::Some(crate::SolanaTransactionEncoding::Json),
std::option::Option::None,
std::option::Option::None,
std::option::Option::None,
);
let pool = pool_for_url("http://127.0.0.1:9");
let result = pool.get_block(&crate::HttpRoleName::new("default"), 430_000_123, std::option::Option::Some(&config)).await;
let error = result.expect_err("processed getBlock commitment must reject before I/O");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
assert_eq!(error.context()[1].key(), "commitment");
assert_eq!(error.context()[1].value(), "processed");
}