v0.2.3-pre.002
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 120
|
# version: 121
|
||||||
|
|
||||||
[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.1"
|
version = "0.2.3-pre.2"
|
||||||
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,36 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"slot": 430000123,
|
||||||
|
"blockTime": 1787000000,
|
||||||
|
"transaction": ["AQIDBAUGBwg=", "base64"],
|
||||||
|
"meta": {
|
||||||
|
"err": null,
|
||||||
|
"status": {"Ok": null},
|
||||||
|
"fee": 5000,
|
||||||
|
"preBalances": [10000],
|
||||||
|
"postBalances": [5000]
|
||||||
|
},
|
||||||
|
"version": 0,
|
||||||
|
"transactionIndex": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slot": 430000100,
|
||||||
|
"blockTime": null,
|
||||||
|
"transaction": "3Bxs4NN8M2Yn4TLb",
|
||||||
|
"meta": null,
|
||||||
|
"version": "legacy",
|
||||||
|
"transactionIndex": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slot": 430000050,
|
||||||
|
"blockTime": null,
|
||||||
|
"transaction": {
|
||||||
|
"signatures": [],
|
||||||
|
"message": {
|
||||||
|
"accountKeys": [],
|
||||||
|
"recentBlockhash": "11111111111111111111111111111111",
|
||||||
|
"instructions": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
"3Bxs4NN8M2Yn4TLb",
|
||||||
|
["AQIDBAUGBwg=", "base64"],
|
||||||
|
{
|
||||||
|
"signatures": ["5h6xBEauJ3PK6SWQkgLxHcQpWyRZyG7c4pUpK9fd2B2B7hRQ1S6F9J7BqvQPEmxQxK4DThfYF5f5f5f5f5f5f5f5"],
|
||||||
|
"message": {
|
||||||
|
"accountKeys": [],
|
||||||
|
"recentBlockhash": "11111111111111111111111111111111",
|
||||||
|
"instructions": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"blockhash": "11111111111111111111111111111111",
|
||||||
|
"lastValidBlockHeight": 430123456
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
{"slot": 430000123, "prioritizationFee": 1000},
|
||||||
|
{"slot": 430000122, "prioritizationFee": 0}
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"signature": "5h6xBEauJ3PK6SWQkgLxHcQpWyRZyG7c4pUpK9fd2B2B7hRQ1S6F9J7BqvQPEmxQxK4DThfYF5f5f5f5f5f5f5f5",
|
||||||
|
"slot": 430000123,
|
||||||
|
"err": null,
|
||||||
|
"memo": "fixture memo",
|
||||||
|
"blockTime": 1787000000,
|
||||||
|
"confirmationStatus": "finalized",
|
||||||
|
"transactionIndex": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"signature": "4Nd1mC5QfVhNq8xqP7z8d7K6a5m4n3b2v1c9x8z7y6w5u4t3s2r1q9p8o7n6m5L4K3J2H1G9F8E7D6C5B4A3",
|
||||||
|
"slot": 430000100,
|
||||||
|
"err": {"InstructionError": [0, "Custom"]},
|
||||||
|
"memo": null,
|
||||||
|
"blockTime": null,
|
||||||
|
"confirmationStatus": "confirmed"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"slot": 430000123,
|
||||||
|
"confirmations": null,
|
||||||
|
"status": {"Ok": null},
|
||||||
|
"err": null,
|
||||||
|
"confirmationStatus": "finalized"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slot": 430000120,
|
||||||
|
"confirmations": 3,
|
||||||
|
"status": {"Err": {"InstructionError": [0, "Custom"]}},
|
||||||
|
"err": {"InstructionError": [0, "Custom"]},
|
||||||
|
"confirmationStatus": "confirmed"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"err": null,
|
||||||
|
"logs": ["Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"lamports": 1000,
|
||||||
|
"data": ["", "base64"],
|
||||||
|
"owner": "11111111111111111111111111111111",
|
||||||
|
"executable": false,
|
||||||
|
"rentEpoch": 0,
|
||||||
|
"space": 0
|
||||||
|
},
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"unitsConsumed": 150,
|
||||||
|
"loadedAccountsDataSize": 64,
|
||||||
|
"returnData": {
|
||||||
|
"programId": "11111111111111111111111111111111",
|
||||||
|
"data": ["AQI=", "base64"]
|
||||||
|
},
|
||||||
|
"innerInstructions": [],
|
||||||
|
"replacementBlockhash": {
|
||||||
|
"blockhash": "ComputeBudget111111111111111111111111111111",
|
||||||
|
"lastValidBlockHeight": 430123999
|
||||||
|
},
|
||||||
|
"fee": 5000,
|
||||||
|
"preBalances": [10000, 0],
|
||||||
|
"postBalances": [5000, 5000],
|
||||||
|
"preTokenBalances": [],
|
||||||
|
"postTokenBalances": [],
|
||||||
|
"loadedAddresses": {
|
||||||
|
"writable": [],
|
||||||
|
"readonly": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
// version: 11
|
// version: 12
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
|
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
|
||||||
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
|
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
|
||||||
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
|
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
|
||||||
//! requests through the shared transport path while the `0.2.3` and `0.2.4` audited families remain staged.
|
//! requests through the shared transport path. `0.2.3` now exposes its shared Transaction wire/config primitives while its 11 typed wrappers remain staged;
|
||||||
|
//! the `0.2.4` family remains fully staged.
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod constants;
|
mod constants;
|
||||||
@@ -25,6 +26,7 @@ mod rpc_cluster;
|
|||||||
mod rpc_common;
|
mod rpc_common;
|
||||||
mod rpc_method;
|
mod rpc_method;
|
||||||
mod rpc_tokens;
|
mod rpc_tokens;
|
||||||
|
mod rpc_transactions;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub(crate) use self::constants::TRACING_TARGET;
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
@@ -197,6 +199,44 @@ pub use self::rpc_tokens::SolanaTokenAccountBalance;
|
|||||||
pub use self::rpc_tokens::SolanaTokenAccountSelector;
|
pub use self::rpc_tokens::SolanaTokenAccountSelector;
|
||||||
/// Token amount returned by Solana HTTP token RPC methods.
|
/// Token amount returned by Solana HTTP token RPC methods.
|
||||||
pub use self::rpc_tokens::SolanaTokenAmount;
|
pub use self::rpc_tokens::SolanaTokenAmount;
|
||||||
|
/// Confirmed transaction result returned by `getTransaction` when the RPC result is non-null.
|
||||||
|
pub use self::rpc_transactions::SolanaConfirmedTransaction;
|
||||||
|
/// Wire-preserving transaction payload returned by `getTransaction`.
|
||||||
|
pub use self::rpc_transactions::SolanaEncodedTransaction;
|
||||||
|
/// Modern configuration object accepted by `getTransaction`.
|
||||||
|
pub use self::rpc_transactions::SolanaGetTransactionConfig;
|
||||||
|
/// Blockhash information returned by `getLatestBlockhash` and optionally by simulation.
|
||||||
|
pub use self::rpc_transactions::SolanaLatestBlockhash;
|
||||||
|
/// One recent prioritization-fee sample returned by `getRecentPrioritizationFees`.
|
||||||
|
pub use self::rpc_transactions::SolanaPrioritizationFee;
|
||||||
|
/// Configuration accepted by `requestAirdrop`.
|
||||||
|
pub use self::rpc_transactions::SolanaRequestAirdropConfig;
|
||||||
|
/// Configuration accepted by `sendTransaction` without changing KSP transport retry semantics.
|
||||||
|
pub use self::rpc_transactions::SolanaSendTransactionConfig;
|
||||||
|
/// One ordered signature record returned by `getSignaturesForAddress`.
|
||||||
|
pub use self::rpc_transactions::SolanaSignatureInfo;
|
||||||
|
/// One non-null position returned by `getSignatureStatuses`.
|
||||||
|
pub use self::rpc_transactions::SolanaSignatureStatus;
|
||||||
|
/// Optional historical-search configuration accepted by `getSignatureStatuses`.
|
||||||
|
pub use self::rpc_transactions::SolanaSignatureStatusesConfig;
|
||||||
|
/// Pagination and context configuration accepted by `getSignaturesForAddress`.
|
||||||
|
pub use self::rpc_transactions::SolanaSignaturesForAddressConfig;
|
||||||
|
/// Configuration accepted by `simulateTransaction`.
|
||||||
|
pub use self::rpc_transactions::SolanaSimulateTransactionConfig;
|
||||||
|
/// Rich result payload returned inside the contextual `simulateTransaction` response.
|
||||||
|
pub use self::rpc_transactions::SolanaSimulateTransactionResult;
|
||||||
|
/// Account-return configuration nested under `simulateTransaction`.
|
||||||
|
pub use self::rpc_transactions::SolanaSimulationAccountsConfig;
|
||||||
|
/// Binary encoding accepted for serialized transaction input payloads.
|
||||||
|
pub use self::rpc_transactions::SolanaTransactionBinaryEncoding;
|
||||||
|
/// Confirmation state reported for a signature or transaction status.
|
||||||
|
pub use self::rpc_transactions::SolanaTransactionConfirmationStatus;
|
||||||
|
/// Encoding accepted by `getTransaction`, including its retained legacy `binary` alias.
|
||||||
|
pub use self::rpc_transactions::SolanaTransactionEncoding;
|
||||||
|
/// Transaction version reported by `getTransaction` when the version field is present.
|
||||||
|
pub use self::rpc_transactions::SolanaTransactionVersion;
|
||||||
|
/// Three-state wire field used when Solana distinguishes omission from an explicit JSON `null`.
|
||||||
|
pub use self::rpc_transactions::SolanaWireField;
|
||||||
/// Open cluster or network descriptor used by HTTP endpoint settings.
|
/// Open cluster or network descriptor used by HTTP endpoint settings.
|
||||||
pub use self::settings::HttpClusterName;
|
pub use self::settings::HttpClusterName;
|
||||||
/// Runtime settings for one role declared by an HTTP endpoint.
|
/// Runtime settings for one role declared by an HTTP endpoint.
|
||||||
|
|||||||
1296
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
Normal file
1296
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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: 9
|
// version: 10
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||||
|
|
||||||
@@ -243,3 +243,30 @@ fn public_pre_006_remaining_cluster_wrappers_are_available_from_crate_root() {
|
|||||||
let request = ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::default();
|
let request = ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::default();
|
||||||
assert!(matches!(request, ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::CurrentEpoch(std::option::Option::None)));
|
assert!(matches!(request, ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::CurrentEpoch(std::option::Option::None)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_transaction_pre_002_shared_types_are_constructible_from_crate_root() {
|
||||||
|
let get_transaction = 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!(get_transaction.max_supported_transaction_version(), std::option::Option::Some(0));
|
||||||
|
let send = ksp_onchain_transport_lib::SolanaSendTransactionConfig::new(
|
||||||
|
std::option::Option::Some(false),
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64),
|
||||||
|
std::option::Option::Some(3),
|
||||||
|
std::option::Option::None,
|
||||||
|
);
|
||||||
|
assert_eq!(send.max_retries(), std::option::Option::Some(3));
|
||||||
|
let pagination = ksp_onchain_transport_lib::SolanaSignaturesForAddressConfig::new(
|
||||||
|
std::option::Option::Some("before".to_owned()),
|
||||||
|
std::option::Option::None,
|
||||||
|
std::option::Option::Some(10),
|
||||||
|
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
|
||||||
|
std::option::Option::None,
|
||||||
|
);
|
||||||
|
assert_eq!(pagination.limit(), std::option::Option::Some(10));
|
||||||
|
assert!(ksp_onchain_transport_lib::SolanaWireField::<u64>::Omitted.is_omitted());
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 6
|
// version: 7
|
||||||
|
|
||||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||||
|
|
||||||
@@ -194,3 +194,50 @@ fn release_pre_007_v0_2_2_exact_set_is_complete_without_advancing_future_release
|
|||||||
assert_eq!(future_transactions, 11);
|
assert_eq!(future_transactions, 11);
|
||||||
assert_eq!(future_blocks_economics, 15);
|
assert_eq!(future_blocks_economics, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_2_3_descriptor_set_and_safety_classes_are_exact_before_wrapper_completion() {
|
||||||
|
let mut expected = std::vec![
|
||||||
|
"getFeeForMessage",
|
||||||
|
"getLatestBlockhash",
|
||||||
|
"getRecentPrioritizationFees",
|
||||||
|
"getSignaturesForAddress",
|
||||||
|
"getSignatureStatuses",
|
||||||
|
"getTransaction",
|
||||||
|
"getTransactionCount",
|
||||||
|
"isBlockhashValid",
|
||||||
|
"requestAirdrop",
|
||||||
|
"sendTransaction",
|
||||||
|
"simulateTransaction",
|
||||||
|
];
|
||||||
|
let mut actual = std::vec::Vec::new();
|
||||||
|
let mut reads = 0_usize;
|
||||||
|
let mut writes = 0_usize;
|
||||||
|
let mut simulations = 0_usize;
|
||||||
|
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
|
||||||
|
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actual.push(descriptor.method());
|
||||||
|
match descriptor.operation_kind() {
|
||||||
|
ksp_onchain_transport_lib::RpcOperationKind::Read => {
|
||||||
|
reads += 1;
|
||||||
|
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||||
|
},
|
||||||
|
ksp_onchain_transport_lib::RpcOperationKind::WriteSubmission => {
|
||||||
|
writes += 1;
|
||||||
|
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NeverAfterDispatch);
|
||||||
|
},
|
||||||
|
ksp_onchain_transport_lib::RpcOperationKind::Simulation => {
|
||||||
|
simulations += 1;
|
||||||
|
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual.sort_unstable();
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert_eq!(reads, 8);
|
||||||
|
assert_eq!(writes, 2);
|
||||||
|
assert_eq!(simulations, 1);
|
||||||
|
}
|
||||||
|
|||||||
286
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
Normal file
286
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
|
||||||
|
assert_eq!(crate::SolanaTransactionBinaryEncoding::Base58.as_str(), "base58");
|
||||||
|
assert_eq!(crate::SolanaTransactionBinaryEncoding::Base64.as_str(), "base64");
|
||||||
|
assert_eq!(crate::SolanaTransactionEncoding::Binary.as_str(), "binary");
|
||||||
|
assert_eq!(crate::SolanaTransactionEncoding::Base58.as_str(), "base58");
|
||||||
|
assert_eq!(crate::SolanaTransactionEncoding::Base64.as_str(), "base64");
|
||||||
|
assert_eq!(crate::SolanaTransactionEncoding::Json.as_str(), "json");
|
||||||
|
assert_eq!(crate::SolanaTransactionEncoding::JsonParsed.as_str(), "jsonParsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wire_field_preserves_omitted_null_and_value_states() {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Holder {
|
||||||
|
#[serde(default)]
|
||||||
|
field: crate::SolanaWireField<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let omitted = serde_json::from_value::<Holder>(serde_json::json!({})).expect("omitted fixture must decode");
|
||||||
|
let null = serde_json::from_value::<Holder>(serde_json::json!({"field": null})).expect("null fixture must decode");
|
||||||
|
let value = serde_json::from_value::<Holder>(serde_json::json!({"field": 7})).expect("value fixture must decode");
|
||||||
|
assert!(omitted.field.is_omitted());
|
||||||
|
assert!(!omitted.field.is_null());
|
||||||
|
assert_eq!(omitted.field.value(), std::option::Option::None);
|
||||||
|
assert!(null.field.is_null());
|
||||||
|
assert!(!null.field.is_omitted());
|
||||||
|
assert_eq!(null.field.value(), std::option::Option::None);
|
||||||
|
assert_eq!(value.field.value(), std::option::Option::Some(&7));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_transaction_config_serializes_modern_object_without_legacy_overload() {
|
||||||
|
let config = crate::SolanaGetTransactionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
|
||||||
|
std::option::Option::Some(0),
|
||||||
|
);
|
||||||
|
assert!(!config.is_empty());
|
||||||
|
assert_eq!(config.to_json_value(), serde_json::json!({"commitment":"confirmed","encoding":"jsonParsed","maxSupportedTransactionVersion":0}));
|
||||||
|
assert!(crate::SolanaGetTransactionConfig::default().is_empty());
|
||||||
|
assert_eq!(crate::SolanaGetTransactionConfig::default().to_json_value(), serde_json::json!({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signatures_for_address_config_preserves_pagination_and_context_order_independently() {
|
||||||
|
let config = crate::SolanaSignaturesForAddressConfig::new(
|
||||||
|
std::option::Option::Some("before-signature".to_owned()),
|
||||||
|
std::option::Option::Some("until-signature".to_owned()),
|
||||||
|
std::option::Option::Some(25),
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||||
|
std::option::Option::Some(430_000_000),
|
||||||
|
);
|
||||||
|
assert_eq!(config.before(), std::option::Option::Some("before-signature"));
|
||||||
|
assert_eq!(config.until(), std::option::Option::Some("until-signature"));
|
||||||
|
assert_eq!(config.limit(), std::option::Option::Some(25));
|
||||||
|
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
||||||
|
assert_eq!(config.min_context_slot(), std::option::Option::Some(430_000_000));
|
||||||
|
assert_eq!(
|
||||||
|
config.to_json_value(),
|
||||||
|
serde_json::json!({
|
||||||
|
"before":"before-signature",
|
||||||
|
"until":"until-signature",
|
||||||
|
"limit":25,
|
||||||
|
"commitment":"finalized",
|
||||||
|
"minContextSlot":430000000
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert!(crate::SolanaSignaturesForAddressConfig::default().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_status_and_airdrop_configs_preserve_explicit_options() {
|
||||||
|
let status = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(false));
|
||||||
|
assert_eq!(status.search_transaction_history(), std::option::Option::Some(false));
|
||||||
|
assert_eq!(status.to_json_value(), serde_json::json!({"searchTransactionHistory":false}));
|
||||||
|
let airdrop = crate::SolanaRequestAirdropConfig::new(
|
||||||
|
std::option::Option::Some("recent-blockhash".to_owned()),
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||||
|
);
|
||||||
|
assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash"));
|
||||||
|
assert_eq!(airdrop.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
||||||
|
assert_eq!(airdrop.to_json_value(), serde_json::json!({"recentBlockhash":"recent-blockhash","commitment":"confirmed"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn send_transaction_config_keeps_node_retries_distinct_from_transport_policy() {
|
||||||
|
let config = crate::SolanaSendTransactionConfig::new(
|
||||||
|
std::option::Option::Some(true),
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Processed),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64),
|
||||||
|
std::option::Option::Some(4),
|
||||||
|
std::option::Option::Some(430_000_000),
|
||||||
|
);
|
||||||
|
assert_eq!(config.skip_preflight(), std::option::Option::Some(true));
|
||||||
|
assert_eq!(config.max_retries(), std::option::Option::Some(4));
|
||||||
|
assert_eq!(
|
||||||
|
config.to_json_value(),
|
||||||
|
serde_json::json!({
|
||||||
|
"skipPreflight":true,
|
||||||
|
"preflightCommitment":"processed",
|
||||||
|
"encoding":"base64",
|
||||||
|
"maxRetries":4,
|
||||||
|
"minContextSlot":430000000
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert!(crate::SolanaSendTransactionConfig::default().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_transaction_config_serializes_accounts_and_flags_without_decoding_transaction() {
|
||||||
|
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
|
||||||
|
let accounts = crate::SolanaSimulationAccountsConfig::new(std::option::Option::Some(crate::SolanaAccountEncoding::Base64), std::vec![address]);
|
||||||
|
let config = crate::SolanaSimulateTransactionConfig::new(
|
||||||
|
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||||
|
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64),
|
||||||
|
std::option::Option::Some(true),
|
||||||
|
std::option::Option::Some(false),
|
||||||
|
std::option::Option::Some(430_000_000),
|
||||||
|
std::option::Option::Some(true),
|
||||||
|
std::option::Option::Some(accounts),
|
||||||
|
);
|
||||||
|
assert_eq!(config.encoding(), std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64));
|
||||||
|
assert_eq!(config.replace_recent_blockhash(), std::option::Option::Some(true));
|
||||||
|
assert_eq!(config.sig_verify(), std::option::Option::Some(false));
|
||||||
|
assert_eq!(config.accounts().expect("accounts config must be present").addresses(), &[address]);
|
||||||
|
assert_eq!(
|
||||||
|
config.to_json_value(),
|
||||||
|
serde_json::json!({
|
||||||
|
"commitment":"confirmed",
|
||||||
|
"encoding":"base64",
|
||||||
|
"replaceRecentBlockhash":true,
|
||||||
|
"sigVerify":false,
|
||||||
|
"minContextSlot":430000000,
|
||||||
|
"innerInstructions":true,
|
||||||
|
"accounts":{"encoding":"base64","addresses":["11111111111111111111111111111111"]}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert!(crate::SolanaSimulateTransactionConfig::default().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn latest_blockhash_fixture_decodes_shared_result() {
|
||||||
|
let value =
|
||||||
|
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/latest_blockhash.v4_2_1.json")).expect("latest blockhash fixture must decode");
|
||||||
|
let blockhash = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", value).expect("latest blockhash must decode");
|
||||||
|
assert_eq!(blockhash.blockhash(), "11111111111111111111111111111111");
|
||||||
|
assert_eq!(blockhash.last_valid_block_height(), 430_123_456);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn latest_blockhash_rejects_empty_wire_value() {
|
||||||
|
let result = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", serde_json::json!({"blockhash":"","lastValidBlockHeight":430123456}));
|
||||||
|
assert_eq!(result.expect_err("empty blockhash must fail closed").code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prioritization_fee_fixture_preserves_server_order() {
|
||||||
|
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/prioritization_fee.v4_2_1.json"))
|
||||||
|
.expect("prioritization fee fixture must decode");
|
||||||
|
let first = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[0].clone()).expect("first fee must decode");
|
||||||
|
let second = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[1].clone()).expect("second fee must decode");
|
||||||
|
assert_eq!(first.slot(), 430_000_123);
|
||||||
|
assert_eq!(first.prioritization_fee(), 1_000);
|
||||||
|
assert_eq!(second.slot(), 430_000_122);
|
||||||
|
assert_eq!(second.prioritization_fee(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_info_fixture_accepts_current_transaction_index_and_older_omission() {
|
||||||
|
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/signature_info.v4_2_1.json"))
|
||||||
|
.expect("signature info fixture must decode");
|
||||||
|
let current = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[0].clone()).expect("current signature info must decode");
|
||||||
|
let older = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[1].clone()).expect("older signature info must decode");
|
||||||
|
assert_eq!(current.slot(), 430_000_123);
|
||||||
|
assert_eq!(current.memo(), std::option::Option::Some("fixture memo"));
|
||||||
|
assert_eq!(current.transaction_index(), std::option::Option::Some(7));
|
||||||
|
assert_eq!(current.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
|
||||||
|
assert_eq!(older.transaction_index(), std::option::Option::None);
|
||||||
|
assert!(older.err().is_some());
|
||||||
|
assert_eq!(older.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Confirmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_status_fixture_preserves_position_null_and_legacy_status_value() {
|
||||||
|
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/signature_status.variants.json"))
|
||||||
|
.expect("signature status fixture must decode");
|
||||||
|
assert!(values[0].is_null());
|
||||||
|
let rooted = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[1].clone()).expect("rooted signature status must decode");
|
||||||
|
let errored = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[2].clone()).expect("errored signature status must decode");
|
||||||
|
assert_eq!(rooted.confirmations(), std::option::Option::None);
|
||||||
|
assert_eq!(rooted.status(), &serde_json::json!({"Ok":null}));
|
||||||
|
assert_eq!(rooted.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
|
||||||
|
assert_eq!(errored.confirmations(), std::option::Option::Some(3));
|
||||||
|
assert!(errored.err().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_confirmation_status_is_rejected_as_invalid_response() {
|
||||||
|
let result = crate::SolanaSignatureStatus::decode_wire(
|
||||||
|
"getSignatureStatuses",
|
||||||
|
serde_json::json!({"slot":1,"confirmations":0,"status":{"Ok":null},"err":null,"confirmationStatus":"future-status"}),
|
||||||
|
);
|
||||||
|
let error = result.expect_err("unknown confirmation status must fail closed");
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encoded_transaction_fixture_preserves_legacy_binary_tuple_and_json_shapes() {
|
||||||
|
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/encoded_transaction.variants.json"))
|
||||||
|
.expect("encoded transaction fixture must decode");
|
||||||
|
let legacy = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[0].clone()).expect("legacy transaction must decode");
|
||||||
|
let binary = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[1].clone()).expect("binary transaction must decode");
|
||||||
|
let json = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[2].clone()).expect("JSON transaction must decode");
|
||||||
|
assert!(matches!(legacy, crate::SolanaEncodedTransaction::LegacyBinary(_)));
|
||||||
|
assert!(matches!(binary, crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. }));
|
||||||
|
assert!(matches!(json, crate::SolanaEncodedTransaction::Json(_)));
|
||||||
|
let invalid = crate::SolanaEncodedTransaction::decode_wire("getTransaction", serde_json::json!(["payload", "json"]));
|
||||||
|
assert_eq!(invalid.expect_err("non-binary tuple encoding must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn confirmed_transaction_fixture_preserves_meta_version_and_transaction_index_states() {
|
||||||
|
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/confirmed_transaction.variants.json"))
|
||||||
|
.expect("confirmed transaction fixture must decode");
|
||||||
|
let current = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[0].clone()).expect("current transaction must decode");
|
||||||
|
let legacy = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[1].clone()).expect("legacy transaction must decode");
|
||||||
|
let omitted = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[2].clone()).expect("omitted-field transaction must decode");
|
||||||
|
assert_eq!(current.slot(), 430_000_123);
|
||||||
|
assert_eq!(current.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Number(0)));
|
||||||
|
assert_eq!(current.transaction_index().value(), std::option::Option::Some(&7));
|
||||||
|
assert!(current.meta().value().is_some());
|
||||||
|
assert_eq!(legacy.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Legacy));
|
||||||
|
assert!(legacy.meta().is_null());
|
||||||
|
assert!(legacy.transaction_index().is_null());
|
||||||
|
assert!(omitted.meta().is_omitted());
|
||||||
|
assert!(omitted.version().is_omitted());
|
||||||
|
assert!(omitted.transaction_index().is_omitted());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn confirmed_transaction_rejects_unknown_version_shape() {
|
||||||
|
let result = crate::SolanaConfirmedTransaction::decode_wire(
|
||||||
|
"getTransaction",
|
||||||
|
serde_json::json!({"slot":1,"blockTime":null,"transaction":"legacy","meta":null,"version":"v0"}),
|
||||||
|
);
|
||||||
|
assert_eq!(result.expect_err("unknown version label must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulation_result_fixture_preserves_rich_optional_fields_and_positional_null_accounts() {
|
||||||
|
let value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/simulate_transaction_result.v4_2_1.json"))
|
||||||
|
.expect("simulation result fixture must decode");
|
||||||
|
let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", value).expect("simulation result must decode");
|
||||||
|
assert!(result.err().is_null());
|
||||||
|
assert_eq!(result.logs().value().expect("logs must be present").len(), 2);
|
||||||
|
let accounts = result.accounts().value().expect("accounts must be present");
|
||||||
|
assert_eq!(accounts.len(), 2);
|
||||||
|
assert_eq!(accounts[0].as_ref().expect("first account must be present").lamports(), 1_000);
|
||||||
|
assert!(accounts[1].is_none());
|
||||||
|
assert_eq!(result.units_consumed().value(), std::option::Option::Some(&150));
|
||||||
|
assert_eq!(result.loaded_accounts_data_size().value(), std::option::Option::Some(&64));
|
||||||
|
assert_eq!(result.fee().value(), std::option::Option::Some(&5_000));
|
||||||
|
let replacement = result.replacement_blockhash().value().expect("replacement blockhash must be present");
|
||||||
|
assert_eq!(replacement.blockhash(), "ComputeBudget111111111111111111111111111111");
|
||||||
|
assert!(result.return_data().value().is_some());
|
||||||
|
assert!(result.inner_instructions().value().is_some());
|
||||||
|
assert!(result.pre_token_balances().value().is_some());
|
||||||
|
assert!(result.post_token_balances().value().is_some());
|
||||||
|
assert!(result.loaded_addresses().value().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulation_result_distinguishes_omitted_from_explicit_null_fields() {
|
||||||
|
let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", serde_json::json!({"err":null,"logs":null,"unitsConsumed":1}))
|
||||||
|
.expect("partial simulation result must decode");
|
||||||
|
assert!(result.err().is_null());
|
||||||
|
assert!(result.logs().is_null());
|
||||||
|
assert_eq!(result.units_consumed().value(), std::option::Option::Some(&1));
|
||||||
|
assert!(result.accounts().is_omitted());
|
||||||
|
assert!(result.replacement_blockhash().is_omitted());
|
||||||
|
assert!(result.loaded_addresses().is_omitted());
|
||||||
|
}
|
||||||
271
deltas/0.2.3/pre.002.md
Normal file
271
deltas/0.2.3/pre.002.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<!-- file: deltas/0.2.3/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.3-pre.002` — primitives Transaction et wire partagé
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
Livraison précédente :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.3-pre.001
|
||||||
|
workspace.package.version = "0.2.3-pre.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plan canonique de départ est `docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md` version 2, avec l'alignement Markdown corrigé par
|
||||||
|
l'opérateur avant cette tranche.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Installer les primitives, configurations et résultats partagés nécessaires aux 11 wrappers Transactions `0.2.3`, ainsi que des fixtures wire
|
||||||
|
locales déterministes, sans encore implémenter les quatre premiers wrappers Read prévus pour `pre.003` et sans déclarer la couverture typed
|
||||||
|
`0.2.3` complète.
|
||||||
|
|
||||||
|
Cette tranche doit préserver les unions/nullable/omissions du wire Transaction tout en restant une couche Transport : aucun décodage Program,
|
||||||
|
aucune construction/signature de transaction et aucun client RPC Solana parallèle ne sont introduits.
|
||||||
|
|
||||||
|
## Version Cargo
|
||||||
|
|
||||||
|
Conformément à `VER-ID-009`, la nouvelle prerelease synchronise le signal technique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.3-pre.1 -> 0.2.3-pre.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance ou feature Cargo n'est ajoutée.
|
||||||
|
|
||||||
|
## Implémentation
|
||||||
|
|
||||||
|
### Encodings et champs wire
|
||||||
|
|
||||||
|
Le nouveau module `rpc_transactions` ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaTransactionBinaryEncoding
|
||||||
|
SolanaTransactionEncoding
|
||||||
|
SolanaWireField<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
`SolanaTransactionBinaryEncoding` borne les entrées sérialisées `sendTransaction`/`simulateTransaction` à `base58 | base64` sans décoder les
|
||||||
|
bytes. `SolanaTransactionEncoding` conserve la surface plus large de `getTransaction` : `binary` legacy, `base58`, `base64`, `json` et
|
||||||
|
`jsonParsed`.
|
||||||
|
|
||||||
|
`SolanaWireField<T>` matérialise explicitement les trois états qui comptent sur les réponses Transaction évolutives :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Omitted
|
||||||
|
Null
|
||||||
|
Value(T)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette primitive évite de fusionner un champ absent avec un champ explicitement `null`, notamment pour `getTransaction` et
|
||||||
|
`simulateTransaction`.
|
||||||
|
|
||||||
|
### Configurations partagées
|
||||||
|
|
||||||
|
Les configurations suivantes sont ajoutées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaGetTransactionConfig
|
||||||
|
SolanaSignaturesForAddressConfig
|
||||||
|
SolanaSignatureStatusesConfig
|
||||||
|
SolanaRequestAirdropConfig
|
||||||
|
SolanaSendTransactionConfig
|
||||||
|
SolanaSimulationAccountsConfig
|
||||||
|
SolanaSimulateTransactionConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles sérialisent uniquement les options explicitement fournies. `SolanaSendTransactionConfig::max_retries` reste nommé et documenté comme une
|
||||||
|
limite de retransmission **côté noeud RPC** et ne modifie jamais la retry policy HTTP KSP.
|
||||||
|
|
||||||
|
Les validations propres aux wrappers restent volontairement dans leurs tranches d'exécution : limites `128 / 1000 / 256`, encodings Account
|
||||||
|
interdits pour le retour de simulation et incompatibilité `sigVerify + replaceRecentBlockhash` ne sont pas transformés ici en une seconde policy
|
||||||
|
parallèle.
|
||||||
|
|
||||||
|
### Résultats et DTOs Transaction
|
||||||
|
|
||||||
|
Les résultats communs suivants sont ajoutés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaLatestBlockhash
|
||||||
|
SolanaPrioritizationFee
|
||||||
|
SolanaTransactionConfirmationStatus
|
||||||
|
SolanaSignatureInfo
|
||||||
|
SolanaSignatureStatus
|
||||||
|
SolanaEncodedTransaction
|
||||||
|
SolanaTransactionVersion
|
||||||
|
SolanaConfirmedTransaction
|
||||||
|
SolanaSimulateTransactionResult
|
||||||
|
```
|
||||||
|
|
||||||
|
`SolanaEncodedTransaction` conserve séparément :
|
||||||
|
|
||||||
|
```text
|
||||||
|
chaîne binaire legacy
|
||||||
|
[data, "base58" | "base64"]
|
||||||
|
objet JSON / jsonParsed
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `base64`, `bs58` ou `wincode` n'est donc requis. Les objets Transaction/message riches restent lossless via `serde_json::Value` aux
|
||||||
|
frontières où Transport ne possède pas de modèle métier.
|
||||||
|
|
||||||
|
`SolanaConfirmedTransaction` conserve `meta`, `version` et `transactionIndex` avec la sémantique `Omitted | Null | Value`. Pendant
|
||||||
|
l'implémentation de cette tranche, le recoupement direct avec Agave `v4.2.1` a confirmé que `EncodedConfirmedTransactionWithStatusMeta` expose
|
||||||
|
également `transactionIndex: Option<u32>` au top-level de `getTransaction`. Le plan `010` passe donc en version 3 pour enregistrer cette précision
|
||||||
|
wire additive ; il ne s'agit pas d'un changement de périmètre.
|
||||||
|
|
||||||
|
`SolanaSimulateTransactionResult` conserve les champs Agave courants utiles, y compris les comptes positionnels nullable, unités consommées,
|
||||||
|
loaded account data size, return data, inner instructions, replacement blockhash, fee, balances, token balances et loaded addresses. Les
|
||||||
|
sous-structures Program/transaction riches restent volontairement opaques lorsqu'un modèle Transport étroit n'apporte pas de valeur.
|
||||||
|
|
||||||
|
### Validation de wire locale
|
||||||
|
|
||||||
|
Les décodeurs partagés :
|
||||||
|
|
||||||
|
- utilisent le domaine d'erreur Transport existant pour les shapes invalides ;
|
||||||
|
- rejettent un `getLatestBlockhash.blockhash` vide sans tenter de le décoder ;
|
||||||
|
- rejettent les labels de confirmation inconnus ;
|
||||||
|
- rejettent les tuples transaction binaires mal formés ou utilisant un encoding non binaire ;
|
||||||
|
- bornent le champ `version` de `getTransaction` à `legacy | u8` conformément au wire actuel ;
|
||||||
|
- préservent les `null` positionnels des comptes de simulation.
|
||||||
|
|
||||||
|
Ils ne désérialisent ni message ni transaction Solana.
|
||||||
|
|
||||||
|
## Fixtures déterministes ajoutées
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/latest_blockhash.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/prioritization_fee.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/signature_info.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/signature_status.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/encoded_transaction.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_transaction.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/simulate_transaction_result.v4_2_1.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles couvrent notamment :
|
||||||
|
|
||||||
|
- latest blockhash ;
|
||||||
|
- ordre de samples de prioritization fees ;
|
||||||
|
- `transactionIndex` présent et absent sur les signatures ;
|
||||||
|
- `null` positionnel pour les statuses ;
|
||||||
|
- status legacy encore présent ;
|
||||||
|
- transaction legacy, tuple base64 et JSON ;
|
||||||
|
- `meta/version/transactionIndex` présents, `null` ou omis ;
|
||||||
|
- résultat de simulation riche avec compte positionnel `null` et champs optionnels actuels.
|
||||||
|
|
||||||
|
## Tests ajoutés
|
||||||
|
|
||||||
|
`unit_tests/rpc_transactions.rs` ajoute **18 tests déterministes** pour les enums/configs/décodeurs/fixtures de cette tranche.
|
||||||
|
|
||||||
|
Les tests de surface ajoutent aussi :
|
||||||
|
|
||||||
|
- un test public de construction/réexport des primitives Transaction depuis la racine de crate ;
|
||||||
|
- une canarie de registre qui confirme l'ensemble exact des 11 descriptors `0.2.3` et leur répartition `8 Read / 2 WriteSubmission / 1 Simulation`
|
||||||
|
sans prétendre que les 11 wrappers typed existent déjà.
|
||||||
|
|
||||||
|
La canarie typed-complete `0.2.3 exact == 11` reste donc volontairement fermée jusqu'aux tranches de wrappers.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||||
|
crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/latest_blockhash.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/prioritization_fee.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/signature_info.v4_2_1.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/signature_status.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/encoded_transaction.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_transaction.variants.json
|
||||||
|
crates/ksp-onchain-transport-lib/fixtures/http/simulate_transaction_result.v4_2_1.json
|
||||||
|
deltas/0.2.3/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||||
|
docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
crates/ksp-onchain-transport-lib/Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/README.md
|
||||||
|
crates/ksp-onchain-transport-lib/USAGE.md
|
||||||
|
crates/ksp-onchain-transport-lib/src/executor.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/rpc_method.rs
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
config/**
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun ancien delta n'est réécrit. Aucun wrapper `0.2.4` n'est avancé.
|
||||||
|
|
||||||
|
## Validations exécutées
|
||||||
|
|
||||||
|
- reconstruction de la base `0.2.3-pre.001` puis remplacement du plan `010` par la version 2 corrigée fournie par l'opérateur ;
|
||||||
|
- contrôle différentiel de la tranche contre cette base corrigée ;
|
||||||
|
- recoupement ciblé des unions/configs/résultats avec les sources primaires Agave `v4.2.1` déjà normatives pour le plan ;
|
||||||
|
- confirmation additive de `getTransaction.transactionIndex: Option<u32>` dans `EncodedConfirmedTransactionWithStatusMeta` ;
|
||||||
|
- parse JSON local des sept nouvelles fixtures ;
|
||||||
|
- contrôle statique des réexports crate-root des nouveaux types publics ;
|
||||||
|
- contrôle statique de l'absence de nouveau client HTTP, de `reqwest` direct dans le module Transaction, de boucle de retry locale et de wrapper
|
||||||
|
Transaction exécuté prématurément ;
|
||||||
|
- contrôle statique de l'absence de nouvelle dépendance/feature Cargo ;
|
||||||
|
- contrôle statique de l'absence de `unwrap`, `expect`, `panic!` et `?` dans la nouvelle source de production ;
|
||||||
|
- contrôle statique des lignes Rust modifiées à `<= 160` colonnes ;
|
||||||
|
- contrôle que `CHANGELOG.md` reste inchangé.
|
||||||
|
|
||||||
|
## Validations non exécutées
|
||||||
|
|
||||||
|
Le sandbox ne fournit pas `cargo` ni `rustfmt`. Les commandes suivantes ne sont donc pas déclarées comme réussies et doivent être exécutées sur
|
||||||
|
le checkout de développement avant commit :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree` supplémentaire n'est requis par cette tranche puisqu'aucune dépendance ni feature n'a changé. Les vues de dépendances du prompt
|
||||||
|
restent obligatoires à la clôture de `0.2.3`.
|
||||||
|
|
||||||
|
Le commit attendu après application et validations suit `VER-GIT-001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.3-pre.002
|
||||||
|
```
|
||||||
|
|
||||||
|
## Décisions prises
|
||||||
|
|
||||||
|
- conserver les payloads message/transaction sérialisés comme chaînes opaques tant qu'aucun invariant KSP n'exige leur décodage ;
|
||||||
|
- distinguer l'encoding binaire d'entrée de la surface d'encoding plus large de `getTransaction` ;
|
||||||
|
- représenter explicitement `absent / null / présent` lorsque le wire courant le nécessite ;
|
||||||
|
- conserver `serde_json::Value` seulement aux frontières Transaction/Program riches et évolutives, plutôt que dupliquer tout
|
||||||
|
`solana-transaction-status-client-types` ;
|
||||||
|
- réutiliser `SolanaContextConfig`, `SolanaAccountEncoding`, `SolanaAccount` et les erreurs Transport existantes ;
|
||||||
|
- ne pas ajouter les validations de wrapper avant leur tranche propriétaire ;
|
||||||
|
- ajouter `getTransaction.transactionIndex` au plan et au DTO après confirmation Agave `v4.2.1`.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
Aucune question bloquante pour `pre.003`.
|
||||||
|
|
||||||
|
Le formatage canonique final doit être produit par `cargo fmt --all`/`rustfmt` lors de l'application locale, puisque ces binaires ne sont pas
|
||||||
|
disponibles dans le sandbox d'échange.
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.2.3-pre.003` : implémenter `getFeeForMessage`, `getLatestBlockhash`, `getTransactionCount` et `isBlockhashValid` via le chemin
|
||||||
|
`descriptor -> execute_standard_rpc`, avec requêtes exactes, résultats contextuels/nullables, erreurs RPC et fixtures HTTP locales.
|
||||||
@@ -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: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Plan `0.2.3` — HTTP Transactions
|
# Plan `0.2.3` — HTTP Transactions
|
||||||
|
|
||||||
@@ -314,13 +314,18 @@ Cette API legacy doit être nommée/annotée de manière à ne pas sembler être
|
|||||||
Le résultat confirmé garde un top-level typé :
|
Le résultat confirmé garde un top-level typé :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
slot u64
|
slot u64
|
||||||
blockTime Option<i64>
|
blockTime Option<i64>
|
||||||
transaction EncodedTransaction
|
transaction EncodedTransaction
|
||||||
meta Option<transaction-meta wire>
|
meta absent | null | transaction-meta wire
|
||||||
version legacy | number
|
version absent | null | legacy | number
|
||||||
|
transactionIndex absent | null | u32
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Le recoupement d'implémentation `pre.002` avec `transaction-status-client-types` Agave `v4.2.1` a confirmé que
|
||||||
|
`EncodedConfirmedTransactionWithStatusMeta` expose également `transactionIndex: Option<u32>` au top-level de `getTransaction`. Cette extension
|
||||||
|
optionnelle est donc préservée comme celle de `getSignaturesForAddress`, sans être rendue obligatoire pour les providers plus anciens.
|
||||||
|
|
||||||
`EncodedTransaction` doit préserver les différentes formes réellement sérialisées :
|
`EncodedTransaction` doit préserver les différentes formes réellement sérialisées :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -335,6 +340,10 @@ sont riches, évolutives ou dépendantes de `jsonParsed` peuvent rester lossless
|
|||||||
Le meta doit au minimum respecter la distinction **absent/null/présent** imposée par le wire et ne jamais inventer des listes ou zéros lorsque le
|
Le meta doit au minimum respecter la distinction **absent/null/présent** imposée par le wire et ne jamais inventer des listes ou zéros lorsque le
|
||||||
provider omet une donnée optionnelle.
|
provider omet une donnée optionnelle.
|
||||||
|
|
||||||
|
`pre.002` matérialise cette distinction avec une primitive générique `SolanaWireField<T>` limitée au wire Transport : `Omitted`, `Null`, `Value(T)`.
|
||||||
|
Elle est utilisée pour les champs Transaction/Simulation où l'omission et `null` doivent rester distinguables ; elle ne transforme pas ce mécanisme
|
||||||
|
en modèle métier et ne remplace pas `Option<T>` lorsque les deux états ont le même sens contractuel.
|
||||||
|
|
||||||
### `getRecentPrioritizationFees`
|
### `getRecentPrioritizationFees`
|
||||||
|
|
||||||
Le paramètre optionnel est un tableau d'adresses. La documentation actuelle fixe un maximum de **128** et précise que, lorsqu'il est fourni, les
|
Le paramètre optionnel est un tableau d'adresses. La documentation actuelle fixe un maximum de **128** et précise que, lorsqu'il est fourni, les
|
||||||
@@ -634,3 +643,18 @@ Aucune commande n'est déclarée réussie sans exécution réelle ou preuve opé
|
|||||||
- découpage `pre.002`–`pre.008` défini.
|
- découpage `pre.002`–`pre.008` défini.
|
||||||
|
|
||||||
La tranche suivante peut commencer par les primitives wire communes sans réouvrir le scope de release.
|
La tranche suivante peut commencer par les primitives wire communes sans réouvrir le scope de release.
|
||||||
|
|
||||||
|
## Résultat de `pre.002`
|
||||||
|
|
||||||
|
`pre.002` installe les primitives/configurations/résultats partagés sans déclarer un wrapper Transaction comme terminé :
|
||||||
|
|
||||||
|
- encodings Transaction séparés entre entrées binaires `base58/base64` et surface `getTransaction` incluant les formes JSON/legacy ;
|
||||||
|
- configs modernes de `getTransaction`, pagination signatures, statuses, airdrop, send et simulation ;
|
||||||
|
- DTOs `latestBlockhash`, prioritization fee, signature info/status, transaction encodée/versionnée et résultat de simulation ;
|
||||||
|
- primitive `SolanaWireField<T>` pour préserver explicitement `omitted/null/value` ;
|
||||||
|
- `transactionIndex` préservé sur `getSignaturesForAddress` et sur le top-level `getTransaction` courant ;
|
||||||
|
- transaction/meta/return data/inner instructions/token balances riches conservés losslessly aux frontières prévues sans décodage Program ;
|
||||||
|
- fixtures communes déterministes pour les variantes legacy/binary/JSON, version/meta/index et simulation riche ;
|
||||||
|
- aucune nouvelle dépendance Cargo et aucun wrapper `0.2.3` activé prématurément.
|
||||||
|
|
||||||
|
La tranche suivante reste `pre.003` : `getFeeForMessage`, `getLatestBlockhash`, `getTransactionCount` et `isBlockhashValid`.
|
||||||
|
|||||||
Reference in New Issue
Block a user