v0.2.4-pre.005
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 135
|
# version: 136
|
||||||
|
|
||||||
[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.4"
|
version = "0.2.4-pre.5"
|
||||||
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,11 @@
|
|||||||
|
{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"result":{
|
||||||
|
"context":{"slot":430000123,"apiVersion":"4.2.1"},
|
||||||
|
"value":{
|
||||||
|
"byIdentity":{"not-a-pubkey":[8,7]},
|
||||||
|
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id":1
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"jsonrpc":"2.0",
|
||||||
|
"result":{
|
||||||
|
"context":{"slot":430000123,"apiVersion":"4.2.1"},
|
||||||
|
"value":{
|
||||||
|
"byIdentity":{"11111111111111111111111111111111":[8,7]},
|
||||||
|
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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: 4
|
// version: 5
|
||||||
|
|
||||||
/// 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)]
|
||||||
@@ -143,7 +143,6 @@ impl SolanaBlockProductionRange {
|
|||||||
|
|
||||||
/// Serializes this range to the exact Solana JSON-RPC object.
|
/// Serializes this range to the exact Solana JSON-RPC object.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
||||||
let mut object = serde_json::Map::new();
|
let mut object = serde_json::Map::new();
|
||||||
object.insert("firstSlot".to_owned(), serde_json::Value::Number(self.first_slot.into()));
|
object.insert("firstSlot".to_owned(), serde_json::Value::Number(self.first_slot.into()));
|
||||||
@@ -192,14 +191,12 @@ impl SolanaBlockProductionConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this config would serialize to an empty object.
|
/// Returns whether this config would serialize to an empty object.
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) const fn is_empty(&self) -> bool {
|
pub(crate) const fn is_empty(&self) -> bool {
|
||||||
return self.commitment.is_none() && self.identity.is_none() && self.range.is_none();
|
return self.commitment.is_none() && self.identity.is_none() && self.range.is_none();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes this config to the exact Solana JSON-RPC object.
|
/// Serializes this config to the exact Solana JSON-RPC object.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
||||||
let mut object = serde_json::Map::new();
|
let mut object = serde_json::Map::new();
|
||||||
if let std::option::Option::Some(commitment) = self.commitment {
|
if let std::option::Option::Some(commitment) = self.commitment {
|
||||||
@@ -287,7 +284,6 @@ impl SolanaBlockProduction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a block-production result from its Solana JSON wire shape.
|
/// Decodes a block-production result 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::<WireBlockProduction>(method, value);
|
let decoded = crate::decode_wire_json::<WireBlockProduction>(method, value);
|
||||||
let wire = match decoded {
|
let wire = match decoded {
|
||||||
@@ -672,6 +668,38 @@ impl crate::HttpTransportPool {
|
|||||||
return self.get_blocks_u64_list("getBlocksWithLimit", role, params).await;
|
return self.get_blocks_u64_list("getBlocksWithLimit", role, params).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes typed `getBlockProduction` with optional identity, range, and commitment filters.
|
||||||
|
pub async fn get_block_production(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
config: std::option::Option<&crate::SolanaBlockProductionConfig>,
|
||||||
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaBlockProduction>> {
|
||||||
|
if let std::option::Option::Some(config) = config
|
||||||
|
&& let std::option::Option::Some(range) = config.range()
|
||||||
|
&& let std::option::Option::Some(last_slot) = range.last_slot()
|
||||||
|
&& last_slot < range.first_slot()
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, "getBlockProduction lastSlot must not be less than firstSlot")
|
||||||
|
.with_context("rpc_method", "getBlockProduction")
|
||||||
|
.with_context("first_slot", range.first_slot().to_string())
|
||||||
|
.with_context("last_slot", last_slot.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut params = std::vec::Vec::new();
|
||||||
|
if let std::option::Option::Some(config) = config
|
||||||
|
&& !config.is_empty()
|
||||||
|
{
|
||||||
|
params.push(config.to_json_value());
|
||||||
|
}
|
||||||
|
let value = self.execute_blocks_rpc("getBlockProduction", 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_block_production_response("getBlockProduction", value);
|
||||||
|
}
|
||||||
|
|
||||||
/// Executes typed `getRecentPerformanceSamples`, preserving runtime order and older sample shapes.
|
/// Executes typed `getRecentPerformanceSamples`, preserving runtime order and older sample shapes.
|
||||||
pub async fn get_recent_performance_samples(
|
pub async fn get_recent_performance_samples(
|
||||||
&self,
|
&self,
|
||||||
@@ -770,6 +798,24 @@ fn invalid_blocks_limit<T>(method: &'static str, message: &'static str, limit: u
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_block_production_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaBlockProduction>> {
|
||||||
|
let decoded = crate::decode_wire_json::<WireBlockProductionRpcResponse>(method, value);
|
||||||
|
let wire = match decoded {
|
||||||
|
std::result::Result::Ok(wire) => wire,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
||||||
|
let context = match context {
|
||||||
|
std::result::Result::Ok(context) => context,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let value = crate::SolanaBlockProduction::decode_wire(method, wire.value);
|
||||||
|
return match value {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, value)),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn decode_performance_samples(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPerformanceSample>> {
|
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 = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(method, value);
|
||||||
let values = match values {
|
let values = match values {
|
||||||
@@ -878,7 +924,6 @@ struct WireBlockCommitment {
|
|||||||
total_stake: u64,
|
total_stake: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WireBlockProductionRange {
|
struct WireBlockProductionRange {
|
||||||
@@ -886,7 +931,6 @@ struct WireBlockProductionRange {
|
|||||||
last_slot: u64,
|
last_slot: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WireBlockProduction {
|
struct WireBlockProduction {
|
||||||
@@ -894,6 +938,12 @@ struct WireBlockProduction {
|
|||||||
range: WireBlockProductionRange,
|
range: WireBlockProductionRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct WireBlockProductionRpcResponse {
|
||||||
|
context: serde_json::Value,
|
||||||
|
value: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -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: 18
|
// version: 19
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -402,3 +402,17 @@ fn public_v0_2_4_pre_004_range_and_performance_wrappers_are_available_from_crate
|
|||||||
let _get_blocks_with_limit = ksp_onchain_transport_lib::HttpTransportPool::get_blocks_with_limit;
|
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;
|
let _get_recent_performance_samples = ksp_onchain_transport_lib::HttpTransportPool::get_recent_performance_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_2_4_pre_005_block_production_wrapper_is_available_from_crate_root() {
|
||||||
|
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("public fixture identity must parse");
|
||||||
|
let range = ksp_onchain_transport_lib::SolanaBlockProductionRange::new(10, std::option::Option::Some(20));
|
||||||
|
let config = ksp_onchain_transport_lib::SolanaBlockProductionConfig::new(
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
|
||||||
|
std::option::Option::Some(identity),
|
||||||
|
std::option::Option::Some(range),
|
||||||
|
);
|
||||||
|
assert_eq!(config.identity(), std::option::Option::Some(&identity));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 16
|
// version: 17
|
||||||
|
|
||||||
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
||||||
|
|
||||||
@@ -521,3 +521,12 @@ fn release_v0_2_4_pre_004_range_performance_subset_is_exact_and_retry_safe() {
|
|||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
assert_eq!(actual.len(), 3);
|
assert_eq!(actual.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_4_pre_005_block_production_subset_is_exact_and_retry_safe() {
|
||||||
|
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method("getBlockProduction").expect("getBlockProduction descriptor must exist");
|
||||||
|
assert_eq!(descriptor.category(), ksp_onchain_transport_lib::HttpRpcCategory::Blocks);
|
||||||
|
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4);
|
||||||
|
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||||
|
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 3
|
// version: 4
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
|
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
|
||||||
@@ -453,3 +453,85 @@ async fn typed_get_recent_performance_samples_rejects_above_720_before_io() {
|
|||||||
assert_eq!(error.context()[1].key(), "limit");
|
assert_eq!(error.context()[1].key(), "limit");
|
||||||
assert_eq!(error.context()[1].value(), "721");
|
assert_eq!(error.context()[1].value(), "721");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_block_production_serializes_full_config_and_decodes_contextual_result() {
|
||||||
|
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
|
||||||
|
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::Some(430_000_099));
|
||||||
|
let config = crate::SolanaBlockProductionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||||
|
std::option::Option::Some(identity),
|
||||||
|
std::option::Option::Some(range),
|
||||||
|
);
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let response = pool
|
||||||
|
.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("block production fixture must succeed");
|
||||||
|
assert_eq!(response.context().slot(), 430_000_123);
|
||||||
|
assert_eq!(response.context().api_version(), std::option::Option::Some("4.2.1"));
|
||||||
|
assert_eq!(response.value().by_identity().get(&identity), std::option::Option::Some(&(8, 7)));
|
||||||
|
assert_eq!(response.value().range().first_slot(), 430_000_000);
|
||||||
|
assert_eq!(response.value().range().last_slot(), 430_000_099);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = request_body(request.as_str());
|
||||||
|
assert_eq!(body["method"], serde_json::json!("getBlockProduction"));
|
||||||
|
assert_eq!(
|
||||||
|
body["params"],
|
||||||
|
serde_json::json!([{
|
||||||
|
"commitment":"finalized",
|
||||||
|
"identity":"11111111111111111111111111111111",
|
||||||
|
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_block_production_omits_empty_config_and_supports_open_range_with_processed_commitment() {
|
||||||
|
let role = crate::HttpRoleName::new("default");
|
||||||
|
let empty = crate::SolanaBlockProductionConfig::default();
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let response = pool.get_block_production(&role, std::option::Option::Some(&empty)).await.expect("empty block production config must succeed");
|
||||||
|
assert_eq!(response.value().range().last_slot(), 430_000_099);
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([]));
|
||||||
|
|
||||||
|
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::None);
|
||||||
|
let config = crate::SolanaBlockProductionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Processed),
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(range),
|
||||||
|
);
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.success.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
pool.get_block_production(&role, std::option::Option::Some(&config)).await.expect("open block production range must succeed");
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(request_body(request.as_str())["params"], serde_json::json!([{"commitment":"processed","range":{"firstSlot":430000000}}]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_block_production_rejects_reversed_range_before_io() {
|
||||||
|
let range = crate::SolanaBlockProductionRange::new(430_000_100, std::option::Option::Some(430_000_099));
|
||||||
|
let config = crate::SolanaBlockProductionConfig::new(std::option::Option::None, std::option::Option::None, std::option::Option::Some(range));
|
||||||
|
let pool = pool_for_url("http://127.0.0.1:9");
|
||||||
|
let result = pool.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::Some(&config)).await;
|
||||||
|
let error = result.expect_err("reversed block production range must reject before I/O");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RPC_PARAMETERS);
|
||||||
|
assert_eq!(error.context()[1].key(), "first_slot");
|
||||||
|
assert_eq!(error.context()[1].value(), "430000100");
|
||||||
|
assert_eq!(error.context()[2].key(), "last_slot");
|
||||||
|
assert_eq!(error.context()[2].value(), "430000099");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_block_production_rejects_invalid_wire_identity_without_echoing_value() {
|
||||||
|
let (url, handle) = serve_once(include_str!("../fixtures/http/get_block_production.invalid_identity.json"));
|
||||||
|
let pool = pool_for_url(url.as_str());
|
||||||
|
let result = pool.get_block_production(&crate::HttpRoleName::new("default"), std::option::Option::None).await;
|
||||||
|
let error = result.expect_err("invalid block production identity must fail closed");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||||
|
assert!(!error.to_string().contains("not-a-pubkey"));
|
||||||
|
handle.join().expect("fixture server must join");
|
||||||
|
}
|
||||||
|
|||||||
355
deltas/0.2.4/pre.005.md
Normal file
355
deltas/0.2.4/pre.005.md
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
<!-- file: deltas/0.2.4/pre.005.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.4-pre.005` — `getBlockProduction`
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
Livraison précédente :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.004
|
||||||
|
workspace.package.version = "0.2.4-pre.4"
|
||||||
|
```
|
||||||
|
|
||||||
|
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 -> 208 unit tests OK
|
||||||
|
22 public API tests OK
|
||||||
|
17 release-completeness tests OK
|
||||||
|
1 smoke Devnet ignoré comme prévu
|
||||||
|
0 échec
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Implémenter exactement la tranche dédiée prévue par le plan `0.2.4` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getBlockProduction
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette méthode est déjà enregistrée dans la partition :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V0_2_4 / Blocks / Read / RetrySafe
|
||||||
|
```
|
||||||
|
|
||||||
|
Le registre central n'est donc pas modifié.
|
||||||
|
|
||||||
|
Après cette tranche, neuf des dix wrappers Blocks de `0.2.4` sont matérialisés. Il reste volontairement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.006 getBlock
|
||||||
|
```
|
||||||
|
|
||||||
|
Les cinq méthodes Economics restent hors scope jusqu'aux tranches `pre.007` et `pre.008`.
|
||||||
|
|
||||||
|
## Réaudit RPC de la tranche
|
||||||
|
|
||||||
|
Le contrat stable a été recroisé avec Agave `v4.2.1` avant implémentation.
|
||||||
|
|
||||||
|
`RpcBlockProductionConfig` expose exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
identity: Option<String>
|
||||||
|
range: Option<{firstSlot, lastSlot?}>
|
||||||
|
commitment: Option<CommitmentConfig>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le résultat est contextualisé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RpcResponse<RpcBlockProduction>
|
||||||
|
```
|
||||||
|
|
||||||
|
avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
context
|
||||||
|
value.byIdentity
|
||||||
|
value.range.firstSlot
|
||||||
|
value.range.lastSlot
|
||||||
|
```
|
||||||
|
|
||||||
|
`byIdentity` associe chaque validator identity aux comptes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(leader slots, blocks produced)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le runtime Agave rejette `lastSlot < firstSlot`. KSP effectue donc le même contrôle déterministe avant I/O.
|
||||||
|
|
||||||
|
Aucune restriction locale `confirmed/finalized` n'est appliquée au commitment de `getBlockProduction` : le `CommitmentConfig` général du runtime est préservé, y compris `processed`.
|
||||||
|
|
||||||
|
Aucun nouvel overload, champ ou type de résultat n'a été identifié ; le plan `011` reste donc inchangé en version 3.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Nouvelle prerelease technique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.4 -> 0.2.4-pre.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
|
||||||
|
|
||||||
|
## Wrapper typed `getBlockProduction`
|
||||||
|
|
||||||
|
Signature publique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_block_production(
|
||||||
|
role,
|
||||||
|
Option<&SolanaBlockProductionConfig>,
|
||||||
|
) -> Result<SolanaRpcResponse<SolanaBlockProduction>>
|
||||||
|
```
|
||||||
|
|
||||||
|
La configuration publique acquise en `pre.002` est réutilisée sans duplication :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaBlockProductionConfig
|
||||||
|
SolanaBlockProductionRange
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemple complet :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"commitment":"finalized",
|
||||||
|
"identity":"11111111111111111111111111111111",
|
||||||
|
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Une plage ouverte conserve l'absence de `lastSlot` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"commitment":"processed",
|
||||||
|
"range":{"firstSlot":430000000}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Un `Some(SolanaBlockProductionConfig::default())` est canoniquement omis et produit :
|
||||||
|
|
||||||
|
```json
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'existe ici aucun overload positional dont la distinction dépendrait d'un objet vide ; l'omission est donc équivalente au config runtime par défaut.
|
||||||
|
|
||||||
|
## Validation déterministe avant I/O
|
||||||
|
|
||||||
|
Lorsque les deux bornes sont explicites :
|
||||||
|
|
||||||
|
```text
|
||||||
|
lastSlot >= firstSlot -> valide
|
||||||
|
lastSlot < firstSlot -> rejet avant I/O
|
||||||
|
```
|
||||||
|
|
||||||
|
L'erreur KSP utilise le domaine partagé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ERROR_CODE_INVALID_RPC_PARAMETERS
|
||||||
|
```
|
||||||
|
|
||||||
|
avec contexte non secret :
|
||||||
|
|
||||||
|
```text
|
||||||
|
rpc_method
|
||||||
|
first_slot
|
||||||
|
last_slot
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune autre validation économique ou de commitment n'est inventée.
|
||||||
|
|
||||||
|
## Résultat contextualisé
|
||||||
|
|
||||||
|
Le wrapper préserve le `SolanaRpcResponse<T>` commun :
|
||||||
|
|
||||||
|
```text
|
||||||
|
context.slot
|
||||||
|
context.apiVersion?
|
||||||
|
value.byIdentity
|
||||||
|
value.range
|
||||||
|
```
|
||||||
|
|
||||||
|
Les clés de `byIdentity` sont décodées en `Pubkey` KSP. Une clé wire invalide ferme le décodage avec `ERROR_CODE_INVALID_RESPONSE` sans recopier la valeur fautive dans le diagnostic.
|
||||||
|
|
||||||
|
Les couples :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(leader slots, blocks produced)
|
||||||
|
```
|
||||||
|
|
||||||
|
sont conservés exactement ; Transport ne recalcule pas les statistiques et n'interprète pas leur économie.
|
||||||
|
|
||||||
|
## Discipline `#[cfg(test)]`
|
||||||
|
|
||||||
|
La règle introduite par `pre.002-fix.001` reste appliquée.
|
||||||
|
|
||||||
|
Éléments devenus runtime dans cette tranche parce qu'ils ont désormais un consommateur réel :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaBlockProductionRange::to_json_value
|
||||||
|
SolanaBlockProductionConfig::is_empty
|
||||||
|
SolanaBlockProductionConfig::to_json_value
|
||||||
|
SolanaBlockProduction::decode_wire
|
||||||
|
WireBlockProductionRange
|
||||||
|
WireBlockProduction
|
||||||
|
WireBlockProductionRpcResponse
|
||||||
|
```
|
||||||
|
|
||||||
|
Éléments déjà runtime des tranches précédentes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaBlockCommitment::decode_wire
|
||||||
|
WireBlockCommitment
|
||||||
|
SolanaPerformanceSample::decode_wire
|
||||||
|
WirePerformanceSample
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent test-only jusqu'à `pre.006` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaGetBlockConfig::{is_empty,to_json_value}
|
||||||
|
SolanaBlockReward::decode_wire
|
||||||
|
SolanaBlockTransaction::decode_wire
|
||||||
|
SolanaConfirmedBlock::decode_wire
|
||||||
|
helpers privés transaction/reward de bloc
|
||||||
|
WireBlockReward
|
||||||
|
WireBlockTransaction
|
||||||
|
WireConfirmedBlock
|
||||||
|
```
|
||||||
|
|
||||||
|
Tous les helpers/wires Economics restent également test-only jusqu'à leurs prereleases dédiées.
|
||||||
|
|
||||||
|
Aucun `#[allow(dead_code)]` n'est introduit.
|
||||||
|
|
||||||
|
## Fixtures HTTP ajoutées
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.invalid_identity.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles couvrent :
|
||||||
|
|
||||||
|
- contexte `slot + apiVersion` ;
|
||||||
|
- `byIdentity` typé ;
|
||||||
|
- plage effective ;
|
||||||
|
- erreur fermée sur identity wire invalide.
|
||||||
|
|
||||||
|
## Tests ajoutés
|
||||||
|
|
||||||
|
Quatre unit tests HTTP sont ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
typed_get_block_production_serializes_full_config_and_decodes_contextual_result
|
||||||
|
typed_get_block_production_omits_empty_config_and_supports_open_range_with_processed_commitment
|
||||||
|
typed_get_block_production_rejects_reversed_range_before_io
|
||||||
|
typed_get_block_production_rejects_invalid_wire_identity_without_echoing_value
|
||||||
|
```
|
||||||
|
|
||||||
|
Une canarie public API vérifie que le wrapper reste disponible depuis la racine de crate :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_v0_2_4_pre_005_block_production_wrapper_is_available_from_crate_root
|
||||||
|
```
|
||||||
|
|
||||||
|
Une canarie release-completeness vérifie le descriptor :
|
||||||
|
|
||||||
|
```text
|
||||||
|
release_v0_2_4_pre_005_block_production_subset_is_exact_and_retry_safe
|
||||||
|
```
|
||||||
|
|
||||||
|
Compteurs attendus après validation locale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
212 unit tests
|
||||||
|
23 public API tests
|
||||||
|
18 release-completeness tests
|
||||||
|
1 smoke Devnet ignoré
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.success.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_block_production.invalid_identity.json
|
||||||
|
deltas/0.2.4/pre.005.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fichier n'est supprimé.
|
||||||
|
|
||||||
|
## Validation attendue
|
||||||
|
|
||||||
|
À exécuter après application de l'overlay :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Les critères de fermeture sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun warning introduit
|
||||||
|
212 unit tests OK
|
||||||
|
23 public API tests OK
|
||||||
|
18 release-completeness tests OK
|
||||||
|
smoke Devnet ignoré comme prévu
|
||||||
|
0 échec
|
||||||
|
```
|
||||||
|
|
||||||
|
Le sandbox de préparation ne possède pas le toolchain Cargo/Rust ; aucune compilation locale n'est revendiquée par ce delta.
|
||||||
|
|
||||||
|
## Commit attendu
|
||||||
|
|
||||||
|
Après validation locale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.4-pre.005
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
La prerelease suivante reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.4-pre.006
|
||||||
|
```
|
||||||
|
|
||||||
|
Scope prévu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
réaudit SIMD-0298 / SIMD-0307
|
||||||
|
getBlock moderne
|
||||||
|
bare encoding legacy
|
||||||
|
transactionDetails
|
||||||
|
wire bloc riche
|
||||||
|
SIMD-0118 numRewardPartitions
|
||||||
|
SIMD-0291 commissionBps
|
||||||
|
canary version transaction numérique non zéro / SIMD-0385
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user