Skip to content
3 changes: 2 additions & 1 deletion canister/src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ impl GetRecentPrioritizationFeesRequest {
config: GetRecentPrioritizationFeesRpcConfig,
params: Params,
) -> Result<Self, ProviderError> {
let max_length = config.max_length();
let consensus_strategy = config.response_consensus.unwrap_or_default();
let providers = Providers::new(rpc_sources, consensus_strategy.clone())?;
let max_response_bytes = config
Expand All @@ -272,8 +273,8 @@ impl GetRecentPrioritizationFeesRequest {
JsonRpcRequest::new("getRecentPrioritizationFees", params.into()),
max_response_bytes,
ResponseTransform::GetRecentPrioritizationFees {
max_length: max_length.into(),
max_slot_rounding_error: config.max_slot_rounding_error.unwrap_or_default(),
max_length: config.max_length.unwrap_or(100),
},
ReductionStrategy::from(consensus_strategy),
))
Expand Down
12 changes: 7 additions & 5 deletions canister/src/rpc_client/sol_rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ use sol_rpc_types::{PrioritizationFee, RoundingError};
use solana_clock::Slot;
use solana_transaction_status_client_types::TransactionStatus;
use std::fmt::Debug;
use strum::EnumIter;
use std::num::NonZeroU8;

/// Describes a payload transformation to execute before passing the HTTP response to consensus.
/// The purpose of these transformations is to ensure that the response encoding is deterministic
/// (the field order is the same).
#[derive(Clone, Debug, Decode, Encode, EnumIter)]
#[derive(Clone, Debug, Decode, Encode)]
#[cfg_attr(test, derive(strum::EnumDiscriminants))]
#[cfg_attr(test, strum_discriminants(derive(strum::EnumIter)))]
pub enum ResponseTransform {
#[n(0)]
GetAccountInfo,
Expand All @@ -32,7 +34,7 @@ pub enum ResponseTransform {
#[cbor(n(0), with = "crate::rpc_client::cbor::rounding_error")]
max_slot_rounding_error: RoundingError,
#[n(1)]
max_length: u8,
max_length: NonZeroU8,
},
#[n(4)]
GetSignaturesForAddress,
Expand Down Expand Up @@ -110,7 +112,7 @@ impl ResponseTransform {
// "Currently, a node's prioritization-fee cache stores data from up to 150 blocks."
// Manual testing shows that the result seems to always contain 150 elements on mainnet (also for not used addresses)
// but not necessarily when using a local validator.
if fees.is_empty() || max_length == &0 {
if fees.is_empty() {
return Vec::default();
}
// The order of the prioritization fees in the response is not specified in the
Expand All @@ -128,7 +130,7 @@ impl ResponseTransform {

fees.into_iter()
.skip_while(|fee| fee.slot > max_rounded_slot)
.take(*max_length as usize)
.take(max_length.get() as usize)
.collect::<Vec<_>>()
.into_iter()
.rev()
Expand Down
58 changes: 41 additions & 17 deletions canister/src/rpc_client/sol_rpc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use strum::IntoEnumIterator;

mod normalization_tests {
use super::*;
use crate::rpc_client::sol_rpc::ResponseTransformDiscriminants;
use std::num::NonZeroU8;

#[test]
fn should_normalize_raw_response() {
Expand Down Expand Up @@ -375,7 +377,7 @@ mod normalization_tests {
bytes
}

for transform in ResponseTransform::iter() {
for transform in all_response_transforms() {
let left = r#"{ "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid param: could not find account" }, "id": 1 }"#;
let right = r#"{ "error": { "message": "Invalid param: could not find account", "code": -32602 }, "id": 1, "jsonrpc": "2.0" }"#;
let normalized_left = normalize_json(&transform, left);
Expand Down Expand Up @@ -434,6 +436,35 @@ mod normalization_tests {
normalize_result(transform, right)
);
}

fn all_response_transforms() -> impl Iterator<Item = ResponseTransform> {
ResponseTransformDiscriminants::iter().map(|variant| match variant {
ResponseTransformDiscriminants::GetAccountInfo => ResponseTransform::GetAccountInfo,
ResponseTransformDiscriminants::GetBalance => ResponseTransform::GetBalance,
ResponseTransformDiscriminants::GetBlock => ResponseTransform::GetBlock,
ResponseTransformDiscriminants::GetRecentPrioritizationFees => {
ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::default(),
max_length: NonZeroU8::new(100).unwrap(),
}
}
ResponseTransformDiscriminants::GetSignatureStatuses => {
ResponseTransform::GetSignatureStatuses
}
ResponseTransformDiscriminants::GetSignaturesForAddress => {
ResponseTransform::GetSignaturesForAddress
}
ResponseTransformDiscriminants::GetSlot => {
ResponseTransform::GetSlot(RoundingError::default())
}
ResponseTransformDiscriminants::GetTokenAccountBalance => {
ResponseTransform::GetTokenAccountBalance
}
ResponseTransformDiscriminants::GetTransaction => ResponseTransform::GetTransaction,
ResponseTransformDiscriminants::SendTransaction => ResponseTransform::SendTransaction,
ResponseTransformDiscriminants::Raw => ResponseTransform::Raw,
})
}
}

mod get_recent_prioritization_fees {
Expand All @@ -458,28 +489,21 @@ mod get_recent_prioritization_fees {
(
ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(2),
max_length: 2,
max_length: 2.try_into().unwrap(),
},
prioritization_fees(vec![3, 4]),
),
(
ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(2),
max_length: 0,
},
prioritization_fees(vec![]),
),
(
ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(2),
max_length: u8::MAX,
max_length: u8::MAX.try_into().unwrap(),
},
prioritization_fees(vec![1, 2, 3, 4]),
),
(
ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(10),
max_length: 2,
max_length: 2.try_into().unwrap(),
},
prioritization_fees(vec![]),
),
Expand All @@ -498,7 +522,7 @@ mod get_recent_prioritization_fees {
let raw_response = json_response::<PrioritizationFee>(&[]);
let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(2),
max_length: 2,
max_length: 2.try_into().unwrap(),
};
let original_bytes = serde_json::to_vec(&raw_response).unwrap();
let mut transformed_bytes = original_bytes.clone();
Expand Down Expand Up @@ -532,7 +556,7 @@ mod get_recent_prioritization_fees {

let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(10),
max_length: 100,
max_length: 100.try_into().unwrap(),
};
let mut raw_bytes = serde_json::to_vec(&json_response(&fees)).unwrap();
transform.apply(&mut raw_bytes);
Expand Down Expand Up @@ -560,7 +584,7 @@ mod get_recent_prioritization_fees {

let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(10),
max_length: 100,
max_length: 100.try_into().unwrap(),
};
let mut raw_bytes = to_vec(&json_response(&fees)).unwrap();
transform.apply(&mut raw_bytes);
Expand All @@ -574,7 +598,7 @@ mod get_recent_prioritization_fees {
fn should_be_nop_when_failed_to_deserialize(original_bytes in prop::collection::vec(any::<u8>(), 0..1000)) {
let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(2),
max_length: 2,
max_length: 2.try_into().unwrap(),
};
let mut transformed_bytes = original_bytes.clone();
transform.apply(&mut transformed_bytes);
Expand All @@ -587,7 +611,7 @@ mod get_recent_prioritization_fees {
let raw_response = json_response(&fees);
let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(20),
max_length: 100,
max_length: 100.try_into().unwrap(),
};
let mut raw_bytes = serde_json::to_vec(&raw_response).unwrap();
transform.apply(&mut raw_bytes);
Expand Down Expand Up @@ -619,7 +643,7 @@ mod get_recent_prioritization_fees {
};
let transform = ResponseTransform::GetRecentPrioritizationFees {
max_slot_rounding_error: RoundingError::new(20),
max_length: 100,
max_length: 100.try_into().unwrap(),
};

let sorted_fees_bytes = {
Expand Down
3 changes: 2 additions & 1 deletion integration_tests/tests/solana_test_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use solana_signature::Signature;
use solana_signer::Signer;
use solana_transaction::Transaction;
use solana_transaction_status_client_types::UiTransactionEncoding;
use std::num::NonZeroU8;
use std::{
future::Future,
iter::zip,
Expand Down Expand Up @@ -124,7 +125,7 @@ async fn should_get_recent_prioritization_fees() {
|ic| async move {
ic.get_recent_prioritization_fees(&[account])
.unwrap()
.with_max_length(150)
.with_max_length(NonZeroU8::new(150).unwrap())
.with_max_slot_rounding_error(1)
.send()
.await
Expand Down
3 changes: 2 additions & 1 deletion integration_tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ mod get_recent_prioritization_fees_tests {
use serde_json::json;
use sol_rpc_int_tests::{mock::MockOutcallBuilder, Setup, SolRpcTestClient};
use sol_rpc_types::PrioritizationFee;
use std::num::NonZeroU8;

#[tokio::test]
async fn should_get_fees_with_rounding() {
Expand Down Expand Up @@ -1107,7 +1108,7 @@ mod get_recent_prioritization_fees_tests {
.get_recent_prioritization_fees(&[USDC_PUBLIC_KEY])
.unwrap()
.with_max_slot_rounding_error(10)
.with_max_length(5)
.with_max_length(NonZeroU8::new(5).unwrap())
.send()
.await
.expect_consistent();
Expand Down
7 changes: 4 additions & 3 deletions libs/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,10 +463,11 @@ impl<R> SolRpcClient<R> {
/// use sol_rpc_client::SolRpcClient;
/// use sol_rpc_types::{RpcSources, SolanaCluster};
/// use solana_pubkey::pubkey;
///
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use sol_rpc_types::{MultiRpcResult, PrioritizationFee, TokenAmount};
/// use std::num::NonZeroU8;
/// use sol_rpc_types::{MultiRpcResult, PrioritizationFee, TokenAmount};
/// let client = SolRpcClient::builder_for_ic()
/// # .with_mocked_response(MultiRpcResult::Consistent(Ok(vec![PrioritizationFee{slot: 338637772, prioritization_fee: 166667}])))
/// .with_rpc_sources(RpcSources::Default(SolanaCluster::Mainnet))
Expand All @@ -475,7 +476,7 @@ impl<R> SolRpcClient<R> {
/// let fees = client
/// .get_recent_prioritization_fees(&[pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")])
/// .unwrap()
/// .with_max_length(1)
/// .with_max_length(NonZeroU8::MIN)
/// .send()
/// .await
/// .expect_consistent();
Expand Down
8 changes: 4 additions & 4 deletions libs/client/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use sol_rpc_types::{
GetRecentPrioritizationFeesParams, GetRecentPrioritizationFeesRpcConfig,
GetSignatureStatusesParams, GetSignaturesForAddressLimit, GetSignaturesForAddressParams,
GetSlotParams, GetSlotRpcConfig, GetTokenAccountBalanceParams, GetTransactionParams, Lamport,
PrioritizationFee, RoundingError, RpcConfig, RpcResult, RpcSources, SendTransactionParams,
Signature, Slot, TokenAmount, TransactionInfo, TransactionStatus,
NonZeroU8, PrioritizationFee, RoundingError, RpcConfig, RpcResult, RpcSources,
SendTransactionParams, Signature, Slot, TokenAmount, TransactionInfo, TransactionStatus,
};
use solana_account_decoder_client_types::token::UiTokenAmount;
use solana_transaction_status_client_types::EncodedConfirmedTransactionWithStatusMeta;
Expand Down Expand Up @@ -585,9 +585,9 @@ impl<Runtime, Params, CandidOutput, Output>
}

/// Change the maximum number of entries for a `getRecentPrioritizationFees` response.
pub fn with_max_length(mut self, len: u8) -> Self {
pub fn with_max_length<T: Into<NonZeroU8>>(mut self, len: T) -> Self {
let config = self.request.rpc_config_mut().get_or_insert_default();
config.max_length = Some(len);
config.set_max_length(len.into());
self
}
}
Expand Down
2 changes: 1 addition & 1 deletion libs/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub use lifecycle::{InstallArgs, Mode, NumSubnetNodes};
pub use response::MultiRpcResult;
pub use rpc_client::{
ConsensusStrategy, GetRecentPrioritizationFeesRpcConfig, GetSlotRpcConfig, HttpHeader,
HttpOutcallError, JsonRpcError, OverrideProvider, ProviderError, RegexString,
HttpOutcallError, JsonRpcError, NonZeroU8, OverrideProvider, ProviderError, RegexString,
RegexSubstitution, RoundingError, RpcAccess, RpcAuth, RpcConfig, RpcEndpoint, RpcError,
RpcResult, RpcSource, RpcSources, SolanaCluster, SupportedRpcProvider, SupportedRpcProviderId,
};
Expand Down
Loading