v0.2.3-pre.005

This commit is contained in:
2026-08-18 12:33:16 +02:00
parent bd26d4f636
commit 1e1bc0d421
16 changed files with 758 additions and 17 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 124
# version: 125
[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.4"
version = "0.2.3-pre.5"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"slot":431000061,"blockTime":1787000201,"transaction":["3Bxs4NN8M2Yn4TLb","base58"],"meta":null,"version":"legacy","transactionIndex":0},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"slot":431000062,"blockTime":1787000202,"transaction":["AQIDBAUGBwg=","base64"],"meta":null,"version":0,"transactionIndex":1},"id":1}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"slot":431000060,"blockTime":null,"transaction":"3Bxs4NN8M2Yn4TLb","meta":null,"version":"legacy"},"id":1}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":null,"id":1}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
// version: 4
// version: 5
/// Binary encoding accepted for serialized transaction input payloads.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -20,7 +20,6 @@ impl SolanaTransactionBinaryEncoding {
};
}
#[cfg(test)]
fn from_wire(value: &str) -> std::option::Option<Self> {
return match value {
"base58" => std::option::Option::Some(Self::Base58),
@@ -167,7 +166,6 @@ impl SolanaGetTransactionConfig {
/// Serializes the modern config to its 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(commitment) = self.commitment {
@@ -827,7 +825,6 @@ pub enum SolanaEncodedTransaction {
impl SolanaEncodedTransaction {
/// 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> {
return match value {
serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)),
@@ -848,7 +845,6 @@ pub enum SolanaTransactionVersion {
}
impl SolanaTransactionVersion {
#[cfg(test)]
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
return match value {
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.
#[cfg(test)]
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 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.
pub async fn get_transaction_count(
&self,
@@ -1450,7 +1513,6 @@ fn decode_confirmation_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> {
if values.len() != 2 {
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 });
}
#[cfg(test)]
fn decode_transaction_version_field(
method: &str,
field: crate::SolanaWireField<serde_json::Value>,
@@ -1597,7 +1658,6 @@ struct WireSignatureStatus {
confirmation_status: std::option::Option<std::string::String>,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct WireConfirmedTransaction {

View File

@@ -1,5 +1,5 @@
// 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.
@@ -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_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));
}

View File

@@ -1,5 +1,5 @@
// 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.
@@ -291,3 +291,38 @@ fn release_pre_004_transaction_read_subset_is_exact_and_retry_safe() {
assert_eq!(actual.len(), 7);
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);
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
// version: 4
// version: 5
#[test]
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");
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
View 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/003-COMPONENT_CONTRACTS.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# 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.
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
Aucune `ksp-offchain-transport-api` commune n'est prévue.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# 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
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
| 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 :
- 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` ;
- `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 ;

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_KSP.md -->
<!-- version: 32 -->
<!-- version: 33 -->
# 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-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-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-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`.