v0.2.3-pre.006
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 125
|
||||
# version: 126
|
||||
|
||||
[workspace]
|
||||
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"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.3-pre.5"
|
||||
version = "0.2.3-pre.6"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid param: supplied blockhash is not valid"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":"airdrop-fixture-signature-111111111111111111111111111111111111111111111111","id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Error processing Instruction 0","data":{"err":{"InstructionError":[0,"Custom"]},"logs":["Program log: fixture preflight failure"],"unitsConsumed":123}},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":"send-fixture-signature-22222222222222222222222222222222222222222222222222","id":1}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -10,10 +10,9 @@
|
||||
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
|
||||
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
|
||||
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
|
||||
//! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives and seven read wrappers through `pre.004`,
|
||||
//! including bounded prioritization-fee, address-signature and signature-status queries. `getTransaction`, both write submissions and
|
||||
//! `simulateTransaction`, plus the
|
||||
//! `0.2.4` family remain staged.
|
||||
//! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives, all eight read wrappers and both write
|
||||
//! submissions through `pre.006`, including complete modern/legacy `getTransaction` coverage and centralized no-resend protection for writes.
|
||||
//! `simulateTransaction` and the `0.2.4` family remain staged.
|
||||
|
||||
mod client;
|
||||
mod constants;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
/// Binary encoding accepted for serialized transaction input payloads.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -321,14 +321,12 @@ impl SolanaRequestAirdropConfig {
|
||||
}
|
||||
|
||||
/// Returns whether the airdrop config would serialize to an empty object.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
return self.recent_blockhash.is_none() && self.commitment.is_none();
|
||||
}
|
||||
|
||||
/// Serializes this config to the Solana JSON-RPC wire 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(blockhash) = self.recent_blockhash.as_ref() {
|
||||
@@ -395,7 +393,6 @@ impl SolanaSendTransactionConfig {
|
||||
}
|
||||
|
||||
/// Returns whether the send config would serialize to an empty object.
|
||||
#[cfg(test)]
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.skip_preflight.is_none()
|
||||
&& self.preflight_commitment.is_none()
|
||||
@@ -406,7 +403,6 @@ impl SolanaSendTransactionConfig {
|
||||
|
||||
/// Serializes this config to the Solana JSON-RPC wire 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(value) = self.skip_preflight {
|
||||
@@ -1314,6 +1310,53 @@ impl crate::HttpTransportPool {
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `requestAirdrop` through the common KSP HTTP transport path.
|
||||
///
|
||||
/// The RPC creates and submits a faucet transaction, so its audited descriptor is `WriteSubmission / NeverAfterDispatch`. The optional
|
||||
/// `recentBlockhash` field is retained from the targeted Agave runtime even though the public Solana page currently documents only `commitment`.
|
||||
pub async fn request_airdrop(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
recipient: &ksp_core_lib::Pubkey,
|
||||
lamports: u64,
|
||||
config: std::option::Option<&crate::SolanaRequestAirdropConfig>,
|
||||
) -> ksp_core_lib::Result<std::string::String> {
|
||||
let mut params = std::vec![serde_json::Value::String(recipient.to_string()), serde_json::json!(lamports)];
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& !config.is_empty()
|
||||
{
|
||||
params.push(config.to_json_value());
|
||||
}
|
||||
let value = self.execute_transaction_rpc("requestAirdrop", role, params).await;
|
||||
return match value {
|
||||
std::result::Result::Ok(value) => crate::decode_wire_json::<std::string::String>("requestAirdrop", value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `sendTransaction` through the common KSP HTTP transport path.
|
||||
///
|
||||
/// Transport forwards an already serialized and signed transaction without decoding or modifying it. `config.maxRetries` controls node-side
|
||||
/// retransmission only; KSP's HTTP retry policy remains governed by the central `WriteSubmission / NeverAfterDispatch` descriptor.
|
||||
pub async fn send_transaction(
|
||||
&self,
|
||||
role: &crate::HttpRoleName,
|
||||
transaction: &str,
|
||||
config: std::option::Option<&crate::SolanaSendTransactionConfig>,
|
||||
) -> ksp_core_lib::Result<std::string::String> {
|
||||
let mut params = std::vec![serde_json::Value::String(transaction.to_owned())];
|
||||
if let std::option::Option::Some(config) = config
|
||||
&& !config.is_empty()
|
||||
{
|
||||
params.push((*config).to_json_value());
|
||||
}
|
||||
let value = self.execute_transaction_rpc("sendTransaction", role, params).await;
|
||||
return match value {
|
||||
std::result::Result::Ok(value) => crate::decode_wire_json::<std::string::String>("sendTransaction", value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes typed `getTransactionCount` through the common KSP HTTP transport path.
|
||||
pub async fn get_transaction_count(
|
||||
&self,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -300,3 +300,24 @@ fn public_transaction_pre_005_get_transaction_complete_request_forms_are_availab
|
||||
assert_eq!(config.encoding(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed));
|
||||
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_transaction_pre_006_write_wrappers_and_complete_configs_are_available_from_crate_root() {
|
||||
let _request_airdrop = ksp_onchain_transport_lib::HttpTransportPool::request_airdrop;
|
||||
let _send_transaction = ksp_onchain_transport_lib::HttpTransportPool::send_transaction;
|
||||
let airdrop = ksp_onchain_transport_lib::SolanaRequestAirdropConfig::new(
|
||||
std::option::Option::Some("recent-blockhash".to_owned()),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||
);
|
||||
assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash"));
|
||||
let send = ksp_onchain_transport_lib::SolanaSendTransactionConfig::new(
|
||||
std::option::Option::Some(false),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64),
|
||||
std::option::Option::Some(5),
|
||||
std::option::Option::Some(431_000_000),
|
||||
);
|
||||
assert_eq!(send.encoding(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64));
|
||||
assert_eq!(send.max_retries(), std::option::Option::Some(5));
|
||||
assert_eq!(send.min_context_slot(), std::option::Option::Some(431_000_000));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||
|
||||
@@ -326,3 +326,36 @@ fn release_pre_005_transaction_read_subset_adds_complete_get_transaction_without
|
||||
assert_eq!(actual.len(), 8);
|
||||
assert_eq!(deferred.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_pre_006_transaction_subset_adds_both_write_submissions_without_advancing_simulation() {
|
||||
let reads = std::vec![
|
||||
"getFeeForMessage",
|
||||
"getLatestBlockhash",
|
||||
"getRecentPrioritizationFees",
|
||||
"getSignaturesForAddress",
|
||||
"getSignatureStatuses",
|
||||
"getTransaction",
|
||||
"getTransactionCount",
|
||||
"isBlockhashValid",
|
||||
];
|
||||
let writes = std::vec!["requestAirdrop", "sendTransaction"];
|
||||
for method_name in &reads {
|
||||
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("pre.006 read descriptor must exist");
|
||||
assert_eq!(descriptor.category(), ksp_onchain_transport_lib::HttpRpcCategory::Transactions);
|
||||
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3);
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||
}
|
||||
for method_name in &writes {
|
||||
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("pre.006 write descriptor must exist");
|
||||
assert_eq!(descriptor.category(), ksp_onchain_transport_lib::HttpRpcCategory::Transactions);
|
||||
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3);
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::WriteSubmission);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NeverAfterDispatch);
|
||||
}
|
||||
let simulation = ksp_onchain_transport_lib::find_http_rpc_method("simulateTransaction").expect("simulation descriptor must remain registered");
|
||||
assert_eq!(simulation.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Simulation);
|
||||
assert_eq!(reads.len(), 8);
|
||||
assert_eq!(writes.len(), 2);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#[test]
|
||||
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
|
||||
@@ -290,27 +290,34 @@ fn simulation_result_distinguishes_omitted_from_explicit_null_fields() {
|
||||
}
|
||||
|
||||
fn transaction_pool_for_url(url: &str) -> crate::HttpTransportPool {
|
||||
let role = crate::HttpEndpointRoleSettings::new(
|
||||
crate::HttpRoleName::new("default"),
|
||||
true,
|
||||
std::vec![crate::HttpRequestKind::wildcard()],
|
||||
10,
|
||||
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
|
||||
);
|
||||
let endpoint = crate::HttpEndpointSettings::new(
|
||||
"fixture",
|
||||
true,
|
||||
crate::HttpProviderName::new("fixture"),
|
||||
crate::HttpClusterName::new("local"),
|
||||
crate::HttpEndpointUrl::parse(url).expect("fixture URL must parse"),
|
||||
std::time::Duration::from_secs(1),
|
||||
std::time::Duration::from_secs(1),
|
||||
std::option::Option::Some(1),
|
||||
std::vec![role],
|
||||
);
|
||||
return transaction_pool_for_urls(&[(url, 10)], std::time::Duration::from_secs(1), 0);
|
||||
}
|
||||
|
||||
fn transaction_pool_for_urls(urls: &[(&str, u32)], request_timeout: std::time::Duration, max_retries: u32) -> crate::HttpTransportPool {
|
||||
let mut endpoints = std::vec::Vec::with_capacity(urls.len());
|
||||
for (index, (url, priority)) in urls.iter().enumerate() {
|
||||
let role = crate::HttpEndpointRoleSettings::new(
|
||||
crate::HttpRoleName::new("default"),
|
||||
true,
|
||||
std::vec![crate::HttpRequestKind::wildcard()],
|
||||
*priority,
|
||||
crate::HttpRoleLimits::new(std::option::Option::None, std::option::Option::None, std::option::Option::None, std::option::Option::None),
|
||||
);
|
||||
endpoints.push(crate::HttpEndpointSettings::new(
|
||||
format!("fixture-{index}"),
|
||||
true,
|
||||
crate::HttpProviderName::new("fixture"),
|
||||
crate::HttpClusterName::new("local"),
|
||||
crate::HttpEndpointUrl::parse(url).expect("fixture URL must parse"),
|
||||
std::time::Duration::from_millis(100),
|
||||
request_timeout,
|
||||
std::option::Option::Some(1),
|
||||
std::vec![role],
|
||||
));
|
||||
}
|
||||
let settings = crate::HttpTransportSettings::new(
|
||||
std::vec![endpoint],
|
||||
crate::HttpRetrySettings::new(0, std::time::Duration::from_millis(1), std::time::Duration::from_millis(1)),
|
||||
endpoints,
|
||||
crate::HttpRetrySettings::new(max_retries, std::time::Duration::from_millis(1), std::time::Duration::from_millis(2)),
|
||||
);
|
||||
return crate::HttpTransportPool::new(settings).expect("fixture pool must build");
|
||||
}
|
||||
@@ -328,6 +335,69 @@ fn serve_transaction_once(body: &'static str) -> (std::string::String, std::thre
|
||||
return (format!("http://{address}"), handle);
|
||||
}
|
||||
|
||||
fn serve_transaction_status_and_count(status_line: &'static str) -> (std::string::String, std::thread::JoinHandle<(usize, std::string::String)>) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind");
|
||||
let address = listener.local_addr().expect("fixture listener address must resolve");
|
||||
let handle = std::thread::spawn(move || {
|
||||
let (mut stream, _) = listener.accept().expect("fixture server must accept first request");
|
||||
let first_request = read_transaction_request(&mut stream);
|
||||
let response = format!("HTTP/1.1 {status_line}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
|
||||
std::io::Write::write_all(&mut stream, response.as_bytes()).expect("fixture response must write");
|
||||
let mut count = 1_usize;
|
||||
listener.set_nonblocking(true).expect("fixture listener must become nonblocking");
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(120);
|
||||
while std::time::Instant::now() < deadline {
|
||||
match listener.accept() {
|
||||
std::result::Result::Ok((mut retry_stream, _)) => {
|
||||
let _ = read_transaction_request(&mut retry_stream);
|
||||
std::io::Write::write_all(&mut retry_stream, response.as_bytes()).expect("fixture retry response must write");
|
||||
count = count.saturating_add(1);
|
||||
},
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
},
|
||||
std::result::Result::Err(error) => panic!("fixture listener failed while counting retries: {error}"),
|
||||
}
|
||||
}
|
||||
return (count, first_request);
|
||||
});
|
||||
return (format!("http://{address}"), handle);
|
||||
}
|
||||
|
||||
fn serve_transaction_timeout_and_count(delay: std::time::Duration) -> (std::string::String, std::thread::JoinHandle<(usize, std::string::String)>) {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind");
|
||||
let address = listener.local_addr().expect("fixture listener address must resolve");
|
||||
let handle = std::thread::spawn(move || {
|
||||
let (mut stream, _) = listener.accept().expect("fixture server must accept first request");
|
||||
let first_request = read_transaction_request(&mut stream);
|
||||
std::thread::sleep(delay);
|
||||
let mut count = 1_usize;
|
||||
listener.set_nonblocking(true).expect("fixture listener must become nonblocking");
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(120);
|
||||
while std::time::Instant::now() < deadline {
|
||||
match listener.accept() {
|
||||
std::result::Result::Ok((mut retry_stream, _)) => {
|
||||
let _ = read_transaction_request(&mut retry_stream);
|
||||
count = count.saturating_add(1);
|
||||
},
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
},
|
||||
std::result::Result::Err(error) => panic!("fixture listener failed while counting timeout retries: {error}"),
|
||||
}
|
||||
}
|
||||
return (count, first_request);
|
||||
});
|
||||
return (format!("http://{address}"), handle);
|
||||
}
|
||||
|
||||
fn unused_local_url() -> std::string::String {
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("unused-port probe must bind");
|
||||
let address = listener.local_addr().expect("unused-port probe address must resolve");
|
||||
drop(listener);
|
||||
return format!("http://{address}");
|
||||
}
|
||||
|
||||
fn read_transaction_request(stream: &mut std::net::TcpStream) -> std::string::String {
|
||||
let mut bytes = std::vec::Vec::new();
|
||||
let mut buffer = [0_u8; 1024];
|
||||
@@ -874,3 +944,198 @@ async fn typed_get_transaction_rejects_processed_commitment_before_io() {
|
||||
assert_eq!(error.context()[1].key(), "commitment");
|
||||
assert_eq!(error.context()[1].value(), "processed");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_request_airdrop_exposes_runtime_config_and_canonicalizes_empty_config() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let config = crate::SolanaRequestAirdropConfig::new(
|
||||
std::option::Option::Some("recent-blockhash-fixture".to_owned()),
|
||||
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||
);
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let signature = pool
|
||||
.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1_000_000_000, std::option::Option::Some(&config))
|
||||
.await
|
||||
.expect("requestAirdrop full-config fixture must succeed");
|
||||
assert_eq!(signature, "airdrop-fixture-signature-111111111111111111111111111111111111111111111111");
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
let body = transaction_request_body(request.as_str());
|
||||
assert_eq!(body["method"], serde_json::json!("requestAirdrop"));
|
||||
assert_eq!(
|
||||
body["params"],
|
||||
serde_json::json!([
|
||||
"11111111111111111111111111111111",
|
||||
1000000000,
|
||||
{"recentBlockhash":"recent-blockhash-fixture","commitment":"finalized"}
|
||||
])
|
||||
);
|
||||
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let empty = crate::SolanaRequestAirdropConfig::default();
|
||||
let _ = pool
|
||||
.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::Some(&empty))
|
||||
.await
|
||||
.expect("requestAirdrop empty-config fixture must succeed");
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["11111111111111111111111111111111", 1]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_send_transaction_exposes_all_current_options_and_both_binary_encodings() {
|
||||
let config = crate::SolanaSendTransactionConfig::new(
|
||||
std::option::Option::Some(true),
|
||||
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64),
|
||||
std::option::Option::Some(7),
|
||||
std::option::Option::Some(431_000_000),
|
||||
);
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let signature = pool
|
||||
.send_transaction(&crate::HttpRoleName::new("default"), "opaque-base64-transaction", std::option::Option::Some(&config))
|
||||
.await
|
||||
.expect("sendTransaction full-config fixture must succeed");
|
||||
assert_eq!(signature, "send-fixture-signature-22222222222222222222222222222222222222222222222222");
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
let body = transaction_request_body(request.as_str());
|
||||
assert_eq!(body["method"], serde_json::json!("sendTransaction"));
|
||||
assert_eq!(
|
||||
body["params"],
|
||||
serde_json::json!([
|
||||
"opaque-base64-transaction",
|
||||
{
|
||||
"skipPreflight":true,
|
||||
"preflightCommitment":"confirmed",
|
||||
"encoding":"base64",
|
||||
"maxRetries":7,
|
||||
"minContextSlot":431000000
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
let base58 = crate::SolanaSendTransactionConfig::new(
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base58),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let _ = pool
|
||||
.send_transaction(&crate::HttpRoleName::new("default"), "opaque-base58-transaction", std::option::Option::Some(&base58))
|
||||
.await
|
||||
.expect("sendTransaction base58 fixture must succeed");
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-base58-transaction", {"encoding":"base58"}]));
|
||||
|
||||
let empty = crate::SolanaSendTransactionConfig::default();
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
|
||||
let pool = transaction_pool_for_url(url.as_str());
|
||||
let _ = pool
|
||||
.send_transaction(&crate::HttpRoleName::new("default"), "opaque-default-transaction", std::option::Option::Some(&empty))
|
||||
.await
|
||||
.expect("sendTransaction empty config must canonicalize to omission");
|
||||
let request = handle.join().expect("fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["opaque-default-transaction"]));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_write_wrappers_preserve_rpc_application_errors_without_transport_retry() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.error.json"));
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("requestAirdrop RPC error must propagate").code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
|
||||
handle.join().expect("fixture server must join");
|
||||
|
||||
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.preflight_error.json"));
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("sendTransaction preflight error must propagate").code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
|
||||
handle.join().expect("fixture server must join");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_write_wrappers_never_resend_after_http_429_dispatch() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let (url, handle) = serve_transaction_status_and_count("429 Too Many Requests");
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("airdrop 429 must stop after dispatch").code(), crate::ERROR_CODE_RATE_LIMITED);
|
||||
let (count, request) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
|
||||
|
||||
let (url, handle) = serve_transaction_status_and_count("429 Too Many Requests");
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("send 429 must stop after dispatch").code(), crate::ERROR_CODE_RATE_LIMITED);
|
||||
let (count, request) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("sendTransaction"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_write_wrappers_never_resend_after_temporary_http_dispatch() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let (url, handle) = serve_transaction_status_and_count("503 Service Unavailable");
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("airdrop 503 must stop after dispatch").code(), crate::ERROR_CODE_HTTP_REQUEST_FAILED);
|
||||
let (count, _) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
|
||||
let (url, handle) = serve_transaction_status_and_count("503 Service Unavailable");
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(500), 3);
|
||||
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("send 503 must stop after dispatch").code(), crate::ERROR_CODE_HTTP_REQUEST_FAILED);
|
||||
let (count, _) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_write_wrappers_never_resend_after_ambiguous_timeout_dispatch() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let (url, handle) = serve_transaction_timeout_and_count(std::time::Duration::from_millis(80));
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(20), 3);
|
||||
let result = pool.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("airdrop timeout must stop after ambiguous dispatch").code(), crate::ERROR_CODE_TIMEOUT);
|
||||
let (count, _) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
|
||||
let (url, handle) = serve_transaction_timeout_and_count(std::time::Duration::from_millis(80));
|
||||
let pool = transaction_pool_for_urls(&[(url.as_str(), 10)], std::time::Duration::from_millis(20), 3);
|
||||
let result = pool.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None).await;
|
||||
assert_eq!(result.expect_err("send timeout must stop after ambiguous dispatch").code(), crate::ERROR_CODE_TIMEOUT);
|
||||
let (count, _) = handle.join().expect("fixture server must join");
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn typed_write_wrappers_can_retry_when_connection_failure_proves_not_dispatched() {
|
||||
let recipient = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||
let closed = unused_local_url();
|
||||
let (secondary, handle) = serve_transaction_once(include_str!("../fixtures/http/request_airdrop.success.json"));
|
||||
let pool = transaction_pool_for_urls(&[(closed.as_str(), 10), (secondary.as_str(), 10)], std::time::Duration::from_millis(500), 1);
|
||||
let result = pool
|
||||
.request_airdrop(&crate::HttpRoleName::new("default"), &recipient, 1, std::option::Option::None)
|
||||
.await
|
||||
.expect("NotDispatched connection failure may retry requestAirdrop safely");
|
||||
assert_eq!(result, "airdrop-fixture-signature-111111111111111111111111111111111111111111111111");
|
||||
let request = handle.join().expect("secondary fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("requestAirdrop"));
|
||||
|
||||
let closed = unused_local_url();
|
||||
let (secondary, handle) = serve_transaction_once(include_str!("../fixtures/http/send_transaction.success.json"));
|
||||
let pool = transaction_pool_for_urls(&[(closed.as_str(), 10), (secondary.as_str(), 10)], std::time::Duration::from_millis(500), 1);
|
||||
let result = pool
|
||||
.send_transaction(&crate::HttpRoleName::new("default"), "opaque-transaction", std::option::Option::None)
|
||||
.await
|
||||
.expect("NotDispatched connection failure may retry sendTransaction safely");
|
||||
assert_eq!(result, "send-fixture-signature-22222222222222222222222222222222222222222222222222");
|
||||
let request = handle.join().expect("secondary fixture server must join");
|
||||
assert_eq!(transaction_request_body(request.as_str())["method"], serde_json::json!("sendTransaction"));
|
||||
}
|
||||
|
||||
282
deltas/0.2.3/pre.006.md
Normal file
282
deltas/0.2.3/pre.006.md
Normal file
@@ -0,0 +1,282 @@
|
||||
<!-- file: deltas/0.2.3/pre.006.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.3-pre.006` — `requestAirdrop` + `sendTransaction` et preuve no-resend
|
||||
|
||||
## Base requise
|
||||
|
||||
Livraison précédente validée localement par l'opérateur :
|
||||
|
||||
```text
|
||||
0.2.3-pre.005
|
||||
workspace.package.version = "0.2.3-pre.5"
|
||||
```
|
||||
|
||||
La validation opérateur du 2026-08-18 a confirmé :
|
||||
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
cargo check --workspace OK
|
||||
cargo clippy --workspace --all-targets OK
|
||||
cargo test -p ksp-onchain-transport-lib OK
|
||||
```
|
||||
|
||||
Résultats Transport de cette base :
|
||||
|
||||
```text
|
||||
168 unit tests
|
||||
17 public API tests
|
||||
11 release completeness tests
|
||||
0 warning signalé par check/clippy
|
||||
```
|
||||
|
||||
## Objectif
|
||||
|
||||
Activer les deux écritures de la famille Transactions :
|
||||
|
||||
```text
|
||||
requestAirdrop
|
||||
sendTransaction
|
||||
```
|
||||
|
||||
Les deux wrappers restent attachés aux descriptors centraux déjà audités :
|
||||
|
||||
```text
|
||||
RpcOperationKind::WriteSubmission
|
||||
TransportRetryClass::NeverAfterDispatch
|
||||
```
|
||||
|
||||
Après cette tranche :
|
||||
|
||||
```text
|
||||
10 / 11 wrappers Transactions exécutés
|
||||
8 Read
|
||||
2 WriteSubmission
|
||||
1 Simulation encore différée
|
||||
```
|
||||
|
||||
`simulateTransaction` reste réservée à `pre.007`.
|
||||
|
||||
## Version Cargo
|
||||
|
||||
Conformément à `VER-ID-009` :
|
||||
|
||||
```text
|
||||
0.2.3-pre.5 -> 0.2.3-pre.6
|
||||
```
|
||||
|
||||
Aucune dépendance ni feature Cargo n'est ajoutée.
|
||||
|
||||
## Complétude `requestAirdrop`
|
||||
|
||||
API publique :
|
||||
|
||||
```text
|
||||
HttpTransportPool::request_airdrop(
|
||||
role,
|
||||
recipient: &Pubkey,
|
||||
lamports: u64,
|
||||
Option<&SolanaRequestAirdropConfig>,
|
||||
)
|
||||
-> Result<String>
|
||||
```
|
||||
|
||||
La page publique Solana courante documente `commitment` dans l'objet de config. La source primaire Agave `v4.2.1` conserve en plus :
|
||||
|
||||
```text
|
||||
recentBlockhash: Option<String>
|
||||
```
|
||||
|
||||
KSP l'expose donc conformément à `KSP-TRANSPORT-007` au lieu de réduire la surface au seul exemple/document public visible.
|
||||
|
||||
Le résultat reste la signature de transaction retournée par le faucet sous forme de chaîne wire. Transport ne prend pas une dépendance Signature uniquement
|
||||
pour redécoder une valeur que le provider vient de produire.
|
||||
|
||||
Une config vide est canonicalisée vers l'omission du troisième paramètre, syntaxe strictement équivalente autorisée par `KSP-TRANSPORT-007`.
|
||||
|
||||
## Complétude `sendTransaction`
|
||||
|
||||
API publique :
|
||||
|
||||
```text
|
||||
HttpTransportPool::send_transaction(
|
||||
role,
|
||||
transaction: &str,
|
||||
Option<&SolanaSendTransactionConfig>,
|
||||
)
|
||||
-> Result<String>
|
||||
```
|
||||
|
||||
Transport reçoit une transaction déjà construite, signée et encodée. Il ne la décode, ne la signe et ne la modifie pas.
|
||||
|
||||
La config expose la surface courante complète :
|
||||
|
||||
```text
|
||||
encoding Base58 | Base64
|
||||
skipPreflight Option<bool>
|
||||
preflightCommitment Option<SolanaCommitment>
|
||||
maxRetries Option<usize>
|
||||
minContextSlot Option<u64>
|
||||
```
|
||||
|
||||
Les deux encodings binaires sont exercés par fixtures HTTP locales.
|
||||
|
||||
Une config vide est canonicalisée vers l'omission du second paramètre.
|
||||
|
||||
### Distinction impérative des retries
|
||||
|
||||
```text
|
||||
SolanaSendTransactionConfig.maxRetries = retransmissions node-side après acceptation RPC
|
||||
HttpRetrySettings = retries HTTP Transport KSP
|
||||
```
|
||||
|
||||
Le premier champ n'accorde aucune permission de resoumission HTTP au wrapper.
|
||||
|
||||
## Preuves end-to-end de no-resend
|
||||
|
||||
Les tests ne se limitent pas à `evaluate_transport_retry`.
|
||||
|
||||
Ils exercent réellement :
|
||||
|
||||
```text
|
||||
wrapper typed
|
||||
-> descriptor central
|
||||
-> execute_standard_rpc
|
||||
-> client HTTP fixture
|
||||
```
|
||||
|
||||
Pour **chacun** de `requestAirdrop` et `sendTransaction`, un serveur local compte les requêtes reçues.
|
||||
|
||||
Cas ambigus après dispatch :
|
||||
|
||||
```text
|
||||
HTTP 429 -> erreur rate_limited -> compteur = 1
|
||||
HTTP 503 -> erreur http_request_failed -> compteur = 1
|
||||
timeout -> erreur timeout -> compteur = 1
|
||||
```
|
||||
|
||||
Même avec plusieurs retries configurés dans `HttpRetrySettings`, aucune seconde soumission n'est émise après ces trois états ambigus.
|
||||
|
||||
Cas explicitement sûr :
|
||||
|
||||
```text
|
||||
connexion refusée -> ERROR_CODE_HTTP_CONNECTION_FAILED -> NotDispatched
|
||||
```
|
||||
|
||||
Avec deux endpoints de même priorité, la première tentative échoue avant dispatch et le retry central peut atteindre le second endpoint. Cette preuve est
|
||||
exercée séparément pour les deux wrappers.
|
||||
|
||||
## Erreurs RPC applicatives
|
||||
|
||||
Les fixtures couvrent aussi :
|
||||
|
||||
- une erreur `requestAirdrop` de paramètres/blockhash ;
|
||||
- une erreur `sendTransaction` de preflight/simulation.
|
||||
|
||||
Ces réponses restent `ERROR_CODE_RPC_APPLICATION_ERROR` et ne deviennent jamais des retries transport.
|
||||
|
||||
## `KSP-TRANSPORT-007` rétroactif
|
||||
|
||||
La question opérateur sur les wrappers antérieurs est enregistrée dans le plan `010`.
|
||||
|
||||
Les audits `0.2.1`/`0.2.2` avaient déjà recherché la surface complète. Un contrôle ciblé pendant cette tranche confirme notamment l'alignement KSP avec
|
||||
Agave `v4.2.1` pour :
|
||||
|
||||
```text
|
||||
RpcAccountInfoConfig
|
||||
RpcProgramAccountsConfig
|
||||
RpcLargestAccountsConfig
|
||||
RpcLeaderScheduleConfig
|
||||
RpcGetVoteAccountsConfig
|
||||
```
|
||||
|
||||
Mais la règle n'était pas encore un critère de clôture nommé. La prévision devient donc :
|
||||
|
||||
```text
|
||||
pre.007 simulateTransaction complet
|
||||
pre.008 audit rétroactif KSP-TRANSPORT-007 sur 0.2.1 -> 0.2.3 + corrections éventuelles
|
||||
pre.009 clôture finale documentaire/validation/smoke/prompt 0.2.4
|
||||
```
|
||||
|
||||
Cette tranche supplémentaire évite de transformer la clôture en remédiation fonctionnelle tardive.
|
||||
|
||||
## Documentation/source synchronisée
|
||||
|
||||
`crates/ksp-onchain-transport-lib/src/lib.rs` est corrigé pour refléter la surface réellement disponible après `pre.006` :
|
||||
|
||||
```text
|
||||
8 reads Transactions
|
||||
2 write submissions
|
||||
simulateTransaction encore staged
|
||||
```
|
||||
|
||||
Le plan `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md` passe en version 5 et enregistre le nouveau découpage jusqu'à `pre.009`.
|
||||
|
||||
`CHANGELOG.md` reste réservé à `0.2.3-rel.001`.
|
||||
|
||||
## Tests ajoutés
|
||||
|
||||
Sept tests unitaires couvrent :
|
||||
|
||||
```text
|
||||
requestAirdrop config complète + canonicalisation config vide
|
||||
sendTransaction config complète + base58/base64 + config vide
|
||||
erreurs RPC applicatives des deux writes
|
||||
no-resend après HTTP 429 pour les deux writes
|
||||
no-resend après HTTP 503 pour les deux writes
|
||||
no-resend après timeout ambigu pour les deux writes
|
||||
retry/fallback autorisé après connexion NotDispatched pour les deux writes
|
||||
```
|
||||
|
||||
Un test public vérifie la disponibilité des deux wrappers/configs à la racine de crate.
|
||||
|
||||
Une canarie release vérifie le sous-ensemble `pre.006` :
|
||||
|
||||
```text
|
||||
8 Read / RetrySafe
|
||||
2 WriteSubmission / NeverAfterDispatch
|
||||
simulateTransaction encore différée
|
||||
```
|
||||
|
||||
Cible après application :
|
||||
|
||||
```text
|
||||
175 unit tests
|
||||
18 public API tests
|
||||
12 release completeness tests
|
||||
```
|
||||
|
||||
## Fichiers du delta
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md
|
||||
```
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/request_airdrop.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/request_airdrop.error.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/send_transaction.success.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/send_transaction.preflight_error.json
|
||||
deltas/0.2.3/pre.006.md
|
||||
```
|
||||
|
||||
## Validations attendues opérateur
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test -p ksp-onchain-transport-lib
|
||||
```
|
||||
|
||||
L'environnement de préparation du delta ne possède pas Cargo/Rustfmt. Aucune de ces commandes n'est donc déclarée réussie avant preuve opérateur.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# Plan `0.2.3` — HTTP Transactions
|
||||
|
||||
@@ -589,6 +589,27 @@ Un candidat raisonnable pour la clôture est un smoke Transport pur combinant `g
|
||||
simulation live n'est retenue que si une fixture transactionnelle stable peut être fournie sans déplacer la construction/signature métier dans
|
||||
Transport.
|
||||
|
||||
## Audit rétroactif `KSP-TRANSPORT-007` planifié
|
||||
|
||||
La règle `KSP-TRANSPORT-007` formalise en `pre.005` un principe déjà appliqué dans les audits de `0.2.1` et `0.2.2`, mais elle n'était pas encore un
|
||||
critère de clôture nommé lors de ces releases. Un contrôle rétroactif dédié est donc ajouté avant la clôture documentaire.
|
||||
|
||||
L'audit `pre.008` couvrira au minimum :
|
||||
|
||||
```text
|
||||
4 wrappers foundation de 0.2.1
|
||||
22 wrappers Accounts/Tokens/Cluster de 0.2.2
|
||||
11 wrappers Transactions de 0.2.3
|
||||
```
|
||||
|
||||
Il comparera la surface publique KSP aux paramètres/configs/overloads et formes de réponse des sources normatives courantes retenues. Les syntaxes
|
||||
strictement équivalentes peuvent rester canonicalisées conformément à la règle. Toute possibilité supportée manquante sera corrigée dans cette tranche ;
|
||||
si aucun manque n'est trouvé, la tranche fournira la preuve/canarie d'audit sans modification fonctionnelle artificielle.
|
||||
|
||||
Un contrôle ciblé effectué pendant `pre.006` confirme déjà que les configs structurantes de `0.2.2` (`RpcAccountInfoConfig`,
|
||||
`RpcProgramAccountsConfig`, `RpcLargestAccountsConfig`, `RpcLeaderScheduleConfig`, `RpcGetVoteAccountsConfig`) correspondent aux champs Agave
|
||||
`v4.2.1` exposés par les DTOs KSP. Ce contrôle ne remplace pas l'audit exhaustif `pre.008`.
|
||||
|
||||
## Prévision souple des prereleases
|
||||
|
||||
```text
|
||||
@@ -599,11 +620,14 @@ pre.004 getRecentPrioritizationFees + getSignaturesForAddress + getSignatureSta
|
||||
pre.005 getTransaction moderne + compatibilité legacy + transaction/meta/version wire
|
||||
pre.006 requestAirdrop + sendTransaction + preuves end-to-end no-resend
|
||||
pre.007 simulateTransaction + résultat riche + invariants de config
|
||||
pre.008 réaudit final + canaries + smoke opt-in si pertinent + README/USAGE + validation + prompt 0.2.4
|
||||
pre.008 audit rétroactif KSP-TRANSPORT-007 sur 0.2.1 -> 0.2.3 + remédiations éventuelles
|
||||
pre.009 réaudit final + canaries + smoke opt-in si pertinent + README/USAGE + validation + prompt 0.2.4
|
||||
```
|
||||
|
||||
Une prerelease intermédiaire peut être ajoutée si le volume réel d'une tranche dépasse le budget. La dernière prerelease reste une tranche de
|
||||
clôture documentaire/validation et ne doit pas devenir une implémentation massive tardive.
|
||||
Une prerelease intermédiaire peut être ajoutée si le volume réel d'une tranche dépasse le budget. Après formalisation de `KSP-TRANSPORT-007` en
|
||||
`pre.005`, `pre.008` est désormais réservée à un audit rétroactif explicite des wrappers HTTP déjà livrés depuis `0.2.1`, afin de ne pas transformer la
|
||||
tranche de clôture en remédiation fonctionnelle tardive. La dernière prerelease reste une tranche de clôture documentaire/validation et ne doit pas
|
||||
devenir une implémentation massive tardive.
|
||||
|
||||
## Critères de clôture de `0.2.3`
|
||||
|
||||
@@ -683,3 +707,21 @@ La tranche suivante peut commencer par les primitives wire communes sans réouvr
|
||||
- aucune nouvelle dépendance Cargo et aucun wrapper `0.2.3` activé prématurément.
|
||||
|
||||
La tranche suivante reste `pre.003` : `getFeeForMessage`, `getLatestBlockhash`, `getTransactionCount` et `isBlockhashValid`.
|
||||
|
||||
## Résultat de `pre.006`
|
||||
|
||||
`pre.006` active les deux écritures Transaction sans introduire de chemin de soumission parallèle :
|
||||
|
||||
- `requestAirdrop` expose `Pubkey`, lamports, `commitment` et le `recentBlockhash` encore supporté par Agave `v4.2.1` ;
|
||||
- `sendTransaction` transporte une transaction déjà signée/encodée et expose `base58/base64`, `skipPreflight`, `preflightCommitment`, `maxRetries` et
|
||||
`minContextSlot` ;
|
||||
- `sendTransaction.maxRetries` reste explicitement node-side et ne modifie jamais `HttpRetrySettings` ;
|
||||
- les deux wrappers passent par leur descriptor central `WriteSubmission / NeverAfterDispatch` puis `execute_standard_rpc` ;
|
||||
- les fixtures end-to-end comptent les requêtes et prouvent une seule soumission après HTTP 429, HTTP 503 et timeout après dispatch ;
|
||||
- une connexion refusée classée `NotDispatched` peut encore utiliser la policy centrale de retry/fallback, démontrée pour les deux wrappers ;
|
||||
- les erreurs RPC applicatives, dont un échec de preflight `sendTransaction`, restent des erreurs applicatives sans retry transport ;
|
||||
- aucune dépendance transactionnelle/codec supplémentaire n'est ajoutée ;
|
||||
- `simulateTransaction` reste la seule méthode `0.2.3` non encore activée.
|
||||
|
||||
La prévision est ajustée : `pre.008` devient l'audit rétroactif `KSP-TRANSPORT-007` et la clôture finale est déplacée à `pre.009`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user