Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getAccountInfo #3

Merged
merged 9 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ solana-program-runtime = "2.1.10"
spl-token-2022 = "4.0.0"
spl-token = "7.0.0"
solana-streamer = "2.1.10"
zstd = "0.13.2"
8 changes: 2 additions & 6 deletions crates/core/src/rpc/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::utils::decode_and_deserialize;
use jsonrpc_core::BoxFuture;
use jsonrpc_core::{Error, Result};
use jsonrpc_derive::rpc;
use solana_client::rpc_config::RpcContextConfig;
use solana_client::rpc_custom_error::RpcCustomError;
use solana_client::rpc_response::RpcApiVersion;
use solana_client::rpc_response::RpcResponseContext;
Expand All @@ -19,17 +20,12 @@ use solana_client::{
use solana_rpc_client_api::response::Response as RpcResponse;
use solana_sdk::clock::UnixTimestamp;
use solana_sdk::transaction::VersionedTransaction;
use solana_send_transaction_service::send_transaction_service::TransactionInfo;
use solana_transaction_status::UiTransactionEncoding;
use solana_transaction_status::{
EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock,
};

use {
super::*,
solana_sdk::message::{SanitizedVersionedMessage, VersionedMessage},
solana_transaction_status::parse_ui_inner_instructions,
};
use super::*;

#[rpc]
pub trait Full {
Expand Down
152 changes: 106 additions & 46 deletions crates/core/src/rpc/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::rpc::utils::verify_pubkey;
use std::io::Write;

use super::{RpcContextConfig, RunloopContext};
use crate::rpc::{utils::verify_pubkey, State};

use super::RunloopContext;
use base64::prelude::*;
use jsonrpc_core::Result;
use jsonrpc_derive::rpc;
use solana_account_decoder::{UiAccount, UiAccountData, UiAccountEncoding};
use solana_client::{
rpc_config::{
RpcGetVoteAccountsConfig, RpcLeaderScheduleConfig, RpcLeaderScheduleConfigWrapper,
RpcAccountInfoConfig, RpcContextConfig, RpcGetVoteAccountsConfig, RpcLeaderScheduleConfig,
RpcLeaderScheduleConfigWrapper,
},
rpc_custom_error::RpcCustomError,
rpc_response::{
RpcIdentity, RpcLeaderSchedule, RpcSnapshotSlotInfo, RpcVersionInfo, RpcVoteAccountStatus,
},
Expand All @@ -23,6 +27,14 @@ use solana_sdk::{
pub trait Minimal {
type Metadata;

#[rpc(meta, name = "getAccountInfo")]
fn get_account_info(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of introducing this endpoint in Minimal, I'd be in favor of introducing the AccountsData trait, responsible for grouping a few other methods (that we can left unimplemented for now).
These traits are coming from the canonical implementation, sticking to these architecture could help with maintenance.

AccountsData introduced here: https://github.com/txtx/surfpool/pull/6/files

&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcAccountInfoConfig>,
) -> Result<Option<UiAccount>>;

#[rpc(meta, name = "getBalance")]
fn get_balance(
&self,
Expand Down Expand Up @@ -94,6 +106,92 @@ pub struct SurfpoolMinimalRpc;
impl Minimal for SurfpoolMinimalRpc {
type Metadata = Option<RunloopContext>;

fn get_account_info(
&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcAccountInfoConfig>,
) -> Result<Option<UiAccount>> {
println!(
"get_account_info rpc request received: {:?} {:?}",
pubkey_str, config
);
let pubkey = verify_pubkey(&pubkey_str)?;
let config: RpcAccountInfoConfig = {
if let Some(config) = config {
config
} else {
RpcAccountInfoConfig::default()
}
};

let state_reader = meta.get_state()?;

if let Some(account) = state_reader.svm.get_account(&pubkey) {
Ok(Some(UiAccount {
lamports: account.lamports,
owner: account.owner.to_string(),
data: {
let account_data = if let Some(data_slice) = config.data_slice {
let end = std::cmp::min(
account.data.len(),
data_slice.offset + data_slice.length,
);
account.data.clone()[data_slice.offset..end].to_vec()
} else {
account.data.clone()
};

match config.encoding {
Some(UiAccountEncoding::Base58) => UiAccountData::Binary(
bs58::encode(account_data).into_string(),
UiAccountEncoding::Base58,
),
Some(UiAccountEncoding::Base64) => UiAccountData::Binary(
BASE64_STANDARD.encode(account_data),
UiAccountEncoding::Base64,
),
Some(UiAccountEncoding::Base64Zstd) => {
let mut data = Vec::with_capacity(account_data.len());

// Default compression level
match zstd::Encoder::new(&mut data, 0).and_then(|mut encoder| {
encoder
.write_all(&account_data)
.and_then(|_| encoder.finish())
}) {
Ok(_) => UiAccountData::Binary(
BASE64_STANDARD.encode(&data),
UiAccountEncoding::Base64Zstd,
),
// Falling back on standard base64 encoding if compression failed
Err(_) => {
eprintln!("Zstd compression failed: {e}");
UiAccountData::Binary(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix undefined error variable in error message.

The error message references an undefined variable e.

Apply this diff to fix the error:

-eprintln!("Zstd compression failed: {e}");
+eprintln!("Zstd compression failed");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
eprintln!("Zstd compression failed: {e}");
eprintln!("Zstd compression failed");

BASE64_STANDARD.encode(&account_data),
UiAccountEncoding::Base64,
)
}
}
}
None => UiAccountData::Binary(
bs58::encode(account_data.clone()).into_string(),
UiAccountEncoding::Base58,
),
encoding => Err(jsonrpc_core::Error::invalid_params(format!(
"Encoding {encoding:?} is not supported yet."
)))?,
}
},
executable: account.executable,
rent_epoch: account.rent_epoch,
space: Some(account.data.len() as u64),
}))
} else {
Ok(None)
}
}

fn get_balance(
&self,
meta: Self::Metadata,
Expand All @@ -111,20 +209,7 @@ impl Minimal for SurfpoolMinimalRpc {
meta: Self::Metadata,
config: Option<RpcContextConfig>,
) -> Result<EpochInfo> {
// Retrieve svm state
let Some(ctx) = meta else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
// Lock read access
let Ok(state_reader) = ctx.state.try_read() else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
let state_reader = meta.get_state()?;

Ok(state_reader.epoch_info.clone())
}
Expand All @@ -136,20 +221,8 @@ impl Minimal for SurfpoolMinimalRpc {
}

fn get_health(&self, meta: Self::Metadata) -> Result<String> {
// Retrieve svm state
let Some(ctx) = meta else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
// Lock read access
let Ok(_state_reader) = ctx.state.try_read() else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
let _state_reader = meta.get_state()?;

// todo: we could check the time from the state clock and compare
Ok("ok".to_string())
}
Expand All @@ -163,20 +236,7 @@ impl Minimal for SurfpoolMinimalRpc {
}

fn get_slot(&self, meta: Self::Metadata, _config: Option<RpcContextConfig>) -> Result<Slot> {
// Retrieve svm state
let Some(ctx) = meta else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
// Lock read access
let Ok(state_reader) = ctx.state.try_read() else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};
let state_reader = meta.get_state()?;
let clock: Clock = state_reader.svm.get_sysvar();
Ok(clock.slot.into())
}
Expand Down
50 changes: 29 additions & 21 deletions crates/core/src/rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
use std::{
sync::{Arc, RwLock},
sync::{Arc, RwLock, RwLockReadGuard},
time::Instant,
};

use jsonrpc_core::{
futures::future::Either, middleware, FutureResponse, Metadata, Middleware, Request, Response,
};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
clock::Slot,
commitment_config::CommitmentLevel,
transaction::{Transaction, VersionedTransaction},
};
use solana_client::rpc_custom_error::RpcCustomError;
use solana_sdk::{clock::Slot, transaction::VersionedTransaction};
use tokio::sync::broadcast;

pub mod full;
pub mod minimal;
pub mod utils;

#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommitmentConfig {
pub commitment: CommitmentLevel,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcContextConfig {
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
pub min_context_slot: Option<Slot>,
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RpcHealthStatus {
Ok,
Expand All @@ -47,6 +29,32 @@ pub struct RunloopContext {
pub mempool_tx: broadcast::Sender<VersionedTransaction>,
}

trait State {
fn get_state<'a>(&'a self) -> Result<RwLockReadGuard<'a, GlobalState>, RpcCustomError>;
}

impl State for Option<RunloopContext> {
fn get_state<'a>(&'a self) -> Result<RwLockReadGuard<'a, GlobalState>, RpcCustomError> {
// Retrieve svm state
let Some(ctx) = self else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};

// Lock read access
let Ok(state_reader) = ctx.state.try_read() else {
return Err(RpcCustomError::NodeUnhealthy {
num_slots_behind: None,
}
.into());
};

Ok(state_reader)
}
}

impl Metadata for RunloopContext {}

use crate::simnet::GlobalState;
Expand Down
13 changes: 3 additions & 10 deletions crates/core/src/rpc/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{any::type_name, collections::HashSet, sync::Arc};
use std::{any::type_name, sync::Arc};

use base64::{prelude::BASE64_STANDARD, Engine};
use bincode::Options;
Expand All @@ -9,17 +9,10 @@ use solana_client::{
rpc_filter::RpcFilterType,
rpc_request::{TokenAccountsFilter, MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT},
};
use solana_metrics::inc_new_counter;
use solana_metrics::inc_new_counter_info;
use solana_runtime::verify_precompiles::verify_precompiles;
use solana_runtime_transaction::runtime_transaction::RuntimeTransaction;
use solana_sdk::{
hash::Hash,
message::AddressLoader,
packet::PACKET_DATA_SIZE,
pubkey::Pubkey,
signature::Signature,
transaction::{MessageHash, SanitizedTransaction, VersionedTransaction},
hash::Hash, packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Signature,
transaction::SanitizedTransaction,
};
use solana_transaction_status::TransactionBinaryEncoding;

Expand Down
Loading