RPC: Add API version to context response (#25134)

* RPC: Add API version to context response

* restore backwards compatibility
This commit is contained in:
Justin Starry 2022-05-12 12:17:21 +08:00 committed by GitHub
parent 9d18fe019b
commit a118af069e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 214 additions and 78 deletions

1
Cargo.lock generated
View File

@ -6103,6 +6103,7 @@ version = "1.11.0"
dependencies = [
"log",
"rustc_version",
"semver",
"serde",
"serde_derive",
"solana-frozen-abi 1.11.0",

View File

@ -163,7 +163,10 @@ mod tests {
fn test_check_account_for_fees() {
let account_balance = 1;
let account_balance_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(account_balance),
});
let pubkey = solana_sdk::pubkey::new_rand();
@ -183,7 +186,10 @@ mod tests {
check_account_for_fee(&rpc_client, &pubkey, &message0).expect("unexpected result");
let check_fee_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(2),
});
let mut mocks = HashMap::new();
@ -193,7 +199,10 @@ mod tests {
assert!(check_account_for_fee(&rpc_client, &pubkey, &message1).is_err());
let check_fee_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(2),
});
let mut mocks = HashMap::new();
@ -206,11 +215,17 @@ mod tests {
let account_balance = 2;
let account_balance_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(account_balance),
});
let check_fee_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(1),
});
@ -227,7 +242,10 @@ mod tests {
fn test_check_account_for_balance() {
let account_balance = 50;
let account_balance_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(account_balance),
});
let pubkey = solana_sdk::pubkey::new_rand();
@ -244,7 +262,10 @@ mod tests {
#[test]
fn test_get_fee_for_messages() {
let check_fee_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(1),
});
let mut mocks = HashMap::new();
@ -263,7 +284,10 @@ mod tests {
// No signatures, no fee.
let check_fee_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(0),
});
let mut mocks = HashMap::new();

View File

@ -1986,7 +1986,10 @@ mod tests {
assert!(result.is_ok());
let vote_account_info_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!({
"data": ["KLUv/QBYNQIAtAIBAAAAbnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/EFAAEAAB8ACQD6gx92zAiAAecDP4B2XeEBSIx7MQeung==", "base64+zstd"],
"lamports": 42,
@ -2272,7 +2275,10 @@ mod tests {
// Success case
let mut config = CliConfig::default();
let account_info_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: Value::Null,
});
let mut mocks = HashMap::new();

View File

@ -355,7 +355,10 @@ mod tests {
let rpc_blockhash = hash(&[1u8]);
let rpc_fee_calc = FeeCalculator::new(42);
let get_recent_blockhash_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(RpcFees {
blockhash: rpc_blockhash.to_string(),
fee_calculator: rpc_fee_calc.clone(),
@ -364,7 +367,10 @@ mod tests {
}),
});
let get_fee_calculator_for_blockhash_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(RpcFeeCalculator {
fee_calculator: rpc_fee_calc.clone()
}),
@ -428,7 +434,10 @@ mod tests {
None,
);
let get_account_response = json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(Some(rpc_nonce_account)),
});

View File

@ -108,15 +108,15 @@ impl RpcSender for MockSender {
let val = match method.as_str().unwrap() {
"getAccountInfo" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: Value::Null,
})?,
"getBalance" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: Value::Number(Number::from(50)),
})?,
"getRecentBlockhash" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: (
Value::String(PUBKEY.to_string()),
serde_json::to_value(FeeCalculator::default()).unwrap(),
@ -137,16 +137,16 @@ impl RpcSender for MockSender {
serde_json::to_value(Some(FeeCalculator::default())).unwrap()
};
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value,
})?
}
"getFeeRateGovernor" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
})?,
"getFees" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: serde_json::to_value(RpcFees {
blockhash: PUBKEY.to_string(),
fee_calculator: FeeCalculator::default(),
@ -185,7 +185,7 @@ impl RpcSender for MockSender {
.map(|_| status.clone())
.collect();
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: statuses,
})?
}
@ -248,7 +248,7 @@ impl RpcSender for MockSender {
"getBlockProduction" => {
if params.is_null() {
json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: RpcBlockProduction {
by_identity: HashMap::new(),
range: RpcBlockProductionRange {
@ -266,7 +266,7 @@ impl RpcSender for MockSender {
let config_range = config.range.unwrap_or_default();
json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: RpcBlockProduction {
by_identity,
range: RpcBlockProductionRange {
@ -289,7 +289,7 @@ impl RpcSender for MockSender {
inactive: 12,
}),
"getSupply" => json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: RpcSupply {
total: 100000000,
circulating: 50000,
@ -304,7 +304,7 @@ impl RpcSender for MockSender {
};
json!(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: vec![rpc_account_balance],
})
}
@ -335,7 +335,7 @@ impl RpcSender for MockSender {
Value::String(signature)
}
"simulateTransaction" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: RpcSimulateTransactionResult {
err: None,
logs: None,
@ -353,14 +353,14 @@ impl RpcSender for MockSender {
})
}
"getLatestBlockhash" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: RpcBlockhash {
blockhash: PUBKEY.to_string(),
last_valid_block_height: 1234,
},
})?,
"getFeeForMessage" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: json!(Some(0)),
})?,
"getClusterNodes" => serde_json::to_value(vec![RpcContactInfo {
@ -441,7 +441,7 @@ impl RpcSender for MockSender {
"minimumLedgerSlot" => json![123],
"getMaxRetransmitSlot" => json![123],
"getMultipleAccounts" => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext { slot: 1, api_version: None },
value: vec![Value::Null, Value::Null]
})?,
"getProgramAccounts" => {

View File

@ -419,7 +419,7 @@ impl RpcClient {
/// // Create a mock with a custom repsonse to the `GetBalance` request
/// let account_balance = 50;
/// let account_balance_response = json!(Response {
/// context: RpcResponseContext { slot: 1 },
/// context: RpcResponseContext { slot: 1, api_version: None },
/// value: json!(account_balance),
/// });
///
@ -5394,7 +5394,10 @@ pub fn create_rpc_client_mocks() -> crate::mock_sender::Mocks {
let get_account_request = RpcRequest::GetAccountInfo;
let get_account_response = serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None,
},
value: {
let pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap();
let account = Account {

View File

@ -448,7 +448,7 @@ impl RpcClient {
/// // Create a mock with a custom repsonse to the `GetBalance` request
/// let account_balance = 50;
/// let account_balance_response = json!(Response {
/// context: RpcResponseContext { slot: 1 },
/// context: RpcResponseContext { slot: 1, api_version: None },
/// value: json!(account_balance),
/// });
///
@ -4061,7 +4061,10 @@ pub fn create_rpc_client_mocks() -> crate::mock_sender::Mocks {
let get_account_request = RpcRequest::GetAccountInfo;
let get_account_response = serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
context: RpcResponseContext {
slot: 1,
api_version: None,
},
value: {
let pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap();
let account = Account {

View File

@ -1,5 +1,6 @@
use {
crate::client_error,
serde::{Deserialize, Deserializer, Serialize, Serializer},
solana_account_decoder::{parse_token::UiTokenAmount, UiAccount},
solana_sdk::{
clock::{Epoch, Slot, UnixTimestamp},
@ -12,15 +13,64 @@ use {
solana_transaction_status::{
ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock,
},
std::{collections::HashMap, fmt, net::SocketAddr},
std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr},
thiserror::Error,
};
pub type RpcResult<T> = client_error::Result<Response<T>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcResponseContext {
pub slot: u64,
pub slot: Slot,
#[serde(skip_serializing_if = "Option::is_none")]
pub api_version: Option<RpcApiVersion>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RpcApiVersion(semver::Version);
impl std::ops::Deref for RpcApiVersion {
type Target = semver::Version;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for RpcApiVersion {
fn default() -> Self {
Self(solana_version::Version::default().as_semver_version())
}
}
impl Serialize for RpcApiVersion {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for RpcApiVersion {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
Ok(RpcApiVersion(
semver::Version::from_str(&s).map_err(serde::de::Error::custom)?,
))
}
}
impl RpcResponseContext {
pub fn new(slot: Slot) -> Self {
Self {
slot,
api_version: Some(RpcApiVersion::default()),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -5379,6 +5379,7 @@ version = "1.11.0"
dependencies = [
"log",
"rustc_version",
"semver",
"serde",
"serde_derive",
"solana-frozen-abi 1.11.0",

View File

@ -116,8 +116,10 @@ pub const PERFORMANCE_SAMPLES_LIMIT: usize = 720;
const MAX_RPC_EPOCH_CREDITS_HISTORY: usize = 5;
fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
let context = RpcResponseContext { slot: bank.slot() };
RpcResponse { context, value }
RpcResponse {
context: RpcResponseContext::new(bank.slot()),
value,
}
}
/// Wrapper for rpc return types of methods that provide responses both with and without context.
@ -774,7 +776,7 @@ impl JsonRpcRequestProcessor {
if let Some((slot, accounts)) = self.get_cached_largest_accounts(&config.filter) {
Ok(RpcResponse {
context: RpcResponseContext { slot },
context: RpcResponseContext::new(slot),
value: accounts,
})
} else {
@ -4814,7 +4816,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":20,
},
"id": 1,
@ -5211,7 +5213,7 @@ pub mod tests {
);
let result: Value = parse_success_result(rpc.handle_request_sync(request));
let expected = json!({
"context": {"slot": 0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"owner": "11111111111111111111111111111111",
"lamports": 1_000_000,
@ -5396,7 +5398,7 @@ pub mod tests {
let result: RpcResponse<Vec<RpcKeyedAccount>> =
parse_success_result(rpc.handle_request_sync(request));
let expected = RpcResponse {
context: RpcResponseContext { slot: 0 },
context: RpcResponseContext::new(0),
value: expected_value,
};
assert_eq!(result, expected);
@ -5562,7 +5564,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"accounts": [
null,
@ -5658,7 +5660,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"accounts":null,
"err":null,
@ -5687,7 +5689,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"accounts":null,
"err":null,
@ -5740,7 +5742,7 @@ pub mod tests {
let expected = json!({
"jsonrpc":"2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"err":"BlockhashNotFound",
"accounts":null,
@ -5767,7 +5769,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"accounts":null,
"err":null,
@ -5906,13 +5908,14 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{
"blockhash": recent_blockhash.to_string(),
"feeCalculator": {
"lamportsPerSignature": 0,
}
}},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"blockhash": recent_blockhash.to_string(),
"feeCalculator": {
"lamportsPerSignature": 0,
}
},
},
"id": 1
});
let expected: Response =
@ -5934,7 +5937,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context": {"slot": 0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value": {
"blockhash": recent_blockhash.to_string(),
"feeCalculator": {
@ -5973,7 +5976,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":fee_calculator,
},
"id": 1
@ -5993,7 +5996,7 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":Value::Null,
},
"id": 1
@ -6014,16 +6017,17 @@ pub mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{
"feeRateGovernor": {
"burnPercent": DEFAULT_BURN_PERCENT,
"maxLamportsPerSignature": 0,
"minLamportsPerSignature": 0,
"targetLamportsPerSignature": 0,
"targetSignaturesPerSlot": 0
}
}},
"context": {"slot": 0, "apiVersion": RpcApiVersion::default()},
"value":{
"feeRateGovernor": {
"burnPercent": DEFAULT_BURN_PERCENT,
"maxLamportsPerSignature": 0,
"minLamportsPerSignature": 0,
"targetLamportsPerSignature": 0,
"targetSignaturesPerSlot": 0
}
},
},
"id": 1
});
let expected: Response =

View File

@ -1,4 +1,5 @@
//! The `pubsub` module implements a threaded subscription service on client RPC request
use {
crate::{
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
@ -16,7 +17,7 @@ use {
serde::Serialize,
solana_account_decoder::{parse_token::is_known_spl_token_id, UiAccount, UiAccountEncoding},
solana_client::rpc_response::{
ProcessedSignatureResult, ReceivedSignatureResult, Response, RpcBlockUpdate,
ProcessedSignatureResult, ReceivedSignatureResult, Response as RpcResponse, RpcBlockUpdate,
RpcBlockUpdateError, RpcKeyedAccount, RpcLogsResponse, RpcResponseContext,
RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
},
@ -153,10 +154,10 @@ where
filter_results(results, params, *w_last_notified_slot, bank);
for result in filter_results {
notifier.notify(
Response {
context: RpcResponseContext { slot },
RpcResponse::from(RpcNotificationResponse {
context: RpcNotificationContext { slot },
value: result,
},
}),
subscription,
is_final,
);
@ -176,6 +177,33 @@ pub struct RpcNotification {
pub created_at: Instant,
}
#[derive(Debug, Clone, PartialEq)]
struct RpcNotificationResponse<T> {
context: RpcNotificationContext,
value: T,
}
impl<T> From<RpcNotificationResponse<T>> for RpcResponse<T> {
fn from(notification: RpcNotificationResponse<T>) -> Self {
let RpcNotificationResponse {
context: RpcNotificationContext { slot },
value,
} = notification;
Self {
context: RpcResponseContext {
slot,
api_version: None,
},
value,
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct RpcNotificationContext {
slot: Slot,
}
const RPC_NOTIFICATIONS_METRICS_SUBMISSION_INTERVAL_MS: Duration = Duration::from_millis(2_000);
struct RecentItems {
@ -839,12 +867,12 @@ impl RpcSubscriptions {
{
if params.enable_received_notification {
notifier.notify(
Response {
context: RpcResponseContext { slot },
RpcResponse::from(RpcNotificationResponse {
context: RpcNotificationContext { slot },
value: RpcSignatureResult::ReceivedSignature(
ReceivedSignatureResult::ReceivedSignature,
),
},
}),
subscription,
false,
);
@ -987,10 +1015,10 @@ impl RpcSubscriptions {
Ok(block_update) => {
if let Some(block_update) = block_update {
notifier.notify(
Response {
context: RpcResponseContext { slot: s },
RpcResponse::from(RpcNotificationResponse {
context: RpcNotificationContext { slot: s },
value: block_update,
},
}),
subscription,
false,
);
@ -1004,14 +1032,14 @@ impl RpcSubscriptions {
// we don't advance `w_last_unnotified_slot` so that
// it'll retry on the next notification trigger
notifier.notify(
Response {
context: RpcResponseContext { slot: s },
RpcResponse::from(RpcNotificationResponse {
context: RpcNotificationContext { slot: s },
value: RpcBlockUpdate {
slot,
block: None,
err: Some(err),
},
},
}),
subscription,
false,
);

View File

@ -11,6 +11,7 @@ edition = "2021"
[dependencies]
log = "0.4.17"
semver = "1.0.9"
serde = "1.0.137"
serde_derive = "1.0.103"
solana-frozen-abi = { path = "../frozen-abi", version = "=1.11.0" }

View File

@ -29,6 +29,12 @@ pub struct Version {
pub feature_set: u32, // first 4 bytes of the FeatureSet identifier
}
impl Version {
pub fn as_semver_version(&self) -> semver::Version {
semver::Version::new(self.major as u64, self.minor as u64, self.patch as u64)
}
}
impl From<LegacyVersion> for Version {
fn from(legacy_version: LegacyVersion) -> Self {
Self {