v0.2.3-pre.005
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 124
|
# version: 125
|
||||||
|
|
||||||
[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.3-pre.4"
|
version = "0.2.3-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 @@
|
|||||||
|
{"jsonrpc":"2.0","result":{"slot":431000061,"blockTime":1787000201,"transaction":["3Bxs4NN8M2Yn4TLb","base58"],"meta":null,"version":"legacy","transactionIndex":0},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":{"slot":431000062,"blockTime":1787000202,"transaction":["AQIDBAUGBwg=","base64"],"meta":null,"version":0,"transactionIndex":1},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":{"slot":431000060,"blockTime":null,"transaction":"3Bxs4NN8M2Yn4TLb","meta":null,"version":"legacy"},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","error":{"code":-32015,"message":"Transaction version (1) is not supported by the requesting client. Please try the request again with the following configuration parameter: \"maxSupportedTransactionVersion\": 1"},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":{"slot":431000063,"blockTime":1787000203,"transaction":{"signatures":["fixture-signature"],"message":{"header":{"numRequiredSignatures":1,"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":1},"accountKeys":["11111111111111111111111111111111","SysvarRent111111111111111111111111111111111"],"recentBlockhash":"11111111111111111111111111111111","instructions":[{"programIdIndex":0,"accounts":[0,1],"data":"3Bxs4NN8","stackHeight":1}],"addressTableLookups":null}},"meta":{"err":null,"status":{"Ok":null},"fee":5000,"preBalances":[100000,1],"postBalances":[95000,1],"innerInstructions":[],"logMessages":["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"preTokenBalances":[],"postTokenBalances":[],"rewards":null,"loadedAddresses":{"writable":[],"readonly":[]},"returnData":{"programId":"11111111111111111111111111111111","data":["AQID","base64"]},"computeUnitsConsumed":150,"costUnits":190},"version":0,"transactionIndex":7},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":{"slot":431000064,"blockTime":1787000204,"transaction":{"signatures":["fixture-signature-parsed"],"message":{"accountKeys":[{"pubkey":"11111111111111111111111111111111","signer":true,"writable":true,"source":"transaction"},{"pubkey":"SysvarRent111111111111111111111111111111111","signer":false,"writable":false,"source":"transaction"}],"recentBlockhash":"11111111111111111111111111111111","instructions":[{"program":"system","programId":"11111111111111111111111111111111","stackHeight":1,"parsed":{"type":"transfer","info":{"lamports":1,"source":"11111111111111111111111111111111","destination":"SysvarRent111111111111111111111111111111111"}}}]}},"meta":{"err":null,"status":{"Ok":null},"fee":5000,"preBalances":[100000,1],"postBalances":[95000,1],"innerInstructions":null,"logMessages":null,"preTokenBalances":[],"postTokenBalances":[],"rewards":null,"returnData":null,"computeUnitsConsumed":150,"costUnits":190},"version":"legacy","transactionIndex":null},"id":1}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"jsonrpc":"2.0","result":null,"id":1}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
/// Binary encoding accepted for serialized transaction input payloads.
|
/// Binary encoding accepted for serialized transaction input payloads.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
@@ -20,7 +20,6 @@ impl SolanaTransactionBinaryEncoding {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn from_wire(value: &str) -> std::option::Option<Self> {
|
fn from_wire(value: &str) -> std::option::Option<Self> {
|
||||||
return match value {
|
return match value {
|
||||||
"base58" => std::option::Option::Some(Self::Base58),
|
"base58" => std::option::Option::Some(Self::Base58),
|
||||||
@@ -167,7 +166,6 @@ impl SolanaGetTransactionConfig {
|
|||||||
|
|
||||||
/// Serializes the modern config to its Solana JSON-RPC wire object.
|
/// Serializes the modern config to its Solana JSON-RPC wire 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 {
|
||||||
@@ -827,7 +825,6 @@ pub enum SolanaEncodedTransaction {
|
|||||||
|
|
||||||
impl SolanaEncodedTransaction {
|
impl SolanaEncodedTransaction {
|
||||||
/// Decodes the untagged transaction wire union without decoding transaction bytes.
|
/// Decodes the untagged transaction wire union without decoding transaction bytes.
|
||||||
#[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> {
|
||||||
return match value {
|
return match value {
|
||||||
serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)),
|
serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)),
|
||||||
@@ -848,7 +845,6 @@ pub enum SolanaTransactionVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SolanaTransactionVersion {
|
impl SolanaTransactionVersion {
|
||||||
#[cfg(test)]
|
|
||||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||||
return match value {
|
return match value {
|
||||||
serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy),
|
serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy),
|
||||||
@@ -926,7 +922,6 @@ impl SolanaConfirmedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes one non-null confirmed transaction without decoding Program-specific transaction internals.
|
/// Decodes one non-null confirmed transaction without decoding Program-specific transaction internals.
|
||||||
#[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::<WireConfirmedTransaction>(method, value);
|
let decoded = crate::decode_wire_json::<WireConfirmedTransaction>(method, value);
|
||||||
let wire = match decoded {
|
let wire = match decoded {
|
||||||
@@ -1251,6 +1246,74 @@ impl crate::HttpTransportPool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the current object-form `getTransaction` request through the common KSP HTTP transport path.
|
||||||
|
///
|
||||||
|
/// `None` omits the optional second parameter. `Some(config)` sends the modern object form exactly, including an empty `{}` object when the
|
||||||
|
/// caller explicitly supplies an empty modern configuration object.
|
||||||
|
pub async fn get_transaction(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
signature: &str,
|
||||||
|
config: std::option::Option<&crate::SolanaGetTransactionConfig>,
|
||||||
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
||||||
|
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,
|
||||||
|
"getTransaction commitment must be confirmed or finalized when explicitly provided",
|
||||||
|
)
|
||||||
|
.with_context("rpc_method", "getTransaction")
|
||||||
|
.with_context("commitment", "processed"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut params = std::vec![serde_json::Value::String(signature.to_owned())];
|
||||||
|
if let std::option::Option::Some(config) = config {
|
||||||
|
params.push((*config).to_json_value());
|
||||||
|
}
|
||||||
|
return self.execute_get_transaction(role, params).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the deprecated bare-encoding `getTransaction` request form retained by Solana RPC for backwards compatibility.
|
||||||
|
#[deprecated(note = "use HttpTransportPool::get_transaction with SolanaGetTransactionConfig; the bare encoding request form is deprecated")]
|
||||||
|
pub async fn get_transaction_legacy(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
signature: &str,
|
||||||
|
encoding: crate::SolanaTransactionEncoding,
|
||||||
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
||||||
|
ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
rpc_method = "getTransaction",
|
||||||
|
request_form = "bare_encoding",
|
||||||
|
encoding = encoding.as_str(),
|
||||||
|
"deprecated Solana HTTP RPC request form used"
|
||||||
|
);
|
||||||
|
let params = std::vec![serde_json::Value::String(signature.to_owned()), serde_json::Value::String(encoding.as_str().to_owned()),];
|
||||||
|
return self.execute_get_transaction(role, params).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_get_transaction(
|
||||||
|
&self,
|
||||||
|
role: &crate::HttpRoleName,
|
||||||
|
params: std::vec::Vec<serde_json::Value>,
|
||||||
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
||||||
|
let value = self.execute_transaction_rpc("getTransaction", role, params).await;
|
||||||
|
let value = match value {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
if value.is_null() {
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
}
|
||||||
|
let transaction = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", value);
|
||||||
|
return match transaction {
|
||||||
|
std::result::Result::Ok(transaction) => std::result::Result::Ok(std::option::Option::Some(transaction)),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Executes typed `getTransactionCount` through the common KSP HTTP transport path.
|
/// Executes typed `getTransactionCount` through the common KSP HTTP transport path.
|
||||||
pub async fn get_transaction_count(
|
pub async fn get_transaction_count(
|
||||||
&self,
|
&self,
|
||||||
@@ -1450,7 +1513,6 @@ fn decode_confirmation_status(
|
|||||||
return std::result::Result::Ok(std::option::Option::Some(status));
|
return std::result::Result::Ok(std::option::Option::Some(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<crate::SolanaEncodedTransaction> {
|
fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<crate::SolanaEncodedTransaction> {
|
||||||
if values.len() != 2 {
|
if values.len() != 2 {
|
||||||
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple must contain data and encoding"));
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple must contain data and encoding"));
|
||||||
@@ -1490,7 +1552,6 @@ fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_jso
|
|||||||
return std::result::Result::Ok(crate::SolanaEncodedTransaction::Binary { data, encoding });
|
return std::result::Result::Ok(crate::SolanaEncodedTransaction::Binary { data, encoding });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn decode_transaction_version_field(
|
fn decode_transaction_version_field(
|
||||||
method: &str,
|
method: &str,
|
||||||
field: crate::SolanaWireField<serde_json::Value>,
|
field: crate::SolanaWireField<serde_json::Value>,
|
||||||
@@ -1597,7 +1658,6 @@ struct WireSignatureStatus {
|
|||||||
confirmation_status: std::option::Option<std::string::String>,
|
confirmation_status: std::option::Option<std::string::String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WireConfirmedTransaction {
|
struct WireConfirmedTransaction {
|
||||||
|
|||||||
@@ -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: 12
|
// version: 13
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -285,3 +285,18 @@ fn public_transaction_pre_004_additional_read_wrappers_are_available_from_crate_
|
|||||||
let _get_signatures_for_address = ksp_onchain_transport_lib::HttpTransportPool::get_signatures_for_address;
|
let _get_signatures_for_address = ksp_onchain_transport_lib::HttpTransportPool::get_signatures_for_address;
|
||||||
let _get_signature_statuses = ksp_onchain_transport_lib::HttpTransportPool::get_signature_statuses;
|
let _get_signature_statuses = ksp_onchain_transport_lib::HttpTransportPool::get_signature_statuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
#[test]
|
||||||
|
fn public_transaction_pre_005_get_transaction_complete_request_forms_are_available_from_crate_root() {
|
||||||
|
let _get_transaction = ksp_onchain_transport_lib::HttpTransportPool::get_transaction;
|
||||||
|
let _get_transaction_legacy = ksp_onchain_transport_lib::HttpTransportPool::get_transaction_legacy;
|
||||||
|
let config = ksp_onchain_transport_lib::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed),
|
||||||
|
std::option::Option::Some(0),
|
||||||
|
);
|
||||||
|
assert_eq!(config.commitment(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed));
|
||||||
|
assert_eq!(config.encoding(), std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed));
|
||||||
|
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(0));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 9
|
// version: 10
|
||||||
|
|
||||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||||
|
|
||||||
@@ -291,3 +291,38 @@ fn release_pre_004_transaction_read_subset_is_exact_and_retry_safe() {
|
|||||||
assert_eq!(actual.len(), 7);
|
assert_eq!(actual.len(), 7);
|
||||||
assert_eq!(deferred.len(), 4);
|
assert_eq!(deferred.len(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_pre_005_transaction_read_subset_adds_complete_get_transaction_without_advancing_writes_or_simulation() {
|
||||||
|
let mut expected = std::vec![
|
||||||
|
"getFeeForMessage",
|
||||||
|
"getLatestBlockhash",
|
||||||
|
"getRecentPrioritizationFees",
|
||||||
|
"getSignaturesForAddress",
|
||||||
|
"getSignatureStatuses",
|
||||||
|
"getTransaction",
|
||||||
|
"getTransactionCount",
|
||||||
|
"isBlockhashValid",
|
||||||
|
];
|
||||||
|
let deferred = std::vec!["requestAirdrop", "sendTransaction", "simulateTransaction"];
|
||||||
|
let mut actual = std::vec::Vec::new();
|
||||||
|
for method_name in &expected {
|
||||||
|
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("pre.005 Transaction descriptor must exist");
|
||||||
|
actual.push(descriptor.method());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
let get_transaction = ksp_onchain_transport_lib::find_http_rpc_method("getTransaction").expect("getTransaction descriptor must exist");
|
||||||
|
assert!(get_transaction.request_form_status().has_deprecated_legacy());
|
||||||
|
for method_name in &deferred {
|
||||||
|
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name).expect("deferred Transaction descriptor must remain registered");
|
||||||
|
assert_eq!(descriptor.coverage_release(), ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3);
|
||||||
|
}
|
||||||
|
actual.sort_unstable();
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert_eq!(actual.len(), 8);
|
||||||
|
assert_eq!(deferred.len(), 3);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
|
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
|
||||||
@@ -686,3 +686,191 @@ async fn typed_get_signature_statuses_rejects_response_count_mismatch() {
|
|||||||
assert_eq!(error.context()[2].value(), "1");
|
assert_eq!(error.context()[2].value(), "1");
|
||||||
handle.join().expect("fixture server must join");
|
handle.join().expect("fixture server must join");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_modern_supports_omitted_and_explicit_empty_config() {
|
||||||
|
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.null.json"));
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let result = pool
|
||||||
|
.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::None)
|
||||||
|
.await
|
||||||
|
.expect("getTransaction without config must succeed");
|
||||||
|
assert!(result.is_none());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["fixture-signature"]));
|
||||||
|
|
||||||
|
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.null.json"));
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let config = crate::SolanaGetTransactionConfig::default();
|
||||||
|
let result = pool
|
||||||
|
.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("getTransaction with explicit empty modern config must succeed");
|
||||||
|
assert!(result.is_none());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(transaction_request_body(request.as_str())["params"], serde_json::json!(["fixture-signature", {}]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_modern_serializes_full_config_and_preserves_rich_json_parsed_wire() {
|
||||||
|
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.json_parsed.json"));
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
|
||||||
|
std::option::Option::Some(0),
|
||||||
|
);
|
||||||
|
let result = pool
|
||||||
|
.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("modern getTransaction fixture must succeed")
|
||||||
|
.expect("modern getTransaction fixture must contain a transaction");
|
||||||
|
assert_eq!(result.slot(), 431_000_064);
|
||||||
|
assert_eq!(result.block_time(), std::option::Option::Some(1_787_000_204));
|
||||||
|
assert_eq!(result.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Legacy));
|
||||||
|
assert!(result.transaction_index().is_null());
|
||||||
|
let transaction = match result.transaction() {
|
||||||
|
crate::SolanaEncodedTransaction::Json(value) => value,
|
||||||
|
_ => panic!("jsonParsed response must remain a JSON object"),
|
||||||
|
};
|
||||||
|
assert_eq!(transaction["message"]["accountKeys"][0]["source"], serde_json::json!("transaction"));
|
||||||
|
assert_eq!(transaction["message"]["instructions"][0]["parsed"]["type"], serde_json::json!("transfer"));
|
||||||
|
let meta = result.meta().value().expect("metadata must be present");
|
||||||
|
assert_eq!(meta["computeUnitsConsumed"], serde_json::json!(150));
|
||||||
|
assert_eq!(meta["costUnits"], serde_json::json!(190));
|
||||||
|
assert!(meta["loadedAddresses"].is_null());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = transaction_request_body(request.as_str());
|
||||||
|
assert_eq!(body["method"], serde_json::json!("getTransaction"));
|
||||||
|
assert_eq!(
|
||||||
|
body["params"],
|
||||||
|
serde_json::json!([
|
||||||
|
"fixture-signature",
|
||||||
|
{"commitment":"confirmed","encoding":"jsonParsed","maxSupportedTransactionVersion":0}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_modern_covers_all_agave_v4_2_1_encoding_labels_and_response_shapes() {
|
||||||
|
let cases = [
|
||||||
|
(crate::SolanaTransactionEncoding::Binary, include_str!("../fixtures/http/get_transaction.binary_legacy.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Base58, include_str!("../fixtures/http/get_transaction.base58.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Base64, include_str!("../fixtures/http/get_transaction.base64.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Json, include_str!("../fixtures/http/get_transaction.json.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::JsonParsed, include_str!("../fixtures/http/get_transaction.json_parsed.json")),
|
||||||
|
];
|
||||||
|
for (encoding, fixture) in cases {
|
||||||
|
let (url, handle) = serve_transaction_once(fixture);
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(std::option::Option::None, std::option::Option::Some(encoding), std::option::Option::None);
|
||||||
|
let result = pool
|
||||||
|
.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("encoding-specific modern getTransaction must succeed")
|
||||||
|
.expect("encoding-specific modern getTransaction must contain a transaction");
|
||||||
|
match encoding {
|
||||||
|
crate::SolanaTransactionEncoding::Binary => assert!(matches!(result.transaction(), crate::SolanaEncodedTransaction::LegacyBinary(_))),
|
||||||
|
crate::SolanaTransactionEncoding::Base58 => assert!(matches!(
|
||||||
|
result.transaction(),
|
||||||
|
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base58, .. }
|
||||||
|
)),
|
||||||
|
crate::SolanaTransactionEncoding::Base64 => assert!(matches!(
|
||||||
|
result.transaction(),
|
||||||
|
crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. }
|
||||||
|
)),
|
||||||
|
crate::SolanaTransactionEncoding::Json | crate::SolanaTransactionEncoding::JsonParsed => {
|
||||||
|
assert!(matches!(result.transaction(), crate::SolanaEncodedTransaction::Json(_)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = transaction_request_body(request.as_str());
|
||||||
|
assert_eq!(body["params"], serde_json::json!(["fixture-signature", {"encoding":encoding.as_str()}]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_legacy_covers_all_bare_encoding_labels() {
|
||||||
|
let cases = [
|
||||||
|
(crate::SolanaTransactionEncoding::Binary, include_str!("../fixtures/http/get_transaction.binary_legacy.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Base58, include_str!("../fixtures/http/get_transaction.base58.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Base64, include_str!("../fixtures/http/get_transaction.base64.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::Json, include_str!("../fixtures/http/get_transaction.json.json")),
|
||||||
|
(crate::SolanaTransactionEncoding::JsonParsed, include_str!("../fixtures/http/get_transaction.json_parsed.json")),
|
||||||
|
];
|
||||||
|
for (encoding, fixture) in cases {
|
||||||
|
let (url, handle) = serve_transaction_once(fixture);
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let result = pool
|
||||||
|
.get_transaction_legacy(&crate::HttpRoleName::new("default"), "fixture-signature", encoding)
|
||||||
|
.await
|
||||||
|
.expect("legacy bare-encoding getTransaction must succeed");
|
||||||
|
assert!(result.is_some());
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
let body = transaction_request_body(request.as_str());
|
||||||
|
assert_eq!(body["method"], serde_json::json!("getTransaction"));
|
||||||
|
assert_eq!(body["params"], serde_json::json!(["fixture-signature", encoding.as_str()]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_preserves_raw_json_meta_version_and_transaction_index() {
|
||||||
|
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.json.json"));
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionEncoding::Json),
|
||||||
|
std::option::Option::Some(0),
|
||||||
|
);
|
||||||
|
let result = pool
|
||||||
|
.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config))
|
||||||
|
.await
|
||||||
|
.expect("raw JSON getTransaction must succeed")
|
||||||
|
.expect("raw JSON getTransaction must contain a transaction");
|
||||||
|
assert_eq!(result.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Number(0)));
|
||||||
|
assert_eq!(result.transaction_index().value(), std::option::Option::Some(&7));
|
||||||
|
let meta = result.meta().value().expect("metadata must be present");
|
||||||
|
assert_eq!(meta["status"], serde_json::json!({"Ok":null}));
|
||||||
|
assert_eq!(meta["loadedAddresses"]["writable"], serde_json::json!([]));
|
||||||
|
assert_eq!(meta["returnData"]["data"], serde_json::json!(["AQID", "base64"]));
|
||||||
|
assert_eq!(meta["costUnits"], serde_json::json!(190));
|
||||||
|
let request = handle.join().expect("fixture server must join");
|
||||||
|
assert_eq!(
|
||||||
|
transaction_request_body(request.as_str())["params"],
|
||||||
|
serde_json::json!([
|
||||||
|
"fixture-signature",
|
||||||
|
{"commitment":"finalized","encoding":"json","maxSupportedTransactionVersion":0}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_preserves_unsupported_version_rpc_error() {
|
||||||
|
let (url, handle) = serve_transaction_once(include_str!("../fixtures/http/get_transaction.error_unsupported_version.json"));
|
||||||
|
let pool = transaction_pool_for_url(url.as_str());
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionEncoding::Base64),
|
||||||
|
std::option::Option::Some(0),
|
||||||
|
);
|
||||||
|
let result = pool.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config)).await;
|
||||||
|
let error = result.expect_err("unsupported transaction version must remain an RPC application error");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
|
||||||
|
handle.join().expect("fixture server must join");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn typed_get_transaction_rejects_processed_commitment_before_io() {
|
||||||
|
let pool = transaction_pool_for_url("http://127.0.0.1:9");
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Processed),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionEncoding::Json),
|
||||||
|
std::option::Option::None,
|
||||||
|
);
|
||||||
|
let result = pool.get_transaction(&crate::HttpRoleName::new("default"), "fixture-signature", std::option::Option::Some(&config)).await;
|
||||||
|
let error = result.expect_err("processed getTransaction 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");
|
||||||
|
}
|
||||||
|
|||||||
408
deltas/0.2.3/pre.005.md
Normal file
408
deltas/0.2.3/pre.005.md
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
<!-- file: deltas/0.2.3/pre.005.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.3-pre.005` — `getTransaction` complet, forme moderne et compatibilité legacy
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
Livraison précédente validée localement par l'opérateur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.3-pre.004
|
||||||
|
workspace.package.version = "0.2.3-pre.4"
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
161 unit tests
|
||||||
|
16 public API tests
|
||||||
|
10 release completeness tests
|
||||||
|
0 warning signalé par check/clippy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Implémenter `getTransaction` comme huitième wrapper Transaction `Read / RetrySafe` de `0.2.3`, sans réduire la méthode à un sous-ensemble de
|
||||||
|
convenance.
|
||||||
|
|
||||||
|
La tranche couvre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
forme moderne objet
|
||||||
|
forme bare encoding legacy dépréciée
|
||||||
|
commitment
|
||||||
|
encoding
|
||||||
|
maxSupportedTransactionVersion
|
||||||
|
result = null
|
||||||
|
transaction chaîne legacy
|
||||||
|
transaction tuple base58/base64
|
||||||
|
transaction JSON/jsonParsed
|
||||||
|
meta lossless
|
||||||
|
version legacy/numérique/omise/null
|
||||||
|
transactionIndex valeur/omission/null
|
||||||
|
erreurs RPC applicatives
|
||||||
|
```
|
||||||
|
|
||||||
|
Après cette tranche :
|
||||||
|
|
||||||
|
```text
|
||||||
|
8 / 11 wrappers Transactions exécutés
|
||||||
|
```
|
||||||
|
|
||||||
|
Restent différés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
requestAirdrop
|
||||||
|
sendTransaction
|
||||||
|
simulateTransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
## Règle générale de complétude RPC
|
||||||
|
|
||||||
|
La précision opérateur formulée avant cette tranche devient une règle durable : un wrapper de transport KSP n'est pas considéré complet lorsqu'il
|
||||||
|
n'expose qu'un sous-ensemble arbitraire des possibilités d'une méthode RPC.
|
||||||
|
|
||||||
|
`KSP-TRANSPORT-007` est ajouté dans `docs/rules/RULES_KSP.md` :
|
||||||
|
|
||||||
|
- tous les paramètres et champs de config audités doivent rester accessibles ;
|
||||||
|
- les variantes/overloads courants doivent être exposés ;
|
||||||
|
- les formes legacy encore supportées restent accessibles avec statut explicite ;
|
||||||
|
- les contraintes déterministes connues sont appliquées localement lorsque KSP peut le faire sans prendre une responsabilité wire étrangère ;
|
||||||
|
- les variantes, `null` et omissions significatives de réponse sont préservés ;
|
||||||
|
- un sous-arbre riche peut rester `serde_json::Value` lorsqu'il est conservé losslessly ;
|
||||||
|
- toute limitation volontaire doit être explicitement documentée.
|
||||||
|
|
||||||
|
`docs/architecture/003-COMPONENT_CONTRACTS.md` est synchronisé avec cette règle.
|
||||||
|
|
||||||
|
Le plan `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md` passe en version 4 et demande désormais une canarie de complétude sémantique pour les
|
||||||
|
wrappers de la release, pas uniquement une canarie du nombre de méthodes.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Conformément à `VER-ID-009` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.3-pre.4 -> 0.2.3-pre.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance ni feature Cargo n'est ajoutée.
|
||||||
|
|
||||||
|
## Surface moderne `getTransaction`
|
||||||
|
|
||||||
|
API publique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_transaction(
|
||||||
|
role,
|
||||||
|
signature,
|
||||||
|
Option<&SolanaGetTransactionConfig>,
|
||||||
|
)
|
||||||
|
-> Result<Option<SolanaConfirmedTransaction>>
|
||||||
|
```
|
||||||
|
|
||||||
|
La signature reste une chaîne base58 opaque pour Transport : KSP ne prend pas de dépendance `solana-signature` uniquement pour répliquer la
|
||||||
|
validation du provider.
|
||||||
|
|
||||||
|
Le second paramètre possède trois usages intentionnels :
|
||||||
|
|
||||||
|
```text
|
||||||
|
None -> second paramètre omis
|
||||||
|
Some(default config) -> objet moderne explicite {}
|
||||||
|
Some(config) -> objet moderne avec tous les champs fournis
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette distinction permet d'exposer réellement la forme objet moderne sans empêcher la forme sans second paramètre.
|
||||||
|
|
||||||
|
`SolanaGetTransactionConfig` conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment
|
||||||
|
encoding
|
||||||
|
maxSupportedTransactionVersion
|
||||||
|
```
|
||||||
|
|
||||||
|
La documentation publique courante borne `commitment` à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
confirmed
|
||||||
|
finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
`processed` est donc rejeté localement avec `ERROR_CODE_INVALID_RPC_PARAMETERS` avant I/O.
|
||||||
|
|
||||||
|
`maxSupportedTransactionVersion` reste un `Option<u8>` plutôt qu'une constante figée à `0`, afin de ne pas fermer la surface KSP aux futures
|
||||||
|
versions wire que le runtime pourra supporter.
|
||||||
|
|
||||||
|
## Encodings complets
|
||||||
|
|
||||||
|
La documentation publique moderne expose :
|
||||||
|
|
||||||
|
```text
|
||||||
|
base58
|
||||||
|
base64
|
||||||
|
json
|
||||||
|
jsonParsed
|
||||||
|
```
|
||||||
|
|
||||||
|
Agave `v4.2.1` utilise encore `UiTransactionEncoding` pour `RpcTransactionConfig` et accepte également l'alias historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
binary
|
||||||
|
```
|
||||||
|
|
||||||
|
KSP conserve donc les cinq labels dans `SolanaTransactionEncoding` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Binary
|
||||||
|
Base58
|
||||||
|
Base64
|
||||||
|
Json
|
||||||
|
JsonParsed
|
||||||
|
```
|
||||||
|
|
||||||
|
`Binary` est une compatibilité runtime/legacy et n'est pas présenté comme le choix moderne recommandé.
|
||||||
|
|
||||||
|
Les cinq labels sont exercés par le wrapper moderne afin de garantir que KSP ne filtre pas une possibilité supportée par le runtime ciblé.
|
||||||
|
|
||||||
|
## Forme legacy bare encoding
|
||||||
|
|
||||||
|
La documentation Solana courante conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getTransaction(signature, "<encoding>")
|
||||||
|
```
|
||||||
|
|
||||||
|
mais marque explicitement cette forme bare comme dépréciée.
|
||||||
|
|
||||||
|
KSP expose donc séparément :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool::get_transaction_legacy(role, signature, encoding)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette API :
|
||||||
|
|
||||||
|
- est annotée `#[deprecated]` côté Rust ;
|
||||||
|
- émet un `warn` via `ksp-logging-lib` lorsqu'elle est appelée ;
|
||||||
|
- sérialise exactement la chaîne d'encoding comme second paramètre ;
|
||||||
|
- accepte les cinq labels du runtime `v4.2.1` ;
|
||||||
|
- réutilise exactement le même executor/décodeur que la forme moderne.
|
||||||
|
|
||||||
|
Le wrapper legacy ne crée donc ni nouveau client HTTP ni logique de retry parallèle.
|
||||||
|
|
||||||
|
## Réponse complète et lossless
|
||||||
|
|
||||||
|
`getTransaction` renvoie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Option<SolanaConfirmedTransaction>
|
||||||
|
```
|
||||||
|
|
||||||
|
`result = null` devient `None` sans être confondu avec une erreur RPC.
|
||||||
|
|
||||||
|
Un résultat présent conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
slot
|
||||||
|
blockTime
|
||||||
|
transaction
|
||||||
|
meta
|
||||||
|
version
|
||||||
|
transactionIndex
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transaction
|
||||||
|
|
||||||
|
`SolanaEncodedTransaction` devient production-live et conserve les formes Agave :
|
||||||
|
|
||||||
|
```text
|
||||||
|
LegacyBinary(String)
|
||||||
|
Binary { data, Base58|Base64 }
|
||||||
|
Json(serde_json::Value)
|
||||||
|
```
|
||||||
|
|
||||||
|
La variante `Json` couvre `json` et `jsonParsed` sans réimplémenter les structures Program-specific du SDK Solana. Aucun champ n'est supprimé.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
`meta` reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaWireField<serde_json::Value>
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette représentation conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Omitted
|
||||||
|
Null
|
||||||
|
Value(object)
|
||||||
|
```
|
||||||
|
|
||||||
|
La fixture riche couvre notamment les champs actuels :
|
||||||
|
|
||||||
|
```text
|
||||||
|
err
|
||||||
|
status legacy
|
||||||
|
fee
|
||||||
|
preBalances
|
||||||
|
postBalances
|
||||||
|
innerInstructions
|
||||||
|
logMessages
|
||||||
|
preTokenBalances
|
||||||
|
postTokenBalances
|
||||||
|
rewards
|
||||||
|
loadedAddresses
|
||||||
|
returnData
|
||||||
|
computeUnitsConsumed
|
||||||
|
costUnits
|
||||||
|
```
|
||||||
|
|
||||||
|
Le choix lossless permet aussi de transporter de nouveaux champs provider/Agave sans les effacer avant qu'un modèle KSP plus spécialisé soit
|
||||||
|
justifié.
|
||||||
|
|
||||||
|
### Version et transaction index
|
||||||
|
|
||||||
|
`version` conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Omitted
|
||||||
|
Null
|
||||||
|
Legacy
|
||||||
|
Number(u8)
|
||||||
|
```
|
||||||
|
|
||||||
|
`transactionIndex`, présent dans Agave `v4.2.1` mais pas historiquement chez tous les providers, conserve également :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Omitted
|
||||||
|
Null
|
||||||
|
Value(u32)
|
||||||
|
```
|
||||||
|
|
||||||
|
Les fixtures préparatoires de `pre.002` qui vérifient déjà ces trois états restent actives.
|
||||||
|
|
||||||
|
## Helpers activés en production
|
||||||
|
|
||||||
|
Seuls les helpers nécessaires à `getTransaction` quittent `#[cfg(test)]` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaTransactionBinaryEncoding::from_wire
|
||||||
|
SolanaGetTransactionConfig::to_json_value
|
||||||
|
SolanaEncodedTransaction::decode_wire
|
||||||
|
SolanaTransactionVersion::decode_wire
|
||||||
|
SolanaConfirmedTransaction::decode_wire
|
||||||
|
decode_binary_transaction_tuple
|
||||||
|
decode_transaction_version_field
|
||||||
|
WireConfirmedTransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
Les helpers write/simulation de `pre.006`–`pre.007` restent staged/test-only. Aucun `#[allow(dead_code)]` global n'est ajouté.
|
||||||
|
|
||||||
|
## Fixtures HTTP déterministes ajoutées
|
||||||
|
|
||||||
|
```text
|
||||||
|
get_transaction.null.json
|
||||||
|
get_transaction.binary_legacy.json
|
||||||
|
get_transaction.base58.json
|
||||||
|
get_transaction.base64.json
|
||||||
|
get_transaction.json.json
|
||||||
|
get_transaction.json_parsed.json
|
||||||
|
get_transaction.error_unsupported_version.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles couvrent les formes de résultat et les encodings sans décoder les bytes transactionnels.
|
||||||
|
|
||||||
|
## Couverture de tests ajoutée
|
||||||
|
|
||||||
|
Sept tests unitaires HTTP supplémentaires couvrent :
|
||||||
|
|
||||||
|
- second paramètre omis et objet moderne vide explicite ;
|
||||||
|
- config moderne complète ;
|
||||||
|
- rejet local de `commitment=processed` ;
|
||||||
|
- cinq encodings runtime via la forme moderne ;
|
||||||
|
- cinq encodings via la forme bare legacy dépréciée ;
|
||||||
|
- préservation d'un `jsonParsed` riche ;
|
||||||
|
- préservation du JSON brut, du meta courant, de la version et de `transactionIndex` ;
|
||||||
|
- erreur RPC `unsupported transaction version` préservée comme `RPC_APPLICATION_ERROR`.
|
||||||
|
|
||||||
|
Les tests préparatoires existants continuent en plus à couvrir :
|
||||||
|
|
||||||
|
- tuple avec encoding non binaire rejeté ;
|
||||||
|
- forme de version inconnue rejetée ;
|
||||||
|
- `meta/version/transactionIndex` omis/null/présents.
|
||||||
|
|
||||||
|
Un test public compile les deux request forms depuis la crate root.
|
||||||
|
|
||||||
|
Une nouvelle canarie release porte le sous-ensemble Read Transaction exécuté à huit méthodes et vérifie que `getTransaction` conserve
|
||||||
|
`StableWithDeprecatedLegacy` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getFeeForMessage
|
||||||
|
getLatestBlockhash
|
||||||
|
getRecentPrioritizationFees
|
||||||
|
getSignaturesForAddress
|
||||||
|
getSignatureStatuses
|
||||||
|
getTransaction
|
||||||
|
getTransactionCount
|
||||||
|
isBlockhashValid
|
||||||
|
```
|
||||||
|
|
||||||
|
Les trois opérations write/simulation restent différées.
|
||||||
|
|
||||||
|
Après application, la cible Transport attendue devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
168 unit tests
|
||||||
|
17 public API tests
|
||||||
|
11 release completeness tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
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/rules/RULES_KSP.md
|
||||||
|
docs/architecture/003-COMPONENT_CONTRACTS.md
|
||||||
|
docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Fichiers ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.null.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.binary_legacy.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.base58.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.base64.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.json.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.json_parsed.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/get_transaction.error_unsupported_version.json
|
||||||
|
deltas/0.2.3/pre.005.md
|
||||||
|
```
|
||||||
|
|
||||||
|
`CHANGELOG.md`, `ROADMAP.md`, `rpc_method.rs`, `executor.rs` et `resilience.rs` restent inchangés.
|
||||||
|
|
||||||
|
## Validations attendues
|
||||||
|
|
||||||
|
```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 ; ces commandes ne sont donc pas déclarées réussies avant preuve opérateur.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
|
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Contrats initiaux des composants KSP
|
# Contrats initiaux des composants KSP
|
||||||
|
|
||||||
@@ -66,6 +66,8 @@ Il ne dépend pas de Config, Store ou Program.
|
|||||||
|
|
||||||
Pour toute surface normative ciblée, toutes les méthodes documentées sont inventoriées/implémentées sauf impossibilité documentée. Les méthodes deprecated/obsolete encore fonctionnelles et unstable/experimental émettent un warning KSP à l'utilisation.
|
Pour toute surface normative ciblée, toutes les méthodes documentées sont inventoriées/implémentées sauf impossibilité documentée. Les méthodes deprecated/obsolete encore fonctionnelles et unstable/experimental émettent un warning KSP à l'utilisation.
|
||||||
|
|
||||||
|
La complétude vaut aussi à l'intérieur de chaque opération : paramètres, options, overloads et formes legacy supportées sont exposés, et les variantes de réponse sont préservées sans perte. KSP n'est pas tenu de dupliquer un SDK externe lorsque des sous-arbres wire lossless suffisent.
|
||||||
|
|
||||||
## Transport off-chain
|
## Transport off-chain
|
||||||
|
|
||||||
Aucune `ksp-offchain-transport-api` commune n'est prévue.
|
Aucune `ksp-offchain-transport-api` commune n'est prévue.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md -->
|
<!-- file: docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md -->
|
||||||
<!-- version: 3 -->
|
<!-- version: 4 -->
|
||||||
|
|
||||||
# Plan `0.2.3` — HTTP Transactions
|
# Plan `0.2.3` — HTTP Transactions
|
||||||
|
|
||||||
@@ -217,6 +217,30 @@ fige donc pas artificiellement le type public sur une liste de versions sériali
|
|||||||
Pour la **sortie** `getTransaction`, le wire doit rester plus large : `base58`, `base64`, `json`, `jsonParsed`, ainsi que les formes legacy encore
|
Pour la **sortie** `getTransaction`, le wire doit rester plus large : `base58`, `base64`, `json`, `jsonParsed`, ainsi que les formes legacy encore
|
||||||
acceptées/auditées. Les formes encodées et JSON ne doivent pas être écrasées dans un seul `String` ambigu.
|
acceptées/auditées. Les formes encodées et JSON ne doivent pas être écrasées dans un seul `String` ambigu.
|
||||||
|
|
||||||
|
## Règle de complétude des wrappers RPC
|
||||||
|
|
||||||
|
À partir de `pre.005`, la règle générale `KSP-TRANSPORT-007` explicite un principe déjà recherché par les releases HTTP précédentes : un wrapper KSP
|
||||||
|
n'est pas considéré complet s'il ne couvre qu'un sous-ensemble de convenance de la méthode RPC. Il doit exposer toute la surface sémantique auditée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
paramètres obligatoires et optionnels
|
||||||
|
champs de configuration
|
||||||
|
variantes/overloads courants
|
||||||
|
formes legacy encore supportées
|
||||||
|
contraintes déterministes connues
|
||||||
|
variantes/null/omissions significatives des réponses
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette exigence ne signifie pas recopier `solana-rpc-client` ou tout `solana-transaction-status-client-types`. Une structure riche peut rester un
|
||||||
|
`serde_json::Value` à une frontière explicitement lossless lorsque KSP n'a pas encore besoin de son modèle métier, à condition qu'aucune possibilité
|
||||||
|
RPC ni information wire ne soit supprimée. Les syntaxes strictement équivalentes peuvent être canonicalisées.
|
||||||
|
|
||||||
|
Pour `getTransaction`, `pre.005` couvre donc les deux formes de requête auditée : objet moderne et bare encoding legacy déprécié. La documentation
|
||||||
|
publique courante expose `base58`, `base64`, `json` et `jsonParsed` pour l'objet moderne ; Agave `v4.2.1` accepte aussi l'alias rétrocompatible
|
||||||
|
`binary` via le même enum wire, que KSP conserve donc sans le présenter comme choix moderne recommandé. La config complète
|
||||||
|
`commitment/encoding/maxSupportedTransactionVersion` est exposée, avec rejet local de `processed` car la méthode documente `confirmed|finalized`. Le résultat
|
||||||
|
`null`, les transactions chaîne/tuple/objet, les versions `legacy`/numériques, `meta` lossless et `transactionIndex` optionnel courant sont préservés.
|
||||||
|
|
||||||
## Matrice exacte des 11 méthodes
|
## Matrice exacte des 11 méthodes
|
||||||
|
|
||||||
| Méthode | Paramètres ordonnés / config | Résultat à préserver | Validation / particularités |
|
| Méthode | Paramètres ordonnés / config | Résultat à préserver | Validation / particularités |
|
||||||
@@ -586,6 +610,7 @@ clôture documentaire/validation et ne doit pas devenir une implémentation mass
|
|||||||
La release ne peut être candidate stable que si :
|
La release ne peut être candidate stable que si :
|
||||||
|
|
||||||
- les 11 wrappers sont publics et passent tous par `execute_standard_rpc` ;
|
- les 11 wrappers sont publics et passent tous par `execute_standard_rpc` ;
|
||||||
|
- chaque wrapper couvre tous les paramètres/options/overloads normatifs ou runtime supportés retenus par l'audit, selon `KSP-TRANSPORT-007` ;
|
||||||
- les trois classes de sécurité restent exactes `8 Read / 2 WriteSubmission / 1 Simulation` ;
|
- les trois classes de sécurité restent exactes `8 Read / 2 WriteSubmission / 1 Simulation` ;
|
||||||
- `requestAirdrop` et `sendTransaction` prouvent l'absence de resend automatique après dispatch ambigu ;
|
- `requestAirdrop` et `sendTransaction` prouvent l'absence de resend automatique après dispatch ambigu ;
|
||||||
- `getTransaction` préserve la forme moderne et la compatibilité legacy explicitement dépréciée ;
|
- `getTransaction` préserve la forme moderne et la compatibilité legacy explicitement dépréciée ;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_KSP.md -->
|
<!-- file: docs/rules/RULES_KSP.md -->
|
||||||
<!-- version: 32 -->
|
<!-- version: 33 -->
|
||||||
|
|
||||||
# Règles spécifiques à KSP
|
# Règles spécifiques à KSP
|
||||||
|
|
||||||
@@ -255,5 +255,6 @@
|
|||||||
- **KSP-REL-015** — Une application Tauri complétée ne crée `PRESENTATION.md` que si elle possède réellement une vue de présentation embarquée ; dans ce cas le fichier est finalisé comme contenu UI sans liens navigables et reste distinct du `README.md` et du `USAGE.md`.
|
- **KSP-REL-015** — Une application Tauri complétée ne crée `PRESENTATION.md` que si elle possède réellement une vue de présentation embarquée ; dans ce cas le fichier est finalisé comme contenu UI sans liens navigables et reste distinct du `README.md` et du `USAGE.md`.
|
||||||
- **KSP-REL-016** — Une prerelease vise environ 15 à 20 minutes de travail effectif. Le `pre.001` dimensionne aussi la release concrète entière : une release doit pouvoir être ouverte, développée, validée et clôturée dans une seule session de chat. Si cette clôture paraît incertaine, la release est scindée avant l'implémentation fonctionnelle lourde ; une version volontairement répartie sur plusieurs sessions est interdite.
|
- **KSP-REL-016** — Une prerelease vise environ 15 à 20 minutes de travail effectif. Le `pre.001` dimensionne aussi la release concrète entière : une release doit pouvoir être ouverte, développée, validée et clôturée dans une seule session de chat. Si cette clôture paraît incertaine, la release est scindée avant l'implémentation fonctionnelle lourde ; une version volontairement répartie sur plusieurs sessions est interdite.
|
||||||
- **KSP-TRANSPORT-006** — Pour une surface de transport explicitement ciblée, KSP inventorie et implémente toutes les méthodes/opérations exposées par la documentation normative retenue, sauf impossibilité technique explicitement documentée. L'inventaire couvre aussi les sections officielles séparées `deprecated`/`obsolete` et `unstable`/`experimental` lorsqu'elles existent. Les opérations deprecated/obsolete encore réellement fonctionnelles et unstable/experimental restent utilisables mais émettent un `warn` via `ksp-logging-lib` à chaque utilisation concernée ; leur statut est décrit par une metadata centralisée et non par des warnings dispersés.
|
- **KSP-TRANSPORT-006** — Pour une surface de transport explicitement ciblée, KSP inventorie et implémente toutes les méthodes/opérations exposées par la documentation normative retenue, sauf impossibilité technique explicitement documentée. L'inventaire couvre aussi les sections officielles séparées `deprecated`/`obsolete` et `unstable`/`experimental` lorsqu'elles existent. Les opérations deprecated/obsolete encore réellement fonctionnelles et unstable/experimental restent utilisables mais émettent un `warn` via `ksp-logging-lib` à chaque utilisation concernée ; leur statut est décrit par une metadata centralisée et non par des warnings dispersés.
|
||||||
|
- **KSP-TRANSPORT-007** — La complétude d'un wrapper de transport standard couvre toute la surface sémantique de requête auditée : paramètres, options de configuration, variantes/overloads courants, formes legacy encore supportées et contraintes déterministes connues. Les formes de réponse pertinentes sont conservées losslessly, y compris les variantes, `null` et omissions significatives. KSP peut canonicaliser des syntaxes strictement équivalentes et conserver des sous-arbres wire riches via `serde_json::Value` tant qu'aucune information n'est perdue ; toute limitation volontaire d'une possibilité normative/runtime supportée doit être explicitement justifiée et documentée.
|
||||||
- **KSP-FLOW-001** — La progression durable canonique est `RAW -> CORE -> DECODE -> SPECIALIZED`. RAW et CORE ne nécessitent aucun decoder Program ; le passage RAW -> CORE reste une normalisation générique de la blockchain Solana. À partir de DECODE, KSP progresse verticalement par groupe fonctionnel à travers wire, décodage, matérialisation, projection spécialisée si utile, préparation d'exécution, policy, exécution et scénarios de validation.
|
- **KSP-FLOW-001** — La progression durable canonique est `RAW -> CORE -> DECODE -> SPECIALIZED`. RAW et CORE ne nécessitent aucun decoder Program ; le passage RAW -> CORE reste une normalisation générique de la blockchain Solana. À partir de DECODE, KSP progresse verticalement par groupe fonctionnel à travers wire, décodage, matérialisation, projection spécialisée si utile, préparation d'exécution, policy, exécution et scénarios de validation.
|
||||||
- **KSP-FLOW-002** — Un programme ou composant satellite nécessaire à la compréhension, la matérialisation ou l'exécution correcte d'un protocole appartient au groupe de ce protocole. Il n'est pas reporté artificiellement dans une catégorie `trading-adjacent`.
|
- **KSP-FLOW-002** — Un programme ou composant satellite nécessaire à la compréhension, la matérialisation ou l'exécution correcte d'un protocole appartient au groupe de ce protocole. Il n'est pas reporté artificiellement dans une catégorie `trading-adjacent`.
|
||||||
|
|||||||
Reference in New Issue
Block a user