v0.2.4-pre.002
This commit is contained in:
@@ -6,7 +6,7 @@ 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.4-pre.1"
|
||||
version = "0.2.4-pre.2"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{"commitment":[1,2,3,4],"totalStake":1000000000},
|
||||
{"commitment":null,"totalStake":1000000000}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"byIdentity": {
|
||||
"11111111111111111111111111111111": [8,7],
|
||||
"Vote111111111111111111111111111111111111111": [4,4]
|
||||
},
|
||||
"range": {"firstSlot":430000000,"lastSlot":430000099}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"previousBlockhash":"previous-a",
|
||||
"blockhash":"block-a",
|
||||
"parentSlot":1,
|
||||
"blockTime":null,
|
||||
"blockHeight":null
|
||||
},
|
||||
{
|
||||
"previousBlockhash":"previous-b",
|
||||
"blockhash":"block-b",
|
||||
"parentSlot":2,
|
||||
"transactions":null,
|
||||
"signatures":null,
|
||||
"rewards":null,
|
||||
"numRewardPartitions":null,
|
||||
"blockTime":null,
|
||||
"blockHeight":null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"previousBlockhash":"previous-blockhash-fixture",
|
||||
"blockhash":"blockhash-fixture",
|
||||
"parentSlot":430000122,
|
||||
"transactions":[
|
||||
{
|
||||
"transaction":{"signatures":["signature-fixture"],"message":{"accountKeys":[],"recentBlockhash":"recent-fixture","instructions":[]}},
|
||||
"meta":{"err":null,"fee":5000},
|
||||
"version":1
|
||||
},
|
||||
{
|
||||
"transaction":["AQID","base64"],
|
||||
"meta":null
|
||||
}
|
||||
],
|
||||
"rewards":[
|
||||
{
|
||||
"pubkey":"11111111111111111111111111111111",
|
||||
"lamports":2500,
|
||||
"postBalance":1000002500,
|
||||
"rewardType":"Fee",
|
||||
"commission":null,
|
||||
"commissionBps":1234
|
||||
},
|
||||
{
|
||||
"pubkey":"Vote111111111111111111111111111111111111111",
|
||||
"lamports":1250,
|
||||
"postBalance":2000001250,
|
||||
"rewardType":"Voting",
|
||||
"commission":5
|
||||
}
|
||||
],
|
||||
"numRewardPartitions":4,
|
||||
"blockTime":1787072400,
|
||||
"blockHeight":410000000
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"initial":0.08,"terminal":0.015,"taper":0.15,"foundation":0.05,"foundationTerm":7.0}
|
||||
@@ -0,0 +1 @@
|
||||
{"total":0.042,"validator":0.041,"foundation":0.001,"epoch":812}
|
||||
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{"epoch":811,"effectiveSlot":429900000,"amount":2500000,"postBalance":1002500000,"commission":7,"commissionBps":725},
|
||||
null,
|
||||
{"epoch":811,"effectiveSlot":429900000,"amount":1250000,"postBalance":501250000,"commission":null}
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{"slot":430000123,"numTransactions":250000,"numNonVoteTransactions":175000,"numSlots":120,"samplePeriodSecs":60},
|
||||
{"slot":430000003,"numTransactions":240000,"numSlots":118,"samplePeriodSecs":60}
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"total":600000000000000000,
|
||||
"circulating":500000000000000000,
|
||||
"nonCirculating":100000000000000000,
|
||||
"nonCirculatingAccounts":[
|
||||
"11111111111111111111111111111111",
|
||||
"Vote111111111111111111111111111111111111111"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -12,7 +12,8 @@
|
||||
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
|
||||
//! requests through the shared transport path. `0.2.3` exposes its shared Transaction wire/config primitives and all eleven Transaction wrappers through
|
||||
//! `pre.007`: eight reads, two write submissions with centralized no-resend protection, and retry-safe `simulateTransaction`, including complete
|
||||
//! modern/legacy `getTransaction` coverage. The `0.2.4` family remains staged.
|
||||
//! modern/legacy `getTransaction` coverage. `0.2.4-pre.002` adds the shared Blocks/Economics wire, config and result primitives without yet advancing
|
||||
//! any `V0_2_4` method to a typed wrapper.
|
||||
|
||||
mod client;
|
||||
mod constants;
|
||||
@@ -22,9 +23,11 @@ mod json_rpc;
|
||||
mod pool;
|
||||
mod resilience;
|
||||
mod rpc_accounts;
|
||||
mod rpc_blocks;
|
||||
mod rpc_canary;
|
||||
mod rpc_cluster;
|
||||
mod rpc_common;
|
||||
mod rpc_economics;
|
||||
mod rpc_method;
|
||||
mod rpc_tokens;
|
||||
mod rpc_transactions;
|
||||
@@ -126,6 +129,28 @@ pub use self::rpc_accounts::SolanaProgramAccountFilter;
|
||||
pub use self::rpc_accounts::SolanaProgramAccountsConfig;
|
||||
/// Result union returned by `getProgramAccounts` with or without an RPC context.
|
||||
pub use self::rpc_accounts::SolanaProgramAccountsResult;
|
||||
/// Commitment distribution returned by `getBlockCommitment`.
|
||||
pub use self::rpc_blocks::SolanaBlockCommitment;
|
||||
/// Block-production counts returned by `getBlockProduction` before the shared RPC context wrapper is applied.
|
||||
pub use self::rpc_blocks::SolanaBlockProduction;
|
||||
/// Optional configuration accepted by `getBlockProduction`.
|
||||
pub use self::rpc_blocks::SolanaBlockProductionConfig;
|
||||
/// Slot range accepted inside `getBlockProduction` configuration.
|
||||
pub use self::rpc_blocks::SolanaBlockProductionRange;
|
||||
/// Effective range reported inside `getBlockProduction` results.
|
||||
pub use self::rpc_blocks::SolanaBlockProductionResultRange;
|
||||
/// One reward entry returned in a confirmed block.
|
||||
pub use self::rpc_blocks::SolanaBlockReward;
|
||||
/// One transaction entry returned inside a confirmed block.
|
||||
pub use self::rpc_blocks::SolanaBlockTransaction;
|
||||
/// Confirmed block wire result returned by `getBlock` when the RPC result is non-null.
|
||||
pub use self::rpc_blocks::SolanaConfirmedBlock;
|
||||
/// Modern configuration object accepted by `getBlock`.
|
||||
pub use self::rpc_blocks::SolanaGetBlockConfig;
|
||||
/// One entry returned by `getRecentPerformanceSamples`.
|
||||
pub use self::rpc_blocks::SolanaPerformanceSample;
|
||||
/// Transaction detail level accepted by modern `getBlock` requests.
|
||||
pub use self::rpc_blocks::SolanaTransactionDetails;
|
||||
/// Optional typed configuration for the `getBalance` canary.
|
||||
pub use self::rpc_canary::GetBalanceConfig;
|
||||
/// Typed lamport balance returned by the `getBalance` canary.
|
||||
@@ -172,6 +197,18 @@ pub use self::rpc_common::SolanaRpcResponse;
|
||||
pub(crate) use self::rpc_common::decode_wire_json;
|
||||
/// Parses a base58 public key without echoing its wire value into diagnostics for typed RPC adapters.
|
||||
pub(crate) use self::rpc_common::parse_wire_pubkey;
|
||||
/// Inflation-governor values returned by `getInflationGovernor`.
|
||||
pub use self::rpc_economics::SolanaInflationGovernor;
|
||||
/// Current inflation-rate values returned by `getInflationRate`.
|
||||
pub use self::rpc_economics::SolanaInflationRate;
|
||||
/// One non-null positional reward returned by `getInflationReward`.
|
||||
pub use self::rpc_economics::SolanaInflationReward;
|
||||
/// Optional epoch/context configuration accepted by `getInflationReward`.
|
||||
pub use self::rpc_economics::SolanaInflationRewardConfig;
|
||||
/// Supply totals returned inside the contextual `getSupply` response.
|
||||
pub use self::rpc_economics::SolanaSupply;
|
||||
/// Optional configuration accepted by `getSupply`.
|
||||
pub use self::rpc_economics::SolanaSupplyConfig;
|
||||
/// Functional category used by the audited Solana HTTP JSON-RPC registry.
|
||||
pub use self::rpc_method::HttpRpcCategory;
|
||||
/// Release that owns typed KSP coverage for one audited HTTP RPC method.
|
||||
|
||||
712
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
Normal file
712
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
Normal file
@@ -0,0 +1,712 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
||||
// version: 1
|
||||
|
||||
/// Transaction detail level accepted by modern `getBlock` requests.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub enum SolanaTransactionDetails {
|
||||
/// Return complete transaction payloads and metadata.
|
||||
#[default]
|
||||
Full,
|
||||
/// Return transaction signatures only.
|
||||
Signatures,
|
||||
/// Return no transaction payloads.
|
||||
None,
|
||||
/// Return the account-list transaction representation.
|
||||
Accounts,
|
||||
}
|
||||
|
||||
impl SolanaTransactionDetails {
|
||||
/// Returns the Solana JSON-RPC wire string.
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
return match self {
|
||||
Self::Full => "full",
|
||||
Self::Signatures => "signatures",
|
||||
Self::None => "none",
|
||||
Self::Accounts => "accounts",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Modern configuration object accepted by `getBlock`.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SolanaGetBlockConfig {
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
||||
transaction_details: std::option::Option<crate::SolanaTransactionDetails>,
|
||||
max_supported_transaction_version: std::option::Option<u8>,
|
||||
rewards: std::option::Option<bool>,
|
||||
}
|
||||
|
||||
impl SolanaGetBlockConfig {
|
||||
/// Creates a modern `getBlock` configuration.
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
||||
transaction_details: std::option::Option<crate::SolanaTransactionDetails>,
|
||||
max_supported_transaction_version: std::option::Option<u8>,
|
||||
rewards: std::option::Option<bool>,
|
||||
) -> Self {
|
||||
return Self { commitment, encoding, transaction_details, max_supported_transaction_version, rewards };
|
||||
}
|
||||
|
||||
/// Returns the optional commitment level.
|
||||
#[must_use]
|
||||
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||
return self.commitment;
|
||||
}
|
||||
|
||||
/// Returns the optional transaction encoding.
|
||||
#[must_use]
|
||||
pub const fn encoding(&self) -> std::option::Option<crate::SolanaTransactionEncoding> {
|
||||
return self.encoding;
|
||||
}
|
||||
|
||||
/// Returns the optional transaction detail level.
|
||||
#[must_use]
|
||||
pub const fn transaction_details(&self) -> std::option::Option<crate::SolanaTransactionDetails> {
|
||||
return self.transaction_details;
|
||||
}
|
||||
|
||||
/// Returns the highest transaction version the caller declares it can consume.
|
||||
#[must_use]
|
||||
pub const fn max_supported_transaction_version(&self) -> std::option::Option<u8> {
|
||||
return self.max_supported_transaction_version;
|
||||
}
|
||||
|
||||
/// Returns whether rewards were explicitly requested or suppressed.
|
||||
#[must_use]
|
||||
pub const fn rewards(&self) -> std::option::Option<bool> {
|
||||
return self.rewards;
|
||||
}
|
||||
|
||||
/// Returns whether this config would serialize to an empty object.
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.commitment.is_none()
|
||||
&& self.encoding.is_none()
|
||||
&& self.transaction_details.is_none()
|
||||
&& self.max_supported_transaction_version.is_none()
|
||||
&& self.rewards.is_none();
|
||||
}
|
||||
|
||||
/// Serializes this config to the exact Solana JSON-RPC object.
|
||||
#[must_use]
|
||||
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 {
|
||||
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(encoding) = self.encoding {
|
||||
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(details) = self.transaction_details {
|
||||
object.insert("transactionDetails".to_owned(), serde_json::Value::String(details.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(version) = self.max_supported_transaction_version {
|
||||
object.insert("maxSupportedTransactionVersion".to_owned(), serde_json::Value::Number(version.into()));
|
||||
}
|
||||
if let std::option::Option::Some(rewards) = self.rewards {
|
||||
object.insert("rewards".to_owned(), serde_json::Value::Bool(rewards));
|
||||
}
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// Slot range accepted inside `getBlockProduction` configuration.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SolanaBlockProductionRange {
|
||||
first_slot: u64,
|
||||
last_slot: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl SolanaBlockProductionRange {
|
||||
/// Creates a block-production range.
|
||||
#[must_use]
|
||||
pub const fn new(first_slot: u64, last_slot: std::option::Option<u64>) -> Self {
|
||||
return Self { first_slot, last_slot };
|
||||
}
|
||||
|
||||
/// Returns the first slot in the requested range.
|
||||
#[must_use]
|
||||
pub const fn first_slot(&self) -> u64 {
|
||||
return self.first_slot;
|
||||
}
|
||||
|
||||
/// Returns the optional last slot in the requested range.
|
||||
#[must_use]
|
||||
pub const fn last_slot(&self) -> std::option::Option<u64> {
|
||||
return self.last_slot;
|
||||
}
|
||||
|
||||
/// Serializes this range to the exact Solana JSON-RPC object.
|
||||
#[must_use]
|
||||
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
object.insert("firstSlot".to_owned(), serde_json::Value::Number(self.first_slot.into()));
|
||||
if let std::option::Option::Some(last_slot) = self.last_slot {
|
||||
object.insert("lastSlot".to_owned(), serde_json::Value::Number(last_slot.into()));
|
||||
}
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional configuration accepted by `getBlockProduction`.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SolanaBlockProductionConfig {
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
identity: std::option::Option<ksp_core_lib::Pubkey>,
|
||||
range: std::option::Option<crate::SolanaBlockProductionRange>,
|
||||
}
|
||||
|
||||
impl SolanaBlockProductionConfig {
|
||||
/// Creates a block-production configuration.
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
identity: std::option::Option<ksp_core_lib::Pubkey>,
|
||||
range: std::option::Option<crate::SolanaBlockProductionRange>,
|
||||
) -> Self {
|
||||
return Self { commitment, identity, range };
|
||||
}
|
||||
|
||||
/// Returns the optional commitment level.
|
||||
#[must_use]
|
||||
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||
return self.commitment;
|
||||
}
|
||||
|
||||
/// Returns the optional validator identity filter.
|
||||
#[must_use]
|
||||
pub const fn identity(&self) -> std::option::Option<&ksp_core_lib::Pubkey> {
|
||||
return self.identity.as_ref();
|
||||
}
|
||||
|
||||
/// Returns the optional slot range.
|
||||
#[must_use]
|
||||
pub const fn range(&self) -> std::option::Option<&crate::SolanaBlockProductionRange> {
|
||||
return self.range.as_ref();
|
||||
}
|
||||
|
||||
/// Returns whether this config would serialize to an empty object.
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.commitment.is_none() && self.identity.is_none() && self.range.is_none();
|
||||
}
|
||||
|
||||
/// Serializes this config to the exact Solana JSON-RPC object.
|
||||
#[must_use]
|
||||
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 {
|
||||
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(identity) = self.identity.as_ref() {
|
||||
object.insert("identity".to_owned(), serde_json::Value::String(identity.to_string()));
|
||||
}
|
||||
if let std::option::Option::Some(range) = self.range {
|
||||
object.insert("range".to_owned(), range.to_json_value());
|
||||
}
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commitment distribution returned by `getBlockCommitment`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SolanaBlockCommitment {
|
||||
commitment: std::option::Option<std::vec::Vec<u64>>,
|
||||
total_stake: u64,
|
||||
}
|
||||
|
||||
impl SolanaBlockCommitment {
|
||||
/// Returns the nullable commitment stake distribution.
|
||||
#[must_use]
|
||||
pub fn commitment(&self) -> std::option::Option<&[u64]> {
|
||||
return self.commitment.as_deref();
|
||||
}
|
||||
|
||||
/// Returns the total active stake considered by the response.
|
||||
#[must_use]
|
||||
pub const fn total_stake(&self) -> u64 {
|
||||
return self.total_stake;
|
||||
}
|
||||
|
||||
/// Decodes a block-commitment result from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockCommitment>(method, value);
|
||||
return match decoded {
|
||||
std::result::Result::Ok(wire) => std::result::Result::Ok(Self { commitment: wire.commitment, total_stake: wire.total_stake }),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Effective range reported inside `getBlockProduction` results.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SolanaBlockProductionResultRange {
|
||||
first_slot: u64,
|
||||
last_slot: u64,
|
||||
}
|
||||
|
||||
impl SolanaBlockProductionResultRange {
|
||||
/// Returns the first slot in the effective range.
|
||||
#[must_use]
|
||||
pub const fn first_slot(&self) -> u64 {
|
||||
return self.first_slot;
|
||||
}
|
||||
|
||||
/// Returns the last slot in the effective range.
|
||||
#[must_use]
|
||||
pub const fn last_slot(&self) -> u64 {
|
||||
return self.last_slot;
|
||||
}
|
||||
}
|
||||
|
||||
/// Block-production counts returned by `getBlockProduction` before the shared RPC context wrapper is applied.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SolanaBlockProduction {
|
||||
by_identity: std::collections::BTreeMap<ksp_core_lib::Pubkey, (usize, usize)>,
|
||||
range: crate::SolanaBlockProductionResultRange,
|
||||
}
|
||||
|
||||
impl SolanaBlockProduction {
|
||||
/// Returns validator identities mapped to `(leader slots, blocks produced)` counts.
|
||||
#[must_use]
|
||||
pub const fn by_identity(&self) -> &std::collections::BTreeMap<ksp_core_lib::Pubkey, (usize, usize)> {
|
||||
return &self.by_identity;
|
||||
}
|
||||
|
||||
/// Returns the effective slot range reported by the runtime.
|
||||
#[must_use]
|
||||
pub const fn range(&self) -> &crate::SolanaBlockProductionResultRange {
|
||||
return &self.range;
|
||||
}
|
||||
|
||||
/// Decodes a block-production result from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockProduction>(method, value);
|
||||
let wire = match decoded {
|
||||
std::result::Result::Ok(wire) => wire,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut by_identity = std::collections::BTreeMap::new();
|
||||
for (identity, counts) in wire.by_identity {
|
||||
let parsed = crate::parse_wire_pubkey(method, "byIdentity", identity.as_str());
|
||||
let identity = match parsed {
|
||||
std::result::Result::Ok(identity) => identity,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
by_identity.insert(identity, counts);
|
||||
}
|
||||
return std::result::Result::Ok(Self {
|
||||
by_identity,
|
||||
range: crate::SolanaBlockProductionResultRange { first_slot: wire.range.first_slot, last_slot: wire.range.last_slot },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// One reward entry returned in a confirmed block.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SolanaBlockReward {
|
||||
pubkey: ksp_core_lib::Pubkey,
|
||||
lamports: i64,
|
||||
post_balance: u64,
|
||||
reward_type: std::option::Option<std::string::String>,
|
||||
commission: std::option::Option<u8>,
|
||||
commission_bps: crate::SolanaWireField<u16>,
|
||||
}
|
||||
|
||||
impl SolanaBlockReward {
|
||||
/// Returns the rewarded account public key.
|
||||
#[must_use]
|
||||
pub const fn pubkey(&self) -> &ksp_core_lib::Pubkey {
|
||||
return &self.pubkey;
|
||||
}
|
||||
|
||||
/// Returns the signed lamport delta applied by the reward.
|
||||
#[must_use]
|
||||
pub const fn lamports(&self) -> i64 {
|
||||
return self.lamports;
|
||||
}
|
||||
|
||||
/// Returns the post-reward account balance in lamports.
|
||||
#[must_use]
|
||||
pub const fn post_balance(&self) -> u64 {
|
||||
return self.post_balance;
|
||||
}
|
||||
|
||||
/// Returns the runtime reward type text without imposing a local economic taxonomy.
|
||||
#[must_use]
|
||||
pub fn reward_type(&self) -> std::option::Option<&str> {
|
||||
return self.reward_type.as_deref();
|
||||
}
|
||||
|
||||
/// Returns the nullable legacy/effective commission percentage.
|
||||
#[must_use]
|
||||
pub const fn commission(&self) -> std::option::Option<u8> {
|
||||
return self.commission;
|
||||
}
|
||||
|
||||
/// Returns the basis-point commission while preserving omitted/null/present wire states.
|
||||
#[must_use]
|
||||
pub const fn commission_bps(&self) -> &crate::SolanaWireField<u16> {
|
||||
return &self.commission_bps;
|
||||
}
|
||||
|
||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockReward>(method, value);
|
||||
let wire = match decoded {
|
||||
std::result::Result::Ok(wire) => wire,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let pubkey = crate::parse_wire_pubkey(method, "rewards.pubkey", wire.pubkey.as_str());
|
||||
return match pubkey {
|
||||
std::result::Result::Ok(pubkey) => std::result::Result::Ok(Self {
|
||||
pubkey,
|
||||
lamports: wire.lamports,
|
||||
post_balance: wire.post_balance,
|
||||
reward_type: wire.reward_type,
|
||||
commission: wire.commission,
|
||||
commission_bps: wire.commission_bps,
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// One transaction entry returned inside a confirmed block.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SolanaBlockTransaction {
|
||||
transaction: crate::SolanaEncodedTransaction,
|
||||
meta: crate::SolanaWireField<serde_json::Value>,
|
||||
version: crate::SolanaWireField<crate::SolanaTransactionVersion>,
|
||||
}
|
||||
|
||||
impl SolanaBlockTransaction {
|
||||
/// Returns the encoded or JSON transaction payload without Program-specific decoding.
|
||||
#[must_use]
|
||||
pub const fn transaction(&self) -> &crate::SolanaEncodedTransaction {
|
||||
return &self.transaction;
|
||||
}
|
||||
|
||||
/// Returns transaction metadata while preserving omission versus explicit `null`.
|
||||
#[must_use]
|
||||
pub const fn meta(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
||||
return &self.meta;
|
||||
}
|
||||
|
||||
/// Returns the transaction version while preserving omission versus explicit `null`.
|
||||
#[must_use]
|
||||
pub const fn version(&self) -> &crate::SolanaWireField<crate::SolanaTransactionVersion> {
|
||||
return &self.version;
|
||||
}
|
||||
|
||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireBlockTransaction>(method, value);
|
||||
let wire = match decoded {
|
||||
std::result::Result::Ok(wire) => wire,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let transaction = crate::SolanaEncodedTransaction::decode_wire(method, wire.transaction);
|
||||
let transaction = match transaction {
|
||||
std::result::Result::Ok(transaction) => transaction,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let version = decode_block_transaction_version(method, wire.version);
|
||||
return match version {
|
||||
std::result::Result::Ok(version) => std::result::Result::Ok(Self { transaction, meta: wire.meta, version }),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirmed block wire result returned by `getBlock` when the RPC result is non-null.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SolanaConfirmedBlock {
|
||||
previous_blockhash: std::string::String,
|
||||
blockhash: std::string::String,
|
||||
parent_slot: u64,
|
||||
transactions: crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>>,
|
||||
signatures: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
||||
rewards: crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>>,
|
||||
num_reward_partitions: crate::SolanaWireField<u64>,
|
||||
block_time: std::option::Option<i64>,
|
||||
block_height: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl SolanaConfirmedBlock {
|
||||
/// Returns the previous blockhash exactly as reported by the RPC wire.
|
||||
#[must_use]
|
||||
pub fn previous_blockhash(&self) -> &str {
|
||||
return self.previous_blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns this block's blockhash exactly as reported by the RPC wire.
|
||||
#[must_use]
|
||||
pub fn blockhash(&self) -> &str {
|
||||
return self.blockhash.as_str();
|
||||
}
|
||||
|
||||
/// Returns the parent slot.
|
||||
#[must_use]
|
||||
pub const fn parent_slot(&self) -> u64 {
|
||||
return self.parent_slot;
|
||||
}
|
||||
|
||||
/// Returns block transactions while preserving omitted/null/present states.
|
||||
#[must_use]
|
||||
pub const fn transactions(&self) -> &crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>> {
|
||||
return &self.transactions;
|
||||
}
|
||||
|
||||
/// Returns block signatures while preserving omitted/null/present states.
|
||||
#[must_use]
|
||||
pub const fn signatures(&self) -> &crate::SolanaWireField<std::vec::Vec<std::string::String>> {
|
||||
return &self.signatures;
|
||||
}
|
||||
|
||||
/// Returns block rewards while preserving omitted/null/present states.
|
||||
#[must_use]
|
||||
pub const fn rewards(&self) -> &crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>> {
|
||||
return &self.rewards;
|
||||
}
|
||||
|
||||
/// Returns the SIMD-0118 reward-partition count while preserving omission versus explicit `null`.
|
||||
#[must_use]
|
||||
pub const fn num_reward_partitions(&self) -> &crate::SolanaWireField<u64> {
|
||||
return &self.num_reward_partitions;
|
||||
}
|
||||
|
||||
/// Returns the nullable block time as a Unix timestamp.
|
||||
#[must_use]
|
||||
pub const fn block_time(&self) -> std::option::Option<i64> {
|
||||
return self.block_time;
|
||||
}
|
||||
|
||||
/// Returns the nullable block height.
|
||||
#[must_use]
|
||||
pub const fn block_height(&self) -> std::option::Option<u64> {
|
||||
return self.block_height;
|
||||
}
|
||||
|
||||
/// Decodes a confirmed block from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireConfirmedBlock>(method, value);
|
||||
let wire = match decoded {
|
||||
std::result::Result::Ok(wire) => wire,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let transactions = decode_block_transactions(method, wire.transactions);
|
||||
let transactions = match transactions {
|
||||
std::result::Result::Ok(transactions) => transactions,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rewards = decode_block_rewards(method, wire.rewards);
|
||||
let rewards = match rewards {
|
||||
std::result::Result::Ok(rewards) => rewards,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(Self {
|
||||
previous_blockhash: wire.previous_blockhash,
|
||||
blockhash: wire.blockhash,
|
||||
parent_slot: wire.parent_slot,
|
||||
transactions,
|
||||
signatures: wire.signatures,
|
||||
rewards,
|
||||
num_reward_partitions: wire.num_reward_partitions,
|
||||
block_time: wire.block_time,
|
||||
block_height: wire.block_height,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// One entry returned by `getRecentPerformanceSamples`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SolanaPerformanceSample {
|
||||
slot: u64,
|
||||
num_transactions: u64,
|
||||
num_non_vote_transactions: std::option::Option<u64>,
|
||||
num_slots: u64,
|
||||
sample_period_secs: u16,
|
||||
}
|
||||
|
||||
impl SolanaPerformanceSample {
|
||||
/// Returns the sample's highest slot.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the total transaction count in the sample.
|
||||
#[must_use]
|
||||
pub const fn num_transactions(&self) -> u64 {
|
||||
return self.num_transactions;
|
||||
}
|
||||
|
||||
/// Returns the optional non-vote transaction count exposed by newer runtimes.
|
||||
#[must_use]
|
||||
pub const fn num_non_vote_transactions(&self) -> std::option::Option<u64> {
|
||||
return self.num_non_vote_transactions;
|
||||
}
|
||||
|
||||
/// Returns the number of slots covered by the sample.
|
||||
#[must_use]
|
||||
pub const fn num_slots(&self) -> u64 {
|
||||
return self.num_slots;
|
||||
}
|
||||
|
||||
/// Returns the sample duration in seconds.
|
||||
#[must_use]
|
||||
pub const fn sample_period_secs(&self) -> u16 {
|
||||
return self.sample_period_secs;
|
||||
}
|
||||
|
||||
/// Decodes one performance sample from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WirePerformanceSample>(method, value);
|
||||
return match decoded {
|
||||
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
|
||||
slot: wire.slot,
|
||||
num_transactions: wire.num_transactions,
|
||||
num_non_vote_transactions: wire.num_non_vote_transactions,
|
||||
num_slots: wire.num_slots,
|
||||
sample_period_secs: wire.sample_period_secs,
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_block_transaction_version(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<serde_json::Value>,
|
||||
) -> ksp_core_lib::Result<crate::SolanaWireField<crate::SolanaTransactionVersion>> {
|
||||
return match field {
|
||||
crate::SolanaWireField::Omitted => std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
||||
crate::SolanaWireField::Null => std::result::Result::Ok(crate::SolanaWireField::Null),
|
||||
crate::SolanaWireField::Value(value) => {
|
||||
let decoded = crate::SolanaTransactionVersion::decode_wire(method, value);
|
||||
match decoded {
|
||||
std::result::Result::Ok(version) => std::result::Result::Ok(crate::SolanaWireField::Value(version)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn decode_block_transactions(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
) -> ksp_core_lib::Result<crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockTransaction>>> {
|
||||
let values = match field {
|
||||
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
||||
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
||||
crate::SolanaWireField::Value(values) => values,
|
||||
};
|
||||
let mut transactions = std::vec::Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let decoded = crate::SolanaBlockTransaction::decode_wire(method, value);
|
||||
match decoded {
|
||||
std::result::Result::Ok(transaction) => transactions.push(transaction),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(crate::SolanaWireField::Value(transactions));
|
||||
}
|
||||
|
||||
fn decode_block_rewards(
|
||||
method: &str,
|
||||
field: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
) -> ksp_core_lib::Result<crate::SolanaWireField<std::vec::Vec<crate::SolanaBlockReward>>> {
|
||||
let values = match field {
|
||||
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
||||
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
||||
crate::SolanaWireField::Value(values) => values,
|
||||
};
|
||||
let mut rewards = std::vec::Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
let decoded = crate::SolanaBlockReward::decode_wire(method, value);
|
||||
match decoded {
|
||||
std::result::Result::Ok(reward) => rewards.push(reward),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(crate::SolanaWireField::Value(rewards));
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockCommitment {
|
||||
commitment: std::option::Option<std::vec::Vec<u64>>,
|
||||
total_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockProductionRange {
|
||||
first_slot: u64,
|
||||
last_slot: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockProduction {
|
||||
by_identity: std::collections::BTreeMap<std::string::String, (usize, usize)>,
|
||||
range: WireBlockProductionRange,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockReward {
|
||||
pubkey: std::string::String,
|
||||
lamports: i64,
|
||||
post_balance: u64,
|
||||
#[serde(default)]
|
||||
reward_type: std::option::Option<std::string::String>,
|
||||
#[serde(default)]
|
||||
commission: std::option::Option<u8>,
|
||||
#[serde(default)]
|
||||
commission_bps: crate::SolanaWireField<u16>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireBlockTransaction {
|
||||
transaction: serde_json::Value,
|
||||
#[serde(default)]
|
||||
meta: crate::SolanaWireField<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
version: crate::SolanaWireField<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireConfirmedBlock {
|
||||
previous_blockhash: std::string::String,
|
||||
blockhash: std::string::String,
|
||||
parent_slot: u64,
|
||||
#[serde(default)]
|
||||
transactions: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
#[serde(default)]
|
||||
signatures: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
||||
#[serde(default)]
|
||||
rewards: crate::SolanaWireField<std::vec::Vec<serde_json::Value>>,
|
||||
#[serde(default)]
|
||||
num_reward_partitions: crate::SolanaWireField<u64>,
|
||||
block_time: std::option::Option<i64>,
|
||||
block_height: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WirePerformanceSample {
|
||||
slot: u64,
|
||||
num_transactions: u64,
|
||||
#[serde(default)]
|
||||
num_non_vote_transactions: std::option::Option<u64>,
|
||||
num_slots: u64,
|
||||
sample_period_secs: u16,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/rpc_blocks.rs"]
|
||||
mod tests;
|
||||
377
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
Normal file
377
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
Normal file
@@ -0,0 +1,377 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_economics.rs
|
||||
// version: 1
|
||||
|
||||
/// Inflation-governor values returned by `getInflationGovernor`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SolanaInflationGovernor {
|
||||
initial: f64,
|
||||
terminal: f64,
|
||||
taper: f64,
|
||||
foundation: f64,
|
||||
foundation_term: f64,
|
||||
}
|
||||
|
||||
impl SolanaInflationGovernor {
|
||||
/// Returns the initial inflation rate.
|
||||
#[must_use]
|
||||
pub const fn initial(&self) -> f64 {
|
||||
return self.initial;
|
||||
}
|
||||
|
||||
/// Returns the terminal inflation rate.
|
||||
#[must_use]
|
||||
pub const fn terminal(&self) -> f64 {
|
||||
return self.terminal;
|
||||
}
|
||||
|
||||
/// Returns the taper/disinflation parameter reported by the runtime.
|
||||
#[must_use]
|
||||
pub const fn taper(&self) -> f64 {
|
||||
return self.taper;
|
||||
}
|
||||
|
||||
/// Returns the foundation allocation rate.
|
||||
#[must_use]
|
||||
pub const fn foundation(&self) -> f64 {
|
||||
return self.foundation;
|
||||
}
|
||||
|
||||
/// Returns the foundation allocation term.
|
||||
#[must_use]
|
||||
pub const fn foundation_term(&self) -> f64 {
|
||||
return self.foundation_term;
|
||||
}
|
||||
|
||||
/// Decodes an inflation-governor result from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireInflationGovernor>(method, value);
|
||||
return match decoded {
|
||||
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
|
||||
initial: wire.initial,
|
||||
terminal: wire.terminal,
|
||||
taper: wire.taper,
|
||||
foundation: wire.foundation,
|
||||
foundation_term: wire.foundation_term,
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Current inflation-rate values returned by `getInflationRate`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SolanaInflationRate {
|
||||
total: f64,
|
||||
validator: f64,
|
||||
foundation: f64,
|
||||
epoch: u64,
|
||||
}
|
||||
|
||||
impl SolanaInflationRate {
|
||||
/// Returns the total inflation rate.
|
||||
#[must_use]
|
||||
pub const fn total(&self) -> f64 {
|
||||
return self.total;
|
||||
}
|
||||
|
||||
/// Returns the validator inflation rate.
|
||||
#[must_use]
|
||||
pub const fn validator(&self) -> f64 {
|
||||
return self.validator;
|
||||
}
|
||||
|
||||
/// Returns the foundation inflation rate.
|
||||
#[must_use]
|
||||
pub const fn foundation(&self) -> f64 {
|
||||
return self.foundation;
|
||||
}
|
||||
|
||||
/// Returns the epoch associated with the rate.
|
||||
#[must_use]
|
||||
pub const fn epoch(&self) -> u64 {
|
||||
return self.epoch;
|
||||
}
|
||||
|
||||
/// Decodes an inflation-rate result from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireInflationRate>(method, value);
|
||||
return match decoded {
|
||||
std::result::Result::Ok(wire) => {
|
||||
std::result::Result::Ok(Self { total: wire.total, validator: wire.validator, foundation: wire.foundation, epoch: wire.epoch })
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional epoch/context configuration accepted by `getInflationReward`.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SolanaInflationRewardConfig {
|
||||
epoch: std::option::Option<u64>,
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
min_context_slot: std::option::Option<u64>,
|
||||
}
|
||||
|
||||
impl SolanaInflationRewardConfig {
|
||||
/// Creates an inflation-reward configuration.
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
epoch: std::option::Option<u64>,
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
min_context_slot: std::option::Option<u64>,
|
||||
) -> Self {
|
||||
return Self { epoch, commitment, min_context_slot };
|
||||
}
|
||||
|
||||
/// Returns the optional target epoch.
|
||||
#[must_use]
|
||||
pub const fn epoch(&self) -> std::option::Option<u64> {
|
||||
return self.epoch;
|
||||
}
|
||||
|
||||
/// Returns the optional commitment level.
|
||||
#[must_use]
|
||||
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||
return self.commitment;
|
||||
}
|
||||
|
||||
/// Returns the optional minimum context slot.
|
||||
#[must_use]
|
||||
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
|
||||
return self.min_context_slot;
|
||||
}
|
||||
|
||||
/// Returns whether this config would serialize to an empty object.
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.epoch.is_none() && self.commitment.is_none() && self.min_context_slot.is_none();
|
||||
}
|
||||
|
||||
/// Serializes this config to the exact Solana JSON-RPC object.
|
||||
#[must_use]
|
||||
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
if let std::option::Option::Some(epoch) = self.epoch {
|
||||
object.insert("epoch".to_owned(), serde_json::Value::Number(epoch.into()));
|
||||
}
|
||||
if let std::option::Option::Some(commitment) = self.commitment {
|
||||
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(min_context_slot) = self.min_context_slot {
|
||||
object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into()));
|
||||
}
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// One non-null positional reward returned by `getInflationReward`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SolanaInflationReward {
|
||||
epoch: u64,
|
||||
effective_slot: u64,
|
||||
amount: u64,
|
||||
post_balance: u64,
|
||||
commission: std::option::Option<u8>,
|
||||
commission_bps: crate::SolanaWireField<u16>,
|
||||
}
|
||||
|
||||
impl SolanaInflationReward {
|
||||
/// Returns the rewarded epoch.
|
||||
#[must_use]
|
||||
pub const fn epoch(&self) -> u64 {
|
||||
return self.epoch;
|
||||
}
|
||||
|
||||
/// Returns the slot at which the reward became effective.
|
||||
#[must_use]
|
||||
pub const fn effective_slot(&self) -> u64 {
|
||||
return self.effective_slot;
|
||||
}
|
||||
|
||||
/// Returns the reward amount in lamports.
|
||||
#[must_use]
|
||||
pub const fn amount(&self) -> u64 {
|
||||
return self.amount;
|
||||
}
|
||||
|
||||
/// Returns the post-reward account balance in lamports.
|
||||
#[must_use]
|
||||
pub const fn post_balance(&self) -> u64 {
|
||||
return self.post_balance;
|
||||
}
|
||||
|
||||
/// Returns the nullable legacy/effective commission percentage.
|
||||
#[must_use]
|
||||
pub const fn commission(&self) -> std::option::Option<u8> {
|
||||
return self.commission;
|
||||
}
|
||||
|
||||
/// Returns the basis-point commission while preserving omitted/null/present wire states.
|
||||
#[must_use]
|
||||
pub const fn commission_bps(&self) -> &crate::SolanaWireField<u16> {
|
||||
return &self.commission_bps;
|
||||
}
|
||||
|
||||
/// Decodes one non-null inflation reward from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireInflationReward>(method, value);
|
||||
return match decoded {
|
||||
std::result::Result::Ok(wire) => std::result::Result::Ok(Self {
|
||||
epoch: wire.epoch,
|
||||
effective_slot: wire.effective_slot,
|
||||
amount: wire.amount,
|
||||
post_balance: wire.post_balance,
|
||||
commission: wire.commission,
|
||||
commission_bps: wire.commission_bps,
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional configuration accepted by `getSupply`.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SolanaSupplyConfig {
|
||||
commitment: std::option::Option<crate::SolanaCommitment>,
|
||||
exclude_non_circulating_accounts_list: std::option::Option<bool>,
|
||||
}
|
||||
|
||||
impl SolanaSupplyConfig {
|
||||
/// Creates a supply configuration.
|
||||
#[must_use]
|
||||
pub const fn new(commitment: std::option::Option<crate::SolanaCommitment>, exclude_non_circulating_accounts_list: std::option::Option<bool>) -> Self {
|
||||
return Self { commitment, exclude_non_circulating_accounts_list };
|
||||
}
|
||||
|
||||
/// Returns the optional commitment level.
|
||||
#[must_use]
|
||||
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
||||
return self.commitment;
|
||||
}
|
||||
|
||||
/// Returns whether the request explicitly controls omission of the non-circulating account list.
|
||||
#[must_use]
|
||||
pub const fn exclude_non_circulating_accounts_list(&self) -> std::option::Option<bool> {
|
||||
return self.exclude_non_circulating_accounts_list;
|
||||
}
|
||||
|
||||
/// Returns whether this config would serialize to an empty object.
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
return self.commitment.is_none() && self.exclude_non_circulating_accounts_list.is_none();
|
||||
}
|
||||
|
||||
/// Serializes this config to the exact Solana JSON-RPC object.
|
||||
#[must_use]
|
||||
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 {
|
||||
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
||||
}
|
||||
if let std::option::Option::Some(exclude) = self.exclude_non_circulating_accounts_list {
|
||||
object.insert("excludeNonCirculatingAccountsList".to_owned(), serde_json::Value::Bool(exclude));
|
||||
}
|
||||
return serde_json::Value::Object(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// Supply totals returned inside the contextual `getSupply` response.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SolanaSupply {
|
||||
total: u64,
|
||||
circulating: u64,
|
||||
non_circulating: u64,
|
||||
non_circulating_accounts: std::vec::Vec<ksp_core_lib::Pubkey>,
|
||||
}
|
||||
|
||||
impl SolanaSupply {
|
||||
/// Returns the total supply in lamports.
|
||||
#[must_use]
|
||||
pub const fn total(&self) -> u64 {
|
||||
return self.total;
|
||||
}
|
||||
|
||||
/// Returns the circulating supply in lamports.
|
||||
#[must_use]
|
||||
pub const fn circulating(&self) -> u64 {
|
||||
return self.circulating;
|
||||
}
|
||||
|
||||
/// Returns the non-circulating supply in lamports.
|
||||
#[must_use]
|
||||
pub const fn non_circulating(&self) -> u64 {
|
||||
return self.non_circulating;
|
||||
}
|
||||
|
||||
/// Returns the ordered non-circulating account list supplied by the runtime.
|
||||
#[must_use]
|
||||
pub fn non_circulating_accounts(&self) -> &[ksp_core_lib::Pubkey] {
|
||||
return self.non_circulating_accounts.as_slice();
|
||||
}
|
||||
|
||||
/// Decodes a supply result from its Solana JSON wire shape.
|
||||
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
let decoded = crate::decode_wire_json::<WireSupply>(method, value);
|
||||
let wire = match decoded {
|
||||
std::result::Result::Ok(wire) => wire,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut accounts = std::vec::Vec::with_capacity(wire.non_circulating_accounts.len());
|
||||
for account in wire.non_circulating_accounts {
|
||||
let parsed = crate::parse_wire_pubkey(method, "nonCirculatingAccounts", account.as_str());
|
||||
match parsed {
|
||||
std::result::Result::Ok(pubkey) => accounts.push(pubkey),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(Self {
|
||||
total: wire.total,
|
||||
circulating: wire.circulating,
|
||||
non_circulating: wire.non_circulating,
|
||||
non_circulating_accounts: accounts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireInflationGovernor {
|
||||
initial: f64,
|
||||
terminal: f64,
|
||||
taper: f64,
|
||||
foundation: f64,
|
||||
foundation_term: f64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireInflationRate {
|
||||
total: f64,
|
||||
validator: f64,
|
||||
foundation: f64,
|
||||
epoch: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireInflationReward {
|
||||
epoch: u64,
|
||||
effective_slot: u64,
|
||||
amount: u64,
|
||||
post_balance: u64,
|
||||
#[serde(default)]
|
||||
commission: std::option::Option<u8>,
|
||||
#[serde(default)]
|
||||
commission_bps: crate::SolanaWireField<u16>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireSupply {
|
||||
total: u64,
|
||||
circulating: u64,
|
||||
non_circulating: u64,
|
||||
non_circulating_accounts: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/rpc_economics.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
/// Binary encoding accepted for serialized transaction input payloads.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -838,7 +838,8 @@ pub enum SolanaTransactionVersion {
|
||||
}
|
||||
|
||||
impl SolanaTransactionVersion {
|
||||
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
||||
/// Decodes a transaction version from the shared Solana JSON wire representation.
|
||||
pub(crate) 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),
|
||||
serde_json::Value::Number(value) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
|
||||
|
||||
@@ -347,3 +347,42 @@ fn public_transaction_pre_007_simulation_wrapper_and_complete_config_are_availab
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaAccountEncoding::Base64Zstd),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_v0_2_4_pre_002_blocks_economics_types_are_available_from_crate_root() {
|
||||
let block = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::Json),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionDetails::Full),
|
||||
std::option::Option::Some(1),
|
||||
std::option::Option::Some(true),
|
||||
);
|
||||
assert_eq!(block.max_supported_transaction_version(), std::option::Option::Some(1));
|
||||
let range = ksp_onchain_transport_lib::SolanaBlockProductionRange::new(100, std::option::Option::Some(200));
|
||||
assert_eq!(range.last_slot(), std::option::Option::Some(200));
|
||||
let production_config = ksp_onchain_transport_lib::SolanaBlockProductionConfig::new(
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(range),
|
||||
);
|
||||
assert_eq!(production_config.range().map(ksp_onchain_transport_lib::SolanaBlockProductionRange::first_slot), std::option::Option::Some(100));
|
||||
let inflation = ksp_onchain_transport_lib::SolanaInflationRewardConfig::new(
|
||||
std::option::Option::Some(812),
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
|
||||
std::option::Option::Some(430_000_000),
|
||||
);
|
||||
assert_eq!(inflation.epoch(), std::option::Option::Some(812));
|
||||
let supply = ksp_onchain_transport_lib::SolanaSupplyConfig::new(std::option::Option::None, std::option::Option::Some(false));
|
||||
assert_eq!(supply.exclude_non_circulating_accounts_list(), std::option::Option::Some(false));
|
||||
let _block_commitment: std::option::Option<ksp_onchain_transport_lib::SolanaBlockCommitment> = std::option::Option::None;
|
||||
let _block_production: std::option::Option<ksp_onchain_transport_lib::SolanaBlockProduction> = std::option::Option::None;
|
||||
let _block_production_result_range: std::option::Option<ksp_onchain_transport_lib::SolanaBlockProductionResultRange> = std::option::Option::None;
|
||||
let _block_reward: std::option::Option<ksp_onchain_transport_lib::SolanaBlockReward> = std::option::Option::None;
|
||||
let _block_transaction: std::option::Option<ksp_onchain_transport_lib::SolanaBlockTransaction> = std::option::Option::None;
|
||||
let _confirmed_block: std::option::Option<ksp_onchain_transport_lib::SolanaConfirmedBlock> = std::option::Option::None;
|
||||
let _performance_sample: std::option::Option<ksp_onchain_transport_lib::SolanaPerformanceSample> = std::option::Option::None;
|
||||
let _governor: std::option::Option<ksp_onchain_transport_lib::SolanaInflationGovernor> = std::option::Option::None;
|
||||
let _rate: std::option::Option<ksp_onchain_transport_lib::SolanaInflationRate> = std::option::Option::None;
|
||||
let _reward: std::option::Option<ksp_onchain_transport_lib::SolanaInflationReward> = std::option::Option::None;
|
||||
let _supply: std::option::Option<ksp_onchain_transport_lib::SolanaSupply> = std::option::Option::None;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
|
||||
//! Release-level completeness canaries for the staged HTTP wrapper sequence.
|
||||
|
||||
#[test]
|
||||
fn release_registry_partition_matches_the_audited_http_plan() {
|
||||
@@ -430,3 +430,44 @@ fn release_pre_008_ksp_transport_007_retro_audit_covers_all_typed_current_method
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_v0_2_4_descriptor_set_is_exact_and_remains_read_retry_safe_before_wrappers() {
|
||||
let mut expected_blocks = std::vec![
|
||||
"getBlock",
|
||||
"getBlockCommitment",
|
||||
"getBlockHeight",
|
||||
"getBlockProduction",
|
||||
"getBlocks",
|
||||
"getBlocksWithLimit",
|
||||
"getBlockTime",
|
||||
"getFirstAvailableBlock",
|
||||
"getRecentPerformanceSamples",
|
||||
"minimumLedgerSlot",
|
||||
];
|
||||
let mut expected_economics = std::vec!["getInflationGovernor", "getInflationRate", "getInflationReward", "getStakeMinimumDelegation", "getSupply",];
|
||||
let mut actual_blocks = std::vec::Vec::new();
|
||||
let mut actual_economics = std::vec::Vec::new();
|
||||
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
|
||||
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_4 {
|
||||
continue;
|
||||
}
|
||||
assert_eq!(descriptor.operation_kind(), ksp_onchain_transport_lib::RpcOperationKind::Read);
|
||||
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
|
||||
match descriptor.category() {
|
||||
ksp_onchain_transport_lib::HttpRpcCategory::Blocks => actual_blocks.push(descriptor.method()),
|
||||
ksp_onchain_transport_lib::HttpRpcCategory::Economics => actual_economics.push(descriptor.method()),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
actual_blocks.sort_unstable();
|
||||
actual_economics.sort_unstable();
|
||||
expected_blocks.sort_unstable();
|
||||
expected_economics.sort_unstable();
|
||||
assert_eq!(actual_blocks, expected_blocks);
|
||||
assert_eq!(actual_economics, expected_economics);
|
||||
assert_eq!(actual_blocks.len(), 10);
|
||||
assert_eq!(actual_economics.len(), 5);
|
||||
let get_block = ksp_onchain_transport_lib::find_http_rpc_method("getBlock").expect("getBlock descriptor must exist");
|
||||
assert!(get_block.request_form_status().has_deprecated_legacy());
|
||||
}
|
||||
|
||||
150
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
Normal file
150
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn transaction_details_and_get_block_config_preserve_all_modern_options() {
|
||||
assert_eq!(crate::SolanaTransactionDetails::Full.as_str(), "full");
|
||||
assert_eq!(crate::SolanaTransactionDetails::Signatures.as_str(), "signatures");
|
||||
assert_eq!(crate::SolanaTransactionDetails::None.as_str(), "none");
|
||||
assert_eq!(crate::SolanaTransactionDetails::Accounts.as_str(), "accounts");
|
||||
let config = crate::SolanaGetBlockConfig::new(
|
||||
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
|
||||
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
|
||||
std::option::Option::Some(crate::SolanaTransactionDetails::Accounts),
|
||||
std::option::Option::Some(1),
|
||||
std::option::Option::Some(false),
|
||||
);
|
||||
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
||||
assert_eq!(config.encoding(), std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed));
|
||||
assert_eq!(config.transaction_details(), std::option::Option::Some(crate::SolanaTransactionDetails::Accounts));
|
||||
assert_eq!(config.max_supported_transaction_version(), std::option::Option::Some(1));
|
||||
assert_eq!(config.rewards(), std::option::Option::Some(false));
|
||||
assert_eq!(
|
||||
config.to_json_value(),
|
||||
serde_json::json!({
|
||||
"commitment":"confirmed",
|
||||
"encoding":"jsonParsed",
|
||||
"transactionDetails":"accounts",
|
||||
"maxSupportedTransactionVersion":1,
|
||||
"rewards":false
|
||||
})
|
||||
);
|
||||
assert!(crate::SolanaGetBlockConfig::default().is_empty());
|
||||
assert_eq!(crate::SolanaGetBlockConfig::default().to_json_value(), serde_json::json!({}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_production_config_preserves_identity_range_and_commitment() {
|
||||
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
|
||||
let range = crate::SolanaBlockProductionRange::new(430_000_000, std::option::Option::Some(430_000_099));
|
||||
let config = crate::SolanaBlockProductionConfig::new(
|
||||
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||
std::option::Option::Some(identity),
|
||||
std::option::Option::Some(range),
|
||||
);
|
||||
assert_eq!(config.identity(), std::option::Option::Some(&identity));
|
||||
assert_eq!(config.range().expect("range must be present").first_slot(), 430_000_000);
|
||||
assert_eq!(config.range().expect("range must be present").last_slot(), std::option::Option::Some(430_000_099));
|
||||
assert_eq!(
|
||||
config.to_json_value(),
|
||||
serde_json::json!({
|
||||
"commitment":"finalized",
|
||||
"identity":"11111111111111111111111111111111",
|
||||
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||
})
|
||||
);
|
||||
assert!(crate::SolanaBlockProductionConfig::default().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_commitment_fixture_preserves_nullable_distribution() {
|
||||
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/block_commitment.variants.json"))
|
||||
.expect("block commitment fixtures must decode");
|
||||
let present = crate::SolanaBlockCommitment::decode_wire("getBlockCommitment", values[0].clone()).expect("present commitment must decode");
|
||||
let absent = crate::SolanaBlockCommitment::decode_wire("getBlockCommitment", values[1].clone()).expect("null commitment must decode");
|
||||
assert_eq!(present.commitment(), std::option::Option::Some(&[1, 2, 3, 4][..]));
|
||||
assert_eq!(present.total_stake(), 1_000_000_000);
|
||||
assert_eq!(absent.commitment(), std::option::Option::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_production_fixture_types_identity_keys_and_preserves_counts() {
|
||||
let value =
|
||||
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/block_production.v4_2_1.json")).expect("block production fixture must decode");
|
||||
let result = crate::SolanaBlockProduction::decode_wire("getBlockProduction", value).expect("block production result must decode");
|
||||
let identity = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture identity must parse");
|
||||
assert_eq!(result.by_identity().get(&identity), std::option::Option::Some(&(8, 7)));
|
||||
assert_eq!(result.range().first_slot(), 430_000_000);
|
||||
assert_eq!(result.range().last_slot(), 430_000_099);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmed_block_fixture_preserves_rich_wire_and_simd_extensions() {
|
||||
let value =
|
||||
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/confirmed_block.v4_2_1.json")).expect("confirmed block fixture must decode");
|
||||
let block = crate::SolanaConfirmedBlock::decode_wire("getBlock", value).expect("confirmed block must decode");
|
||||
assert_eq!(block.parent_slot(), 430_000_122);
|
||||
assert_eq!(block.block_time(), std::option::Option::Some(1_787_072_400));
|
||||
assert_eq!(block.block_height(), std::option::Option::Some(410_000_000));
|
||||
assert_eq!(block.num_reward_partitions().value(), std::option::Option::Some(&4));
|
||||
let transactions = block.transactions().value().expect("transactions must be present");
|
||||
assert_eq!(transactions.len(), 2);
|
||||
assert!(matches!(transactions[0].version(), crate::SolanaWireField::Value(crate::SolanaTransactionVersion::Number(1))));
|
||||
assert!(transactions[1].version().is_omitted());
|
||||
assert!(transactions[1].meta().is_null());
|
||||
let rewards = block.rewards().value().expect("rewards must be present");
|
||||
assert_eq!(rewards.len(), 2);
|
||||
assert_eq!(rewards[0].reward_type(), std::option::Option::Some("Fee"));
|
||||
assert_eq!(rewards[0].commission(), std::option::Option::None);
|
||||
assert_eq!(rewards[0].commission_bps().value(), std::option::Option::Some(&1_234));
|
||||
assert_eq!(rewards[1].commission(), std::option::Option::Some(5));
|
||||
assert!(rewards[1].commission_bps().is_omitted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmed_block_fixture_distinguishes_omitted_and_explicit_null_fields() {
|
||||
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/confirmed_block.omissions.json"))
|
||||
.expect("confirmed block omission fixtures must decode");
|
||||
let omitted = crate::SolanaConfirmedBlock::decode_wire("getBlock", values[0].clone()).expect("omitted-fields block must decode");
|
||||
let nulls = crate::SolanaConfirmedBlock::decode_wire("getBlock", values[1].clone()).expect("null-fields block must decode");
|
||||
assert!(omitted.transactions().is_omitted());
|
||||
assert!(omitted.signatures().is_omitted());
|
||||
assert!(omitted.rewards().is_omitted());
|
||||
assert!(omitted.num_reward_partitions().is_omitted());
|
||||
assert!(nulls.transactions().is_null());
|
||||
assert!(nulls.signatures().is_null());
|
||||
assert!(nulls.rewards().is_null());
|
||||
assert!(nulls.num_reward_partitions().is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn performance_sample_fixture_accepts_current_and_older_shapes() {
|
||||
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/performance_sample.variants.json"))
|
||||
.expect("performance sample fixtures must decode");
|
||||
let current = crate::SolanaPerformanceSample::decode_wire("getRecentPerformanceSamples", values[0].clone()).expect("current sample must decode");
|
||||
let older = crate::SolanaPerformanceSample::decode_wire("getRecentPerformanceSamples", values[1].clone()).expect("older sample must decode");
|
||||
assert_eq!(current.slot(), 430_000_123);
|
||||
assert_eq!(current.num_transactions(), 250_000);
|
||||
assert_eq!(current.num_non_vote_transactions(), std::option::Option::Some(175_000));
|
||||
assert_eq!(current.num_slots(), 120);
|
||||
assert_eq!(current.sample_period_secs(), 60);
|
||||
assert_eq!(older.num_non_vote_transactions(), std::option::Option::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_reward_rejects_invalid_pubkey_without_echoing_value() {
|
||||
let result = crate::SolanaConfirmedBlock::decode_wire(
|
||||
"getBlock",
|
||||
serde_json::json!({
|
||||
"previousBlockhash":"previous",
|
||||
"blockhash":"block",
|
||||
"parentSlot":1,
|
||||
"rewards":[{"pubkey":"not-a-pubkey","lamports":1,"postBalance":2,"rewardType":"Fee","commission":null}],
|
||||
"blockTime":null,
|
||||
"blockHeight":null
|
||||
}),
|
||||
);
|
||||
let error = result.expect_err("invalid reward pubkey must fail closed");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||
assert!(!error.to_string().contains("not-a-pubkey"));
|
||||
}
|
||||
86
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
Normal file
86
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn inflation_reward_config_preserves_epoch_commitment_and_min_context_slot() {
|
||||
let config = crate::SolanaInflationRewardConfig::new(
|
||||
std::option::Option::Some(811),
|
||||
std::option::Option::Some(crate::SolanaCommitment::Finalized),
|
||||
std::option::Option::Some(429_000_000),
|
||||
);
|
||||
assert_eq!(config.epoch(), std::option::Option::Some(811));
|
||||
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
|
||||
assert_eq!(config.min_context_slot(), std::option::Option::Some(429_000_000));
|
||||
assert_eq!(config.to_json_value(), serde_json::json!({"epoch":811,"commitment":"finalized","minContextSlot":429000000}));
|
||||
assert!(crate::SolanaInflationRewardConfig::default().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supply_config_distinguishes_omitted_from_explicit_false() {
|
||||
let omitted = crate::SolanaSupplyConfig::default();
|
||||
let explicit_false = crate::SolanaSupplyConfig::new(std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(false));
|
||||
assert!(omitted.is_empty());
|
||||
assert_eq!(omitted.to_json_value(), serde_json::json!({}));
|
||||
assert_eq!(explicit_false.exclude_non_circulating_accounts_list(), std::option::Option::Some(false));
|
||||
assert_eq!(explicit_false.to_json_value(), serde_json::json!({"commitment":"confirmed","excludeNonCirculatingAccountsList":false}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflation_governor_and_rate_fixtures_preserve_runtime_values() {
|
||||
let governor_value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/inflation_governor.v4_2_1.json"))
|
||||
.expect("inflation governor fixture must decode");
|
||||
let rate_value =
|
||||
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/inflation_rate.v4_2_1.json")).expect("inflation rate fixture must decode");
|
||||
let governor = crate::SolanaInflationGovernor::decode_wire("getInflationGovernor", governor_value).expect("inflation governor must decode");
|
||||
let rate = crate::SolanaInflationRate::decode_wire("getInflationRate", rate_value).expect("inflation rate must decode");
|
||||
assert_eq!(governor.initial(), 0.08);
|
||||
assert_eq!(governor.terminal(), 0.015);
|
||||
assert_eq!(governor.taper(), 0.15);
|
||||
assert_eq!(governor.foundation(), 0.05);
|
||||
assert_eq!(governor.foundation_term(), 7.0);
|
||||
assert_eq!(rate.total(), 0.042);
|
||||
assert_eq!(rate.validator(), 0.041);
|
||||
assert_eq!(rate.foundation(), 0.001);
|
||||
assert_eq!(rate.epoch(), 812);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inflation_reward_fixture_preserves_position_null_and_commission_bps_states() {
|
||||
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/inflation_reward.variants.json"))
|
||||
.expect("inflation reward fixtures must decode");
|
||||
let first = crate::SolanaInflationReward::decode_wire("getInflationReward", values[0].clone()).expect("first reward must decode");
|
||||
let third = crate::SolanaInflationReward::decode_wire("getInflationReward", values[2].clone()).expect("third reward must decode");
|
||||
assert_eq!(values.len(), 3);
|
||||
assert!(values[1].is_null());
|
||||
assert_eq!(first.epoch(), 811);
|
||||
assert_eq!(first.effective_slot(), 429_900_000);
|
||||
assert_eq!(first.amount(), 2_500_000);
|
||||
assert_eq!(first.post_balance(), 1_002_500_000);
|
||||
assert_eq!(first.commission(), std::option::Option::Some(7));
|
||||
assert_eq!(first.commission_bps().value(), std::option::Option::Some(&725));
|
||||
assert_eq!(third.commission(), std::option::Option::None);
|
||||
assert!(third.commission_bps().is_omitted());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supply_fixture_types_non_circulating_accounts_and_preserves_order() {
|
||||
let value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/supply.v4_2_1.json")).expect("supply fixture must decode");
|
||||
let supply = crate::SolanaSupply::decode_wire("getSupply", value).expect("supply must decode");
|
||||
assert_eq!(supply.total(), 600_000_000_000_000_000);
|
||||
assert_eq!(supply.circulating(), 500_000_000_000_000_000);
|
||||
assert_eq!(supply.non_circulating(), 100_000_000_000_000_000);
|
||||
assert_eq!(supply.non_circulating_accounts().len(), 2);
|
||||
assert_eq!(supply.non_circulating_accounts()[0].to_string(), "11111111111111111111111111111111");
|
||||
assert_eq!(supply.non_circulating_accounts()[1].to_string(), "Vote111111111111111111111111111111111111111");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supply_rejects_invalid_non_circulating_pubkey_without_echoing_value() {
|
||||
let result = crate::SolanaSupply::decode_wire(
|
||||
"getSupply",
|
||||
serde_json::json!({"total":3,"circulating":2,"nonCirculating":1,"nonCirculatingAccounts":["invalid-pubkey"]}),
|
||||
);
|
||||
let error = result.expect_err("invalid supply pubkey must fail closed");
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
|
||||
assert!(!error.to_string().contains("invalid-pubkey"));
|
||||
}
|
||||
331
deltas/0.2.4/pre.002.md
Normal file
331
deltas/0.2.4/pre.002.md
Normal file
@@ -0,0 +1,331 @@
|
||||
<!-- file: deltas/0.2.4/pre.002.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.2.4-pre.002` — primitives Blocks/Economics et fixtures wire partagées
|
||||
|
||||
## Base requise
|
||||
|
||||
Livraison précédente :
|
||||
|
||||
```text
|
||||
0.2.4-pre.001-fix.001
|
||||
workspace.package.version = "0.2.4-pre.1"
|
||||
```
|
||||
|
||||
Le plan canonique est `docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md` version 2. Le fix `pre.001-fix.001` est appliqué avant cette tranche ; les deltas historiques `pre.001.md` et `pre.001-fix.001.md` restent inchangés.
|
||||
|
||||
## Objectif
|
||||
|
||||
Installer les primitives, configurations et résultats partagés nécessaires aux 10 wrappers Blocks et 5 wrappers Economics de `0.2.4`, ainsi que leurs fixtures wire locales déterministes, sans encore implémenter les wrappers prévus à partir de `pre.003` et sans déclarer la couverture typed `V0_2_4` complète.
|
||||
|
||||
Cette tranche matérialise dès maintenant les exigences `KSP-TRANSPORT-007` qui appartiennent au wire partagé : omissions/null explicites, versions de transaction numériques génériques, reward partitions SIMD-0118, commissions basis-points SIMD-0291, adresses RPC typées en `Pubkey` et sous-arbres Transaction riches conservés losslessly.
|
||||
|
||||
## Version Cargo
|
||||
|
||||
Conformément à `VER-ID-009`, la nouvelle prerelease synchronise le signal technique :
|
||||
|
||||
```text
|
||||
0.2.4-pre.1 -> 0.2.4-pre.2
|
||||
```
|
||||
|
||||
Aucune dépendance ni feature Cargo n'est ajoutée ou modifiée.
|
||||
|
||||
## Implémentation
|
||||
|
||||
### Module Blocks partagé
|
||||
|
||||
Le nouveau module privé `rpc_blocks` ajoute les types publics suivants, réexportés explicitement depuis la racine de crate :
|
||||
|
||||
```text
|
||||
SolanaTransactionDetails
|
||||
SolanaGetBlockConfig
|
||||
SolanaBlockProductionRange
|
||||
SolanaBlockProductionConfig
|
||||
SolanaBlockCommitment
|
||||
SolanaBlockProductionResultRange
|
||||
SolanaBlockProduction
|
||||
SolanaBlockReward
|
||||
SolanaBlockTransaction
|
||||
SolanaConfirmedBlock
|
||||
SolanaPerformanceSample
|
||||
```
|
||||
|
||||
`SolanaTransactionDetails` couvre exactement les quatre valeurs wire modernes :
|
||||
|
||||
```text
|
||||
full
|
||||
signatures
|
||||
none
|
||||
accounts
|
||||
```
|
||||
|
||||
`SolanaGetBlockConfig` conserve indépendamment les cinq options du contrat moderne :
|
||||
|
||||
```text
|
||||
commitment
|
||||
encoding
|
||||
transactionDetails
|
||||
maxSupportedTransactionVersion
|
||||
rewards
|
||||
```
|
||||
|
||||
Aucune validation de commitment ni aucun choix de forme legacy n'est encore exécuté ici : ces responsabilités appartiennent au wrapper `getBlock` de `pre.006`.
|
||||
|
||||
`SolanaBlockProductionConfig` conserve `commitment`, une `identity` typée `Pubkey` et une range `firstSlot/lastSlot?`. La validation `lastSlot >= firstSlot` reste volontairement réservée au wrapper `getBlockProduction` de `pre.005`.
|
||||
|
||||
`SolanaBlockProduction` décode les clés dynamiques de `byIdentity` en `Pubkey` et conserve les couples `(leader slots, blocks produced)` ainsi que la range effective retournée par le runtime. Le futur wrapper appliquera le `SolanaRpcResponse<T>` partagé autour de ce résultat.
|
||||
|
||||
### Wire `getBlock`
|
||||
|
||||
`SolanaConfirmedBlock` est un DTO bloc dédié et ne réutilise pas `SolanaConfirmedTransaction`, dont `slot` et `blockTime` sont propres au top-level de `getTransaction`.
|
||||
|
||||
Le bloc conserve :
|
||||
|
||||
```text
|
||||
previousBlockhash
|
||||
blockhash
|
||||
parentSlot
|
||||
transactions
|
||||
signatures
|
||||
rewards
|
||||
numRewardPartitions
|
||||
blockTime
|
||||
blockHeight
|
||||
```
|
||||
|
||||
Les champs conditionnels `transactions`, `signatures`, `rewards` et `numRewardPartitions` utilisent `SolanaWireField<T>` afin de préserver les trois états :
|
||||
|
||||
```text
|
||||
Omitted
|
||||
Null
|
||||
Value(T)
|
||||
```
|
||||
|
||||
`numRewardPartitions` couvre explicitement le wire associé à SIMD-0118 sans synthétiser une valeur `0` en cas d'omission ou de `null`.
|
||||
|
||||
`SolanaBlockTransaction` compose les primitives Transaction déjà acquises :
|
||||
|
||||
```text
|
||||
SolanaEncodedTransaction
|
||||
SolanaWireField<serde_json::Value> pour meta
|
||||
SolanaWireField<SolanaTransactionVersion> pour version
|
||||
```
|
||||
|
||||
Le décodeur interne `SolanaTransactionVersion::decode_wire` passe donc de privé à `pub(crate)` pour être réutilisé sans dupliquer la logique `legacy | u8`. Son comportement public reste inchangé. Une fixture `getBlock` porte volontairement la version numérique `1` afin d'empêcher une future régression qui bornerait artificiellement cette primitive à V0, conformément au watchpoint SIMD-0385.
|
||||
|
||||
`SolanaBlockReward` conserve :
|
||||
|
||||
```text
|
||||
pubkey: Pubkey
|
||||
lamports: i64
|
||||
postBalance: u64
|
||||
rewardType: String ou null
|
||||
commission: u8 ou null
|
||||
commissionBps: SolanaWireField<u16>
|
||||
```
|
||||
|
||||
`commissionBps` couvre l'extension stable déjà exposée par le runtime pour SIMD-0291. `commission` et `commissionBps` restent indépendants ; Transport ne dérive jamais l'un depuis l'autre.
|
||||
|
||||
`rewardType` reste volontairement une chaîne nullable au niveau Transport. Cette tranche n'introduit pas une taxonomie économique KSP spéculative alors que les évolutions de reward semantics restent dans la watchlist, notamment SIMD-0123.
|
||||
|
||||
### Module Economics partagé
|
||||
|
||||
Le nouveau module privé `rpc_economics` ajoute et réexporte :
|
||||
|
||||
```text
|
||||
SolanaInflationGovernor
|
||||
SolanaInflationRate
|
||||
SolanaInflationRewardConfig
|
||||
SolanaInflationReward
|
||||
SolanaSupplyConfig
|
||||
SolanaSupply
|
||||
```
|
||||
|
||||
`SolanaInflationGovernor` et `SolanaInflationRate` conservent directement les valeurs runtime `f64`/epoch sans reproduire localement les formules d'inflation.
|
||||
|
||||
`SolanaInflationRewardConfig` sérialise uniquement les options fournies :
|
||||
|
||||
```text
|
||||
epoch
|
||||
commitment
|
||||
minContextSlot
|
||||
```
|
||||
|
||||
`SolanaInflationReward` conserve `commission: Option<u8>` et `commissionBps: SolanaWireField<u16>` pour couvrir SIMD-0291 sans perdre la différence omission/null/présent du champ basis-points. La cardinalité et les `null` positionnels de la liste `getInflationReward` seront pris en charge par le wrapper de `pre.008`; le DTO de cette tranche représente uniquement une entrée non-null.
|
||||
|
||||
`SolanaSupplyConfig` utilise `Option<bool>` pour `excludeNonCirculatingAccountsList`. Cela distingue un champ omis d'un `false` explicitement envoyé, tout en laissant le runtime appliquer son défaut lorsque l'option est absente.
|
||||
|
||||
`SolanaSupply` convertit la liste ordonnée `nonCirculatingAccounts` en `Vec<Pubkey>` et échoue proprement si une clé retournée n'est pas une public key valide. Le futur wrapper réutilisera `SolanaRpcResponse<SolanaSupply>`.
|
||||
|
||||
### Frontières conservées
|
||||
|
||||
Cette prerelease n'ajoute aucun `impl HttpTransportPool` pour les 15 méthodes `V0_2_4`. Les nouveaux types sont donc uniquement des contrats wire/config/result partagés.
|
||||
|
||||
Le flux d'exécution reste inchangé :
|
||||
|
||||
```text
|
||||
wrapper typed futur
|
||||
-> descriptor central
|
||||
-> execute_standard_rpc
|
||||
-> pool/admission
|
||||
-> executor HTTP
|
||||
-> validation JSON-RPC
|
||||
-> decode typed
|
||||
```
|
||||
|
||||
Aucun client HTTP parallèle, aucune boucle de retry locale et aucune dépendance Solana RPC haut niveau ne sont introduits.
|
||||
|
||||
## Fixtures déterministes ajoutées
|
||||
|
||||
```text
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/block_commitment.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/block_production.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.omissions.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/performance_sample.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_governor.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_rate.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_reward.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/supply.v4_2_1.json
|
||||
```
|
||||
|
||||
Elles couvrent notamment :
|
||||
|
||||
- block commitment présent et `null` ;
|
||||
- `getBlockProduction.byIdentity` et la range effective ;
|
||||
- bloc riche avec transaction version numérique `1` ;
|
||||
- `meta` explicite `null` et version omise ;
|
||||
- reward `commissionBps` présent puis omis ;
|
||||
- `numRewardPartitions` présent, omis et explicitement `null` ;
|
||||
- performance sample courant puis ancien sans `numNonVoteTransactions` ;
|
||||
- inflation governor/rate ;
|
||||
- liste inflation reward avec un `null` positionnel et les variantes `commissionBps` ;
|
||||
- supply avec liste ordonnée de public keys.
|
||||
|
||||
## Tests ajoutés
|
||||
|
||||
`unit_tests/rpc_blocks.rs` ajoute **8 tests déterministes** couvrant configs, résultats, omissions/null, version transaction non nulle, extensions SIMD et validation des public keys.
|
||||
|
||||
`unit_tests/rpc_economics.rs` ajoute **6 tests déterministes** couvrant configs, inflation values, reward basis-points, `null` positionnel de fixture, supply ordonnée et invalid pubkey.
|
||||
|
||||
Les tests d'intégration ajoutent aussi :
|
||||
|
||||
- une canarie de surface publique qui vérifie que les 17 nouveaux types sont disponibles depuis la racine de crate ;
|
||||
- une canarie de registre qui confirme que `V0_2_4` reste exactement composé des **10 Blocks + 5 Economics**, tous `Read / RetrySafe`, et que `getBlock` conserve son marqueur de forme legacy dépréciée sans prétendre que les wrappers existent déjà.
|
||||
|
||||
La canarie typed-complete `V0_2_4 == 15` reste volontairement fermée jusqu'aux tranches d'implémentation des wrappers.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
crates/ksp-onchain-transport-lib/src/rpc_blocks.rs
|
||||
crates/ksp-onchain-transport-lib/src/rpc_economics.rs
|
||||
crates/ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs
|
||||
crates/ksp-onchain-transport-lib/unit_tests/rpc_economics.rs
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/block_commitment.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/block_production.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/confirmed_block.omissions.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/performance_sample.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_governor.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_rate.v4_2_1.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/inflation_reward.variants.json
|
||||
crates/ksp-onchain-transport-lib/fixtures/http/supply.v4_2_1.json
|
||||
deltas/0.2.4/pre.002.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-onchain-transport-lib/src/lib.rs
|
||||
crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
||||
crates/ksp-onchain-transport-lib/tests/public_api.rs
|
||||
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## Fichiers volontairement inchangés
|
||||
|
||||
```text
|
||||
CHANGELOG.md
|
||||
ROADMAP.md
|
||||
docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.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/**
|
||||
deltas/0.2.4/pre.001.md
|
||||
deltas/0.2.4/pre.001-fix.001.md
|
||||
```
|
||||
|
||||
Aucun ancien delta n'est réécrit. Aucun wrapper Blocks/Economics n'est avancé prématurément.
|
||||
|
||||
## Validations exécutées dans le sandbox
|
||||
|
||||
- reconstruction exacte de la base `v0.2.3` puis application des overlays `0.2.4-pre.001` et `0.2.4-pre.001-fix.001` ;
|
||||
- contrôle différentiel de la tranche `pre.002` contre cette base ;
|
||||
- recoupement ciblé des configs/résultats avec la baseline Agave `v4.2.1` retenue par le plan ;
|
||||
- parse JSON local des neuf nouvelles fixtures ;
|
||||
- contrôle statique des 17 réexports crate-root ;
|
||||
- contrôle statique que chaque nouveau item `pub`/`pub(crate)` de production possède sa documentation ;
|
||||
- contrôle statique de l'absence de `impl HttpTransportPool` dans les nouveaux modules ;
|
||||
- contrôle statique de l'absence de `use`, `unwrap`, `expect`, `panic!` et opérateur `?` dans les deux nouvelles sources de production ;
|
||||
- contrôle statique des lignes Rust modifiées à `<= 160` colonnes ;
|
||||
- contrôle différentiel de `Cargo.toml` : seul `workspace.package.version` change ;
|
||||
- contrôle différentiel de `rpc_transactions.rs` : seule la version de fichier et la visibilité interne documentée de `SolanaTransactionVersion::decode_wire` changent ;
|
||||
- contrôle que `CHANGELOG.md`, `ROADMAP.md`, le plan `011` version 2 et les deltas historiques restent inchangés.
|
||||
|
||||
## Validations Cargo non exécutées
|
||||
|
||||
Le sandbox courant ne fournit pas `cargo`, `rustc` 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 graphes complets restent obligatoires à la clôture `pre.009`.
|
||||
|
||||
Le commit attendu après application et validations suit `VER-GIT-001` :
|
||||
|
||||
```text
|
||||
v0.2.4-pre.002
|
||||
```
|
||||
|
||||
## Décisions prises
|
||||
|
||||
- séparer physiquement les primitives Blocks et Economics dans deux modules Transport dédiés ;
|
||||
- ne pas avancer de wrapper avant sa tranche propriétaire ;
|
||||
- réutiliser `SolanaWireField<T>`, `SolanaEncodedTransaction` et `SolanaTransactionVersion` plutôt que dupliquer le wire Transaction ;
|
||||
- ouvrir uniquement la visibilité interne de `SolanaTransactionVersion::decode_wire` sans modifier son API publique ;
|
||||
- typer les identités/adresses RPC en `Pubkey` lorsqu'elles sont structurelles ;
|
||||
- garder les sous-arbres Transaction/meta riches lossless via `serde_json::Value` ;
|
||||
- garder `rewardType` ouvert au niveau Transport au lieu d'introduire une taxonomie économique prématurée ;
|
||||
- distinguer `excludeNonCirculatingAccountsList` omis d'un `false` explicite ;
|
||||
- intégrer dès les fixtures partagées SIMD-0118, SIMD-0291 et la canarie future-compatible SIMD-0385 ;
|
||||
- ne pas modifier le plan `011`, aucune capacité stable additive n'ayant été découverte pendant cette tranche.
|
||||
|
||||
## Questions ouvertes
|
||||
|
||||
Aucune question bloquante pour `pre.003`.
|
||||
|
||||
## Suite
|
||||
|
||||
`0.2.4-pre.003` : implémenter les cinq wrappers Blocks simples avec requêtes JSON exactes et réponses typed :
|
||||
|
||||
```text
|
||||
getBlockCommitment
|
||||
getBlockHeight
|
||||
getBlockTime
|
||||
getFirstAvailableBlock
|
||||
minimumLedgerSlot
|
||||
```
|
||||
Reference in New Issue
Block a user