v0.2.4-pre.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 134
|
# version: 135
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.4-pre.3"
|
version = "0.2.4-pre.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":[],"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":[430000100,430000103,430000109],"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":[],"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":[430000200,430000201,430000205],"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":[{"slot":430000123,"numTransactions":250000,"numNonVoteTransactions":175000,"numSlots":120,"samplePeriodSecs":60},{"slot":430000000,"numTransactions":200000,"numSlots":110,"samplePeriodSecs":60}],"id":1}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
/// Transaction detail level accepted by modern `getBlock` requests.
|
/// Transaction detail level accepted by modern `getBlock` requests.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||||
@@ -570,7 +570,6 @@ impl SolanaPerformanceSample {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes one performance sample from its Solana JSON wire shape.
|
/// Decodes one performance sample from its Solana JSON wire shape.
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||||
let decoded = crate::decode_wire_json::<WirePerformanceSample>(method, value);
|
let decoded = crate::decode_wire_json::<WirePerformanceSample>(method, value);
|
||||||
return match decoded {
|
return match decoded {
|
||||||
@@ -622,6 +621,93 @@ impl crate::HttpTransportPool {
|
|||||||
return self.get_blocks_u64("minimumLedgerSlot", role, std::vec::Vec::new()).await;
|
return self.get_blocks_u64("minimumLedgerSlot", role, std::vec::Vec::new()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes typed `getBlocks` while preserving all four supported parameter overloads.
|
||||||
|
pub async fn get_blocks(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
start_slot: u64,
|
||||||
|
end_slot: std::option::Option<u64>,
|
||||||
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
||||||
|
let validated = validate_blocks_context_commitment("getBlocks", config);
|
||||||
|
if let std::result::Result::Err(error) = validated {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(end_slot) = end_slot
|
||||||
|
&& end_slot >= start_slot
|
||||||
|
&& end_slot - start_slot > MAX_GET_BLOCKS_RANGE
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "getBlocks range must not exceed 500000 slots")
|
||||||
|
.with_context("rpc_method", "getBlocks")
|
||||||
|
.with_context("start_slot", start_slot.to_string())
|
||||||
|
.with_context("end_slot", end_slot.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut params = std::vec![serde_json::json!(start_slot)];
|
||||||
|
if let std::option::Option::Some(end_slot) = end_slot {
|
||||||
|
params.push(serde_json::json!(end_slot));
|
||||||
|
}
|
||||||
|
push_blocks_explicit_context_config(&mut params, config);
|
||||||
|
return self.get_blocks_u64_list("getBlocks", role, params).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes typed `getBlocksWithLimit` with the runtime-supported inclusive upper bound.
|
||||||
|
pub async fn get_blocks_with_limit(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
start_slot: u64,
|
||||||
|
limit: u64,
|
||||||
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
||||||
|
let validated = validate_blocks_context_commitment("getBlocksWithLimit", config);
|
||||||
|
if let std::result::Result::Err(error) = validated {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
if limit > MAX_GET_BLOCKS_RANGE {
|
||||||
|
return invalid_blocks_limit("getBlocksWithLimit", "getBlocksWithLimit limit must not exceed 500000", limit);
|
||||||
|
}
|
||||||
|
let mut params = std::vec![serde_json::json!(start_slot), serde_json::json!(limit)];
|
||||||
|
push_blocks_explicit_context_config(&mut params, config);
|
||||||
|
return self.get_blocks_u64_list("getBlocksWithLimit", role, params).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes typed `getRecentPerformanceSamples`, preserving runtime order and older sample shapes.
|
||||||
|
pub async fn get_recent_performance_samples(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
limit: std::option::Option<u64>,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPerformanceSample>> {
|
||||||
|
if let std::option::Option::Some(limit) = limit
|
||||||
|
&& limit > MAX_GET_RECENT_PERFORMANCE_SAMPLES
|
||||||
|
{
|
||||||
|
return invalid_blocks_limit("getRecentPerformanceSamples", "getRecentPerformanceSamples limit must not exceed 720", limit);
|
||||||
|
}
|
||||||
|
let mut params = std::vec::Vec::new();
|
||||||
|
if let std::option::Option::Some(limit) = limit {
|
||||||
|
params.push(serde_json::json!(limit));
|
||||||
|
}
|
||||||
|
let value = self.execute_blocks_rpc("getRecentPerformanceSamples", 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_performance_samples("getRecentPerformanceSamples", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_blocks_u64_list(
|
||||||
|
&self,
|
||||||
|
method_name: &'static str,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
params: std::vec::Vec<serde_json::Value>,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<u64>> {
|
||||||
|
let value = self.execute_blocks_rpc(method_name, role, params).await;
|
||||||
|
return match value {
|
||||||
|
std::result::Result::Ok(value) => crate::decode_wire_json::<std::vec::Vec<u64>>(method_name, value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_blocks_u64(
|
async fn get_blocks_u64(
|
||||||
&self,
|
&self,
|
||||||
method_name: &'static str,
|
method_name: &'static str,
|
||||||
@@ -650,6 +736,57 @@ impl crate::HttpTransportPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_GET_BLOCKS_RANGE: u64 = 500_000;
|
||||||
|
const MAX_GET_RECENT_PERFORMANCE_SAMPLES: u64 = 720;
|
||||||
|
|
||||||
|
fn validate_blocks_context_commitment(method: &'static str, config: std::option::Option<&crate::SolanaContextConfig>) -> ksp_core_lib::Result<()> {
|
||||||
|
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,
|
||||||
|
"block-range commitment must be confirmed or finalized when explicitly provided",
|
||||||
|
)
|
||||||
|
.with_context("rpc_method", method)
|
||||||
|
.with_context("commitment", "processed"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_blocks_explicit_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
||||||
|
if let std::option::Option::Some(config) = config {
|
||||||
|
params.push((*config).to_json_value());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_blocks_limit<T>(method: &'static str, message: &'static str, limit: u64) -> 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("limit", limit.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_performance_samples(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPerformanceSample>> {
|
||||||
|
let values = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(method, value);
|
||||||
|
let values = match values {
|
||||||
|
std::result::Result::Ok(values) => values,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let mut samples = std::vec::Vec::with_capacity(values.len());
|
||||||
|
for value in values {
|
||||||
|
let sample = crate::SolanaPerformanceSample::decode_wire(method, value);
|
||||||
|
match sample {
|
||||||
|
std::result::Result::Ok(sample) => samples.push(sample),
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(samples);
|
||||||
|
}
|
||||||
|
|
||||||
fn push_blocks_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
fn push_blocks_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
||||||
if let std::option::Option::Some(config) = config
|
if let std::option::Option::Some(config) = config
|
||||||
&& (config.commitment().is_some() || config.min_context_slot().is_some())
|
&& (config.commitment().is_some() || config.min_context_slot().is_some())
|
||||||
@@ -802,7 +939,6 @@ struct WireConfirmedBlock {
|
|||||||
block_height: std::option::Option<u64>,
|
block_height: std::option::Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WirePerformanceSample {
|
struct WirePerformanceSample {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
// version: 17
|
// version: 18
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -395,3 +395,10 @@ fn public_v0_2_4_pre_003_simple_block_wrappers_are_available_from_crate_root() {
|
|||||||
let _get_first_available_block = ksp_onchain_transport_lib::HttpTransportPool::get_first_available_block;
|
let _get_first_available_block = ksp_onchain_transport_lib::HttpTransportPool::get_first_available_block;
|
||||||
let _minimum_ledger_slot = ksp_onchain_transport_lib::HttpTransportPool::minimum_ledger_slot;
|
let _minimum_ledger_slot = ksp_onchain_transport_lib::HttpTransportPool::minimum_ledger_slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_2_4_pre_004_range_and_performance_wrappers_are_available_from_crate_root() {
|
||||||
|
let _get_blocks = ksp_onchain_transport_lib::HttpTransportPool::get_blocks;
|
||||||
|
let _get_blocks_with_limit = ksp_onchain_transport_lib::HttpTransportPool::get_blocks_with_limit;
|
||||||
|
let _get_recent_performance_samples = ksp_onchain_transport_lib::HttpTransportPool::get_recent_performance_samples;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
// version: 15
|
// version: 16
|
||||||
|
|
||||||
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
||||||
|
|
||||||
@@ -496,3 +496,28 @@ fn release_v0_2_4_pre_003_simple_blocks_subset_is_exact_and_retry_safe() {
|
|||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
assert_eq!(actual.len(), 5);
|
assert_eq!(actual.len(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_4_pre_004_range_performance_subset_is_exact_and_retry_safe() {
|
||||||
|
let mut expected = std::vec!["getBlocks", "getBlocksWithLimit", "getRecentPerformanceSamples"];
|
||||||
|
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
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match descriptor.method() {
|
||||||
|
"getBlocks" | "getBlocksWithLimit" | "getRecentPerformanceSamples" => {
|
||||||
|
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();
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert_eq!(actual.len(), 3);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
|
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
|
||||||
@@ -311,3 +311,145 @@ async fn typed_minimum_ledger_slot_has_no_params() {
|
|||||||
assert_eq!(body["method"], serde_json::json!("minimumLedgerSlot"));
|
assert_eq!(body["method"], serde_json::json!("minimumLedgerSlot"));
|
||||||
assert_eq!(body["params"], serde_json::json!([]));
|
assert_eq!(body["params"], serde_json::json!([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_blocks_covers_all_four_overloads_and_preserves_server_order() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(429_999_999));
|
||||||
|
let empty_config = crate::SolanaContextConfig::default();
|
||||||
|
let cases = [
|
||||||
|
(std::option::Option::None, std::option::Option::None, serde_json::json!([430000100])),
|
||||||
|
(std::option::Option::Some(430_000_109), std::option::Option::None, serde_json::json!([430000100, 430000109])),
|
||||||
|
(
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(&config),
|
||||||
|
serde_json::json!([430000100,{"commitment":"confirmed","minContextSlot":429999999}]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
std::option::Option::Some(430_000_109),
|
||||||
|
std::option::Option::Some(&config),
|
||||||
|
serde_json::json!([430000100,430000109,{"commitment":"confirmed","minContextSlot":429999999}]),
|
||||||
|
),
|
||||||
|
(std::option::Option::None, std::option::Option::Some(&empty_config), serde_json::json!([430000100, {}])),
|
||||||
|
];
|
||||||
|
for (end_slot, config, expected_params) in cases {
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let blocks = pool.get_blocks(&role, 430_000_100, end_slot, config).await.expect("getBlocks overload fixture must succeed");
|
||||||
|
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = request_body(request.as_str());
|
||||||
|
assert_eq!(body["method"], serde_json::json!("getBlocks"));
|
||||||
|
assert_eq!(body["params"], expected_params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversized_before_io() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.empty.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let blocks = pool
|
||||||
|
.get_blocks(&role, 430_000_100, std::option::Option::Some(430_000_099), std::option::Option::None)
|
||||||
|
.await
|
||||||
|
.expect("reversed getBlocks range must remain a valid RPC request");
|
||||||
|
assert!(blocks.is_empty());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000100, 430000099]));
|
||||||
|
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let blocks = pool
|
||||||
|
.get_blocks(&role, 10, std::option::Option::Some(500_010), std::option::Option::None)
|
||||||
|
.await
|
||||||
|
.expect("500000-slot getBlocks range must remain valid");
|
||||||
|
assert_eq!(blocks, std::vec![430_000_100, 430_000_103, 430_000_109]);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([10, 500010]));
|
||||||
|
|
||||||
|
let pool = pool_for_url("http://127.0.0.1:9");
|
||||||
|
let result = pool.get_blocks(&role, 10, std::option::Option::Some(500_011), std::option::Option::None).await;
|
||||||
|
let error = result.expect_err("range above 500000 must reject before I/O");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
|
||||||
|
assert_eq!(error.context()[1].key(), "start_slot");
|
||||||
|
assert_eq!(error.context()[1].value(), "10");
|
||||||
|
assert_eq!(error.context()[2].key(), "end_slot");
|
||||||
|
assert_eq!(error.context()[2].value(), "500011");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_block_range_wrappers_reject_processed_commitment_before_io() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
let pool = pool_for_url("http://127.0.0.1:9");
|
||||||
|
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Processed), std::option::Option::None);
|
||||||
|
let get_blocks = pool.get_blocks(&role, 1, std::option::Option::None, std::option::Option::Some(&config)).await;
|
||||||
|
let error = get_blocks.expect_err("getBlocks processed 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");
|
||||||
|
|
||||||
|
let get_blocks_with_limit = pool.get_blocks_with_limit(&role, 1, 1, std::option::Option::Some(&config)).await;
|
||||||
|
let error = get_blocks_with_limit.expect_err("getBlocksWithLimit processed 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_maximum() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.empty.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let blocks = pool.get_blocks_with_limit(&role, 430_000_200, 0, std::option::Option::None).await.expect("zero getBlocksWithLimit limit must remain valid");
|
||||||
|
assert!(blocks.is_empty());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200, 0]));
|
||||||
|
|
||||||
|
let config = crate::SolanaContextConfig::new(std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(430_000_000));
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_blocks_with_limit.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let blocks = pool
|
||||||
|
.get_blocks_with_limit(&role, 430_000_200, 500_000, std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("maximum getBlocksWithLimit limit must remain valid");
|
||||||
|
assert_eq!(blocks, std::vec![430_000_200, 430_000_201, 430_000_205]);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([430000200,500000,{"commitment":"finalized","minContextSlot":430000000}]));
|
||||||
|
|
||||||
|
let pool = pool_for_url("http://127.0.0.1:9");
|
||||||
|
let result = pool.get_blocks_with_limit(&role, 1, 500_001, std::option::Option::None).await;
|
||||||
|
let error = result.expect_err("getBlocksWithLimit above 500000 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(), "500001");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_recent_performance_samples_preserves_default_explicit_limit_order_and_older_shape() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
for (limit, expected_params) in [(std::option::Option::None, serde_json::json!([])), (std::option::Option::Some(720), serde_json::json!([720]))] {
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_recent_performance_samples.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let samples = pool.get_recent_performance_samples(&role, limit).await.expect("performance sample fixture must succeed");
|
||||||
|
assert_eq!(samples.len(), 2);
|
||||||
|
assert_eq!(samples[0].slot(), 430_000_123);
|
||||||
|
assert_eq!(samples[0].num_transactions(), 250_000);
|
||||||
|
assert_eq!(samples[0].num_non_vote_transactions(), std::option::Option::Some(175_000));
|
||||||
|
assert_eq!(samples[1].slot(), 430_000_000);
|
||||||
|
assert_eq!(samples[1].num_non_vote_transactions(), std::option::Option::None);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = request_body(request.as_str());
|
||||||
|
assert_eq!(body["method"], serde_json::json!("getRecentPerformanceSamples"));
|
||||||
|
assert_eq!(body["params"], expected_params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_recent_performance_samples_rejects_above_720_before_io() {
|
||||||
|
let pool = pool_for_url("http://127.0.0.1:9");
|
||||||
|
let result = pool.get_recent_performance_samples(&crate::HttpRoleName::new("default"), std::option::Option::Some(721)).await;
|
||||||
|
let error = result.expect_err("performance sample limit above 720 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(), "721");
|
||||||
|
}
|
||||||
|
|||||||
423
deltas/0.2.4/pre.004.md
Normal file
423
deltas/0.2.4/pre.004.md
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
<!-- file: deltas/0.2.4/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.4-pre.004` — ranges Blocks et performance samples
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
Livraison précédente :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.003
|
||||||
|
workspace.package.version = "0.2.4-pre.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
Les validations locales fournies pour cette base sont propres :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -> terminé
|
||||||
|
cargo check --workspace -> terminé sans warning
|
||||||
|
cargo clippy --workspace --all-targets -> terminé sans warning
|
||||||
|
cargo test -p ksp-onchain-transport-lib -> 202 unit tests OK
|
||||||
|
21 public API tests OK
|
||||||
|
16 release-completeness tests OK
|
||||||
|
1 smoke Devnet ignoré comme prévu
|
||||||
|
0 échec
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plan canonique `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` passe de la version 2 à la version 3 dans cette livraison afin de corriger explicitement la config runtime stable de `getBlocks` / `getBlocksWithLimit` : Agave `v4.2.1` utilise `RpcContextConfig` avec `commitment` et `minContextSlot`.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Implémenter exactement la tranche `pre.004` prévue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getBlocks
|
||||||
|
getBlocksWithLimit
|
||||||
|
getRecentPerformanceSamples
|
||||||
|
```
|
||||||
|
|
||||||
|
Les trois méthodes sont déjà enregistrées dans la partition `V0_2_4 / Blocks` et classées `Read / RetrySafe`. Cette prerelease ne modifie donc pas le registre central.
|
||||||
|
|
||||||
|
Après cette tranche, huit des dix wrappers Blocks de `0.2.4` sont matérialisés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.003
|
||||||
|
getBlockCommitment
|
||||||
|
getBlockHeight
|
||||||
|
getBlockTime
|
||||||
|
getFirstAvailableBlock
|
||||||
|
minimumLedgerSlot
|
||||||
|
|
||||||
|
pre.004
|
||||||
|
getBlocks
|
||||||
|
getBlocksWithLimit
|
||||||
|
getRecentPerformanceSamples
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent volontairement différés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.005 getBlockProduction
|
||||||
|
pre.006 getBlock
|
||||||
|
```
|
||||||
|
|
||||||
|
Les cinq méthodes Economics restent également hors scope.
|
||||||
|
|
||||||
|
## Réaudit RPC de la tranche
|
||||||
|
|
||||||
|
Le contrat HTTP courant a été revérifié avant implémentation et reste conforme au plan `pre.001` :
|
||||||
|
|
||||||
|
- `getBlocks` accepte `start_slot`, un second paramètre qui peut être soit `end_slot`, soit un `RpcContextConfig`, puis éventuellement ce même type de config en troisième position ; la config stable contient `commitment` et `minContextSlot`, et la plage maximale est de `500_000` slots ;
|
||||||
|
- `getBlocksWithLimit` accepte `start_slot`, `limit` puis un `RpcContextConfig` optionnel `{commitment,minContextSlot}` ; `limit` ne doit pas dépasser `500_000` ;
|
||||||
|
- `getRecentPerformanceSamples` accepte un `limit` optionnel avec maximum `720`; l'absence du paramètre laisse le runtime appliquer son défaut ;
|
||||||
|
- les résultats `getBlocks` / `getBlocksWithLimit` restent des listes ordonnées de slots `u64` ;
|
||||||
|
- `getRecentPerformanceSamples` reste une liste dans l'ordre fourni par le serveur et expose `slot`, `numTransactions`, `numSlots`, `samplePeriodSecs`, avec `numNonVoteTransactions` conservé comme champ optionnel pour compatibilité avec les anciens wires.
|
||||||
|
|
||||||
|
Aucun nouvel overload ni nouvelle limite n'a été identifié. En revanche, le cross-audit source Agave a clarifié un champ stable que la page publique n'affiche pas : `minContextSlot` fait partie du `RpcContextConfig` accepté par `getBlocks` et `getBlocksWithLimit`. Le plan vivant est corrigé en conséquence sous sa version 3.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Nouvelle prerelease technique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.3 -> 0.2.4-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
|
||||||
|
|
||||||
|
## `getBlocks`
|
||||||
|
|
||||||
|
Signature publique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_blocks(
|
||||||
|
role,
|
||||||
|
start_slot,
|
||||||
|
Option<end_slot>,
|
||||||
|
Option<&SolanaContextConfig>,
|
||||||
|
) -> Result<Vec<u64>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le wrapper préserve les quatre formes retenues par `KSP-TRANSPORT-007` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[430000100]
|
||||||
|
[430000100, 430000109]
|
||||||
|
[430000100, {"commitment":"confirmed", "minContextSlot":429999999}]
|
||||||
|
[430000100, 430000109, {"commitment":"confirmed", "minContextSlot":429999999}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Un `Some(SolanaContextConfig::default())` reste un overload config explicite :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[430000100, {}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'est pas réécrit silencieusement en `[430000100]`.
|
||||||
|
|
||||||
|
Validation déterministe avant I/O :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment = processed -> rejet
|
||||||
|
end >= start et end - start > 500_000 -> rejet
|
||||||
|
```
|
||||||
|
|
||||||
|
La borne est inclusive :
|
||||||
|
|
||||||
|
```text
|
||||||
|
end - start = 500_000 -> valide
|
||||||
|
```
|
||||||
|
|
||||||
|
Le cas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
end < start
|
||||||
|
```
|
||||||
|
|
||||||
|
reste valide et est envoyé au runtime ; KSP accepte alors le tableau vide retourné sans fabriquer une erreur locale ni synthétiser une autre réponse.
|
||||||
|
|
||||||
|
Le résultat `Vec<u64>` est conservé dans l'ordre exact retourné par le serveur ; aucun tri ni remplissage des slots absents n'est effectué.
|
||||||
|
|
||||||
|
## `getBlocksWithLimit`
|
||||||
|
|
||||||
|
Signature publique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_blocks_with_limit(
|
||||||
|
role,
|
||||||
|
start_slot,
|
||||||
|
limit,
|
||||||
|
Option<&SolanaContextConfig>,
|
||||||
|
) -> Result<Vec<u64>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation déterministe avant I/O :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment = processed -> rejet
|
||||||
|
limit > 500_000 -> rejet
|
||||||
|
```
|
||||||
|
|
||||||
|
Les deux bornes importantes sont couvertes explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
limit = 0 -> valide
|
||||||
|
limit = 500_000 -> valide
|
||||||
|
limit = 500_001 -> rejet avant I/O
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune borne minimale artificielle `1` n'est ajoutée.
|
||||||
|
|
||||||
|
Comme pour `getBlocks`, l'ordre et les éventuels trous de slots de la réponse sont laissés au runtime et préservés par Transport.
|
||||||
|
|
||||||
|
## `getRecentPerformanceSamples`
|
||||||
|
|
||||||
|
Signature publique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_recent_performance_samples(
|
||||||
|
role,
|
||||||
|
Option<limit>,
|
||||||
|
) -> Result<Vec<SolanaPerformanceSample>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Wire sans limite explicite :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Wire avec maximum explicite :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[720]
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation déterministe :
|
||||||
|
|
||||||
|
```text
|
||||||
|
limit <= 720 -> valide
|
||||||
|
limit > 720 -> rejet avant I/O
|
||||||
|
```
|
||||||
|
|
||||||
|
Le wrapper n'introduit aucune borne minimale locale et ne remplace pas l'absence de limite par une valeur synthétique `720` dans la requête.
|
||||||
|
|
||||||
|
`SolanaPerformanceSample`, introduit en `pre.002`, est désormais consommé par un wrapper runtime. Son décodeur interne et `WirePerformanceSample` sortent donc de `#[cfg(test)]`.
|
||||||
|
|
||||||
|
Le décodage conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
slot
|
||||||
|
numTransactions
|
||||||
|
numNonVoteTransactions présent ou omis
|
||||||
|
numSlots
|
||||||
|
samplePeriodSecs
|
||||||
|
```
|
||||||
|
|
||||||
|
L'ordre retourné par le serveur est conservé sans tri local.
|
||||||
|
|
||||||
|
## Discipline `#[cfg(test)]`
|
||||||
|
|
||||||
|
La règle introduite par `pre.002-fix.001` reste appliquée strictement.
|
||||||
|
|
||||||
|
Nouveaux éléments devenus runtime en `pre.004` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaPerformanceSample::decode_wire
|
||||||
|
WirePerformanceSample
|
||||||
|
```
|
||||||
|
|
||||||
|
Éléments déjà runtime en `pre.003` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaBlockCommitment::decode_wire
|
||||||
|
WireBlockCommitment
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent test-only jusqu'à leur tranche :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaGetBlockConfig::{is_empty,to_json_value}
|
||||||
|
SolanaBlockProductionRange::to_json_value
|
||||||
|
SolanaBlockProductionConfig::{is_empty,to_json_value}
|
||||||
|
SolanaBlockProduction::decode_wire
|
||||||
|
SolanaBlockReward::decode_wire
|
||||||
|
SolanaBlockTransaction::decode_wire
|
||||||
|
SolanaConfirmedBlock::decode_wire
|
||||||
|
helpers privés transaction/reward de bloc
|
||||||
|
WireBlockProduction*
|
||||||
|
WireBlockReward
|
||||||
|
WireBlockTransaction
|
||||||
|
WireConfirmedBlock
|
||||||
|
helpers/wires Economics
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `#[allow(dead_code)]` n'est introduit.
|
||||||
|
|
||||||
|
## Fixtures HTTP ajoutées
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.empty.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.empty.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_performance_samples.success.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Les réponses de blocs utilisent volontairement des slots non continus afin de prouver que Transport ne synthétise pas les slots absents.
|
||||||
|
|
||||||
|
La fixture performance contient un échantillon courant avec `numNonVoteTransactions` et un échantillon compatible ancien wire sans ce champ.
|
||||||
|
|
||||||
|
## Tests unitaires ajoutés
|
||||||
|
|
||||||
|
`unit_tests/rpc_blocks.rs` ajoute six tests async :
|
||||||
|
|
||||||
|
```text
|
||||||
|
typed_get_blocks_covers_all_four_overloads_and_preserves_server_order
|
||||||
|
typed_get_blocks_allows_reversed_and_boundary_ranges_but_rejects_oversized_before_io
|
||||||
|
typed_block_range_wrappers_reject_processed_commitment_before_io
|
||||||
|
typed_get_blocks_with_limit_accepts_zero_and_maximum_and_rejects_above_maximum
|
||||||
|
typed_get_recent_performance_samples_preserves_default_explicit_limit_order_and_older_shape
|
||||||
|
typed_get_recent_performance_samples_rejects_above_720_before_io
|
||||||
|
```
|
||||||
|
|
||||||
|
Ils couvrent notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getBlocks [start]
|
||||||
|
getBlocks [start,end]
|
||||||
|
getBlocks [start,config]
|
||||||
|
getBlocks [start,end,config]
|
||||||
|
getBlocks [start,{}]
|
||||||
|
getBlocks end < start
|
||||||
|
getBlocks différence = 500_000
|
||||||
|
autorejet getBlocks différence = 500_001
|
||||||
|
commitment processed rejeté avant I/O
|
||||||
|
getBlocksWithLimit 0
|
||||||
|
getBlocksWithLimit 500_000
|
||||||
|
getBlocksWithLimit 500_001 rejeté avant I/O
|
||||||
|
getRecentPerformanceSamples sans limite
|
||||||
|
getRecentPerformanceSamples 720
|
||||||
|
getRecentPerformanceSamples 721 rejeté avant I/O
|
||||||
|
ordre des résultats conservé
|
||||||
|
ancien sample sans numNonVoteTransactions accepté
|
||||||
|
```
|
||||||
|
|
||||||
|
## Canaries d'intégration
|
||||||
|
|
||||||
|
`tests/public_api.rs` ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_v0_2_4_pre_004_range_and_performance_wrappers_are_available_from_crate_root
|
||||||
|
```
|
||||||
|
|
||||||
|
La canarie référence directement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_blocks
|
||||||
|
HttpTransportPool::get_blocks_with_limit
|
||||||
|
HttpTransportPool::get_recent_performance_samples
|
||||||
|
```
|
||||||
|
|
||||||
|
`tests/release_completeness.rs` ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
release_v0_2_4_pre_004_range_performance_subset_is_exact_and_retry_safe
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle verrouille le sous-ensemble exact :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getBlocks
|
||||||
|
getBlocksWithLimit
|
||||||
|
getRecentPerformanceSamples
|
||||||
|
```
|
||||||
|
|
||||||
|
et leur classification commune :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpRpcCoverageRelease::V0_2_4
|
||||||
|
HttpRpcCategory::Blocks
|
||||||
|
RpcOperationKind::Read
|
||||||
|
TransportRetryClass::RetrySafe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontières architecturales
|
||||||
|
|
||||||
|
Cette tranche ne modifie pas les frontières établies :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport -X-> Config
|
||||||
|
Transport -X-> Store
|
||||||
|
Transport -X-> Program
|
||||||
|
Transport -X-> tracing direct
|
||||||
|
```
|
||||||
|
|
||||||
|
Les wrappers passent exclusivement par `execute_standard_rpc` et la politique centrale de retry.
|
||||||
|
|
||||||
|
Aucun client RPC Solana haut niveau, aucun retry local, aucune lecture d'environnement/configuration et aucune dépendance externe nouvelle ne sont introduits.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks.empty.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_blocks_with_limit.empty.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_recent_performance_samples.success.json
|
||||||
|
deltas/0.2.4/pre.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fichier n'est supprimé.
|
||||||
|
|
||||||
|
## Validation à exécuter après application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Compteurs attendus si aucun test existant n'est ajouté/supprimé localement entre-temps :
|
||||||
|
|
||||||
|
```text
|
||||||
|
208 unit tests
|
||||||
|
22 public API tests
|
||||||
|
17 release-completeness tests
|
||||||
|
1 smoke Devnet ignoré comme prévu
|
||||||
|
0 échec
|
||||||
|
```
|
||||||
|
|
||||||
|
Le sandbox de génération ne possède pas le toolchain Rust ; aucune compilation locale n'est revendiquée dans ce delta.
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
La tranche suivante reste celle prévue par le plan :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.005
|
||||||
|
getBlockProduction
|
||||||
|
```
|
||||||
|
|
||||||
|
Elle devra activer uniquement les helpers runtime nécessaires à `SolanaBlockProductionConfig`, `SolanaBlockProductionRange` et `SolanaBlockProduction`, puis couvrir `identity`, `range`, le résultat contextualisé et la validation `lastSlot >= firstSlot`.
|
||||||
|
|
||||||
|
## Commit attendu
|
||||||
|
|
||||||
|
Après validation locale réussie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.4-pre.004
|
||||||
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md -->
|
<!-- file: docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Plan `0.2.4` — HTTP Blocks + Economics + compliance HTTP finale
|
# Plan `0.2.4` — HTTP Blocks + Economics + compliance HTTP finale
|
||||||
|
|
||||||
@@ -221,13 +221,26 @@ Les SIMDs `Review` liés au block-revenue sharing, notamment SIMD-0123, peuvent
|
|||||||
| `getBlockCommitment` | `slot` uniquement | `{commitment: array<u64> ou null, totalStake: u64}` | aucune config à inventer; préserver commitment nullable |
|
| `getBlockCommitment` | `slot` uniquement | `{commitment: array<u64> ou null, totalStake: u64}` | aucune config à inventer; préserver commitment nullable |
|
||||||
| `getBlockHeight` | config contextuelle optionnelle `{commitment, minContextSlot}` | `u64` | réutiliser `SolanaContextConfig`; `minContextSlot` est transmis tel quel au runtime |
|
| `getBlockHeight` | config contextuelle optionnelle `{commitment, minContextSlot}` | `u64` | réutiliser `SolanaContextConfig`; `minContextSlot` est transmis tel quel au runtime |
|
||||||
| `getBlockProduction` | config optionnelle `{commitment, identity, range:{firstSlot,lastSlot?}}` | `SolanaRpcResponse<{byIdentity, range}>` | `identity` typée Pubkey; validation locale déterministe `lastSlot >= firstSlot` lorsque les deux sont fournis; préserver les couples `[leaderSlots, blocksProduced]` |
|
| `getBlockProduction` | config optionnelle `{commitment, identity, range:{firstSlot,lastSlot?}}` | `SolanaRpcResponse<{byIdentity, range}>` | `identity` typée Pubkey; validation locale déterministe `lastSlot >= firstSlot` lorsque les deux sont fournis; préserver les couples `[leaderSlots, blocksProduced]` |
|
||||||
| `getBlocks` | `[start]`, `[start,end]`, `[start,config]`, `[start,end,config]` | `Vec<u64>` | commitment au moins `confirmed`; conserver l'overload untagged du second paramètre; `end < start` donne `[]`; rejeter localement une différence `end-start > 500_000` |
|
| `getBlocks` | `[start]`, `[start,end]`, `[start,config]`, `[start,end,config]`; config `{commitment,minContextSlot}` | `Vec<u64>` | commitment au moins `confirmed`; transmettre `minContextSlot`; conserver l'overload untagged du second paramètre; `end < start` donne `[]`; rejeter localement une différence `end-start > 500_000` |
|
||||||
| `getBlocksWithLimit` | `start`, `limit`, config contextuelle optionnelle | `Vec<u64>` | commitment au moins `confirmed`; `limit <= 500_000`; `limit = 0` est valide et donne `[]` |
|
| `getBlocksWithLimit` | `start`, `limit`, config contextuelle optionnelle `{commitment,minContextSlot}` | `Vec<u64>` | commitment au moins `confirmed`; transmettre `minContextSlot`; `limit <= 500_000`; `limit = 0` est valide et donne `[]` |
|
||||||
| `getBlockTime` | `slot` uniquement | `i64 ou null` | préserver l'absence de timestamp; les cas cleaned/skipped/not available restent des erreurs RPC runtime, pas des valeurs synthétiques |
|
| `getBlockTime` | `slot` uniquement | `i64 ou null` | préserver l'absence de timestamp; les cas cleaned/skipped/not available restent des erreurs RPC runtime, pas des valeurs synthétiques |
|
||||||
| `getFirstAvailableBlock` | aucun paramètre | `u64` | requête exacte `params: []`; aucune config |
|
| `getFirstAvailableBlock` | aucun paramètre | `u64` | requête exacte `params: []`; aucune config |
|
||||||
| `getRecentPerformanceSamples` | `limit?` | tableau de `{slot,numTransactions,numSlots,samplePeriodSecs,numNonVoteTransactions?}` | défaut runtime `720`; maximum `720`; rejeter avant I/O `limit > 720`; préserver `numNonVoteTransactions` nullable/ancien runtime |
|
| `getRecentPerformanceSamples` | `limit?` | tableau de `{slot,numTransactions,numSlots,samplePeriodSecs,numNonVoteTransactions?}` | défaut runtime `720`; maximum `720`; rejeter avant I/O `limit > 720`; préserver `numNonVoteTransactions` nullable/ancien runtime |
|
||||||
| `minimumLedgerSlot` | aucun paramètre | `u64` | requête exacte `params: []`; conserver les erreurs ledger/runtime |
|
| `minimumLedgerSlot` | aucun paramètre | `u64` | requête exacte `params: []`; conserver les erreurs ledger/runtime |
|
||||||
|
|
||||||
|
### Clarification runtime `pre.004` — config des ranges Blocks
|
||||||
|
|
||||||
|
Le réaudit d’implémentation de `pre.004` confirme dans Agave `v4.2.1` que `getBlocks` et `getBlocksWithLimit` consomment tous deux `RpcContextConfig`, et non un simple objet commitment. Le wrapper `RpcBlocksConfigWrapper` permet en plus à `getBlocks` de recevoir soit `end_slot`, soit cette config en deuxième position.
|
||||||
|
|
||||||
|
KSP doit donc réutiliser `SolanaContextConfig` et préserver les deux champs stables :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment
|
||||||
|
minContextSlot
|
||||||
|
```
|
||||||
|
|
||||||
|
La documentation publique n’affiche actuellement que `commitment` sur ces pages ; la source runtime stable prime ici conformément à `KSP-TRANSPORT-007`.
|
||||||
|
|
||||||
### `getBlock` — stratégie wire
|
### `getBlock` — stratégie wire
|
||||||
|
|
||||||
`getBlock` est le plus gros fil de la release et reçoit une prerelease dédiée.
|
`getBlock` est le plus gros fil de la release et reçoit une prerelease dédiée.
|
||||||
@@ -378,9 +391,9 @@ getBlock result null
|
|||||||
getBlock numRewardPartitions présent/omis (SIMD-0118)
|
getBlock numRewardPartitions présent/omis (SIMD-0118)
|
||||||
getBlock rewards commission nullable + commissionBps présent/omis (SIMD-0291)
|
getBlock rewards commission nullable + commissionBps présent/omis (SIMD-0291)
|
||||||
getBlock transaction version numérique non nulle conservée (canary SIMD-0385)
|
getBlock transaction version numérique non nulle conservée (canary SIMD-0385)
|
||||||
getBlocks [start,config] vs [start,end,config]
|
getBlocks [start,config] vs [start,end,config] + minContextSlot transmis
|
||||||
getBlocks range > 500_000 rejetée avant I/O
|
getBlocks range > 500_000 rejetée avant I/O
|
||||||
getBlocksWithLimit 0 et > 500_000
|
getBlocksWithLimit 0 et > 500_000 + minContextSlot transmis
|
||||||
getRecentPerformanceSamples défaut/720/>720
|
getRecentPerformanceSamples défaut/720/>720
|
||||||
getBlockProduction lastSlot < firstSlot rejeté avant I/O
|
getBlockProduction lastSlot < firstSlot rejeté avant I/O
|
||||||
getInflationReward ordre + null positionnels + commission/commissionBps présent/omis (SIMD-0291)
|
getInflationReward ordre + null positionnels + commission/commissionBps présent/omis (SIMD-0291)
|
||||||
|
|||||||
Reference in New Issue
Block a user