v0.2.4-pre.006
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-accounts"],"accountKeys":[{"pubkey":"11111111111111111111111111111111","writable":true,"signer":true,"source":"transaction"}]},"meta":{"err":null,"status":{"Ok":null},"fee":5000,"preBalances":[10],"postBalances":[5]},"version":1}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["3BxsFixtureBase58","base58"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":"3BxsFixtureLegacyBinary","meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32004,"message":"Block not available for slot 430000123"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32015,"message":"Transaction version (1) is not supported by the requesting client. Please try the request again with maxSupportedTransactionVersion 1"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-v1"],"message":{"accountKeys":[],"recentBlockhash":"recent-v1","instructions":[],"transactionConfig":{"priorityFee":1,"computeUnitLimit":200000}}},"meta":{"err":null,"fee":5000,"status":{"Ok":null},"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":1,"postBalance":2,"rewardType":"Fee","commission":null,"commissionBps":1234}]},"version":1},{"transaction":["AQID","base64"],"meta":null}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-full-no-rewards"],"message":{"accountKeys":[],"recentBlockhash":"recent-full-no-rewards","instructions":[]}},"meta":{"err":null,"fee":5000,"rewards":null},"version":1}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-json"],"message":{"accountKeys":[],"recentBlockhash":"recent-json","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-parsed"],"message":{"accountKeys":[],"recentBlockhash":"recent-parsed","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":null,"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-b","blockhash":"block-b","parentSlot":2,"transactions":null,"signatures":null,"rewards":null,"numRewardPartitions":null,"blockTime":null,"blockHeight":null},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-a","blockhash":"block-a","parentSlot":1,"blockTime":null,"blockHeight":null},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"signatures":["sig-a","sig-b"]},"id":1}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
/// Transaction detail level accepted by modern `getBlock` requests.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
@@ -93,7 +93,6 @@ impl SolanaGetBlockConfig {
|
||||
|
||||
/// Serializes this config to the exact Solana JSON-RPC 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(commitment) = self.commitment {
|
||||
@@ -354,7 +353,6 @@ impl SolanaBlockReward {
|
||||
return &self.commission_bps;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockReward>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -403,7 +401,6 @@ impl SolanaBlockTransaction {
|
||||
return &self.version;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockTransaction>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -493,7 +490,6 @@ impl SolanaConfirmedBlock {
|
||||
}
|
||||
|
||||
/// Decodes a confirmed block from its 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::<WireConfirmedBlock>(method, value);
|
||||
let wire = match decoded {
|
||||
@@ -582,6 +578,74 @@ impl SolanaPerformanceSample {
|
||||
}
|
||||
|
||||
impl crate::HttpTransportPool {
|
||||
/// Executes the current object-form `getBlock` request through the common KSP HTTP transport path.
|
||||
///
|
||||
/// `None` omits the optional second parameter. `Some(config)` sends the modern object form exactly, including an empty `{}` when the caller
|
||||
/// explicitly supplies an empty modern configuration. The runtime requires an explicitly supplied commitment to be at least `confirmed`.
|
||||
pub async fn get_block(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
slot: u64,
|
||||
config: std::option::Option<&crate::SolanaGetBlockConfig>,
|
||||
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& config.commitment() == std::option::Option::Some(crate::SolanaCommitment::Processed)
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
|
||||
"getBlock commitment must be confirmed or finalized when explicitly provided",
|
||||
)
|
||||
.with_context("rpc_method", "getBlock")
|
||||
.with_context("commitment", "processed"),
|
||||
);
|
||||
}
|
||||
let mut params = std::vec![serde_json::json!(slot)];
|
||||
if let std::option::Option::Some(config) = config {
|
||||
params.push((*config).to_json_value());
|
||||
}
|
||||
return self.execute_get_block(role, params).await;
|
||||
}
|
||||
|
||||
/// Executes the deprecated bare-encoding `getBlock` request form retained by Solana RPC for backwards compatibility.
|
||||
#[deprecated(note = "use HttpTransportPool::get_block with SolanaGetBlockConfig; the bare encoding request form is deprecated")]
|
||||
pub async fn get_block_legacy(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
slot: u64,
|
||||
encoding: crate::SolanaTransactionEncoding,
|
||||
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
rpc_method = "getBlock",
|
||||
request_form = "bare_encoding",
|
||||
encoding = encoding.as_str(),
|
||||
"deprecated Solana HTTP RPC request form used"
|
||||
);
|
||||
let params = std::vec![serde_json::json!(slot), serde_json::Value::String(encoding.as_str().to_owned())];
|
||||
return self.execute_get_block(role, params).await;
|
||||
}
|
||||
|
||||
async fn execute_get_block(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
params: std::vec::Vec<serde_json::Value>,
|
||||
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedBlock>> {
|
||||
let value = self.execute_blocks_rpc("getBlock", role, params).await;
|
||||
let value = match value {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if value.is_null() {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
let block = crate::SolanaConfirmedBlock::decode_wire("getBlock", value);
|
||||
return match block {
|
||||
std::result::Result::Ok(block) => std::result::Result::Ok(std::option::Option::Some(block)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `getBlockCommitment` through the common KSP HTTP transport path.
|
||||
pub async fn get_block_commitment(&self, role: &crate::HttpRoleName, slot: u64) -> ksp_core_lib::Result<crate::SolanaBlockCommitment> {
|
||||
let value = self.execute_blocks_rpc("getBlockCommitment", role, std::vec![serde_json::json!(slot)]).await;
|
||||
@@ -857,7 +921,6 @@ fn blocks_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpR
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_transaction_version(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<serde_json::Value>,
|
||||
@@ -875,7 +938,6 @@ fn decode_block_transaction_version(
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_transactions(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
@@ -896,7 +958,6 @@ fn decode_block_transactions(
|
||||
return std::result::Result::Ok(crate::SolanaWireField::Value(transactions));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_block_rewards(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
@@ -944,7 +1005,6 @@ struct WireBlockProductionRpcResponse {
|
||||
value: serde_json::Value,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockReward {
|
||||
@@ -959,7 +1019,6 @@ struct WireBlockReward {
|
||||
commission_bps: crate::SolanaWireField<u16>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockTransaction {
|
||||
@@ -970,7 +1029,6 @@ struct WireBlockTransaction {
|
||||
version: crate::SolanaWireField<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireConfirmedBlock {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 19
|
||||
// version: 20
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -416,3 +416,22 @@ fn public_v0_2_4_pre_005_block_production_wrapper_is_available_from_crate_root()
|
||||
assert_eq!(config.range().expect("public range must exist").last_slot(), std::option::Option::Some(20));
|
||||
let _method = ksp_onchain_transport_lib::HttpTransportPool::get_block_production;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[test]
|
||||
fn public_v0_2_4_pre_006_get_block_complete_request_forms_are_available_from_crate_root() {
|
||||
let _get_block = ksp_onchain_transport_lib::HttpTransportPool::get_block;
|
||||
let _get_block_legacy = ksp_onchain_transport_lib::HttpTransportPool::get_block_legacy;
|
||||
let config = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Accounts),
|
||||
std::option::Option::Some(1),
|
||||
std::option::Option::Some(true),
|
||||
);
|
||||
assert_eq!(config.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
|
||||
assert_eq!(config.encoding(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed));
|
||||
assert_eq!(config.transaction_details(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Accounts));
|
||||
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(1));
|
||||
assert_eq!(config.rewards(), std::option::Option::Some(true));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 17
|
||||
// version: 18
|
||||
|
||||
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
||||
|
||||
@@ -530,3 +530,36 @@ fn release_v0_2_4_pre_005_block_production_subset_is_exact_and_retry_safe() {
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_4_pre_006_get_block_completes_all_ten_block_wrappers_with_legacy_marker() {
|
||||
let expected = std::vec![
|
||||
"getBlock",
|
||||
"getBlockCommitment",
|
||||
"getBlockHeight",
|
||||
"getBlockProduction",
|
||||
"getBlocks",
|
||||
"getBlocksWithLimit",
|
||||
"getBlockTime",
|
||||
"getFirstAvailableBlock",
|
||||
"getRecentPerformanceSamples",
|
||||
"minimumLedgerSlot",
|
||||
];
|
||||
let mut actual = std::vec::Vec::new();
|
||||
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
|
||||
if descriptor.coverage_release() == ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4
|
||||
&& descriptor.category() == ksp_onchain_transport_lib::HttpRpcCategory::Blocks
|
||||
{
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||
actual.push(descriptor.method());
|
||||
}
|
||||
}
|
||||
actual.sort_unstable();
|
||||
let mut expected = expected;
|
||||
expected.sort_unstable();
|
||||
assert_eq!(actual, expected);
|
||||
assert_eq!(actual.len(), 10);
|
||||
let get_block = ksp_onchain_transport_lib::find_http_rpc_method("getBlock").expect("getBlock descriptor must exist");
|
||||
assert!(get_block.request_form_status().has_deprecated_legacy());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user