|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
// file: crates/ksp-onchain-transport-lib/src/rpc_accounts.rs
|
|
|
|
|
// version: 2
|
|
|
|
|
// version: 3
|
|
|
|
|
|
|
|
|
|
/// Account-data encoding accepted by Solana HTTP account methods.
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
@@ -29,7 +29,6 @@ impl SolanaAccountEncoding {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn from_wire(value: &str) -> std::option::Option<Self> {
|
|
|
|
|
return match value {
|
|
|
|
|
"binary" => std::option::Option::Some(Self::Binary),
|
|
|
|
|
@@ -68,7 +67,6 @@ impl SolanaDataSliceConfig {
|
|
|
|
|
return self.length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn to_json_value(self) -> serde_json::Value {
|
|
|
|
|
return serde_json::json!({"offset": self.offset, "length": self.length});
|
|
|
|
|
}
|
|
|
|
|
@@ -118,9 +116,12 @@ impl SolanaAccountInfoConfig {
|
|
|
|
|
return self.context.min_context_slot();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
|
return self.encoding.is_none() && self.data_slice.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
|
|
|
#[must_use]
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
|
|
|
let context_value = self.context.to_json_value();
|
|
|
|
|
let mut object = match context_value {
|
|
|
|
|
@@ -147,7 +148,6 @@ pub enum SolanaLargestAccountsFilter {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SolanaLargestAccountsFilter {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn as_str(self) -> &'static str {
|
|
|
|
|
return match self {
|
|
|
|
|
Self::Circulating => "circulating",
|
|
|
|
|
@@ -193,9 +193,12 @@ impl SolanaLargestAccountsConfig {
|
|
|
|
|
return self.sort_results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
|
return self.commitment.is_none() && self.filter.is_none() && self.sort_results.is_none();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
|
|
|
#[must_use]
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
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 {
|
|
|
|
|
@@ -248,7 +251,6 @@ impl SolanaMemcmpFilter {
|
|
|
|
|
return &self.bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn to_json_value(&self) -> serde_json::Value {
|
|
|
|
|
return match &self.bytes {
|
|
|
|
|
crate::SolanaMemcmpBytes::Base58(bytes) => serde_json::json!({"offset": self.offset, "bytes": bytes, "encoding": "base58"}),
|
|
|
|
|
@@ -270,7 +272,6 @@ pub enum SolanaProgramAccountFilter {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SolanaProgramAccountFilter {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn to_json_value(&self) -> serde_json::Value {
|
|
|
|
|
return match self {
|
|
|
|
|
Self::DataSize(size) => serde_json::json!({"dataSize": size}),
|
|
|
|
|
@@ -325,9 +326,12 @@ impl SolanaProgramAccountsConfig {
|
|
|
|
|
return self.sort_results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
|
return self.account_config.is_empty() && self.filters.is_empty() && self.with_context.is_none() && self.sort_results.is_none();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
|
|
|
#[must_use]
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
|
|
|
let account_value = self.account_config.to_json_value();
|
|
|
|
|
let mut object = match account_value {
|
|
|
|
|
@@ -393,7 +397,6 @@ pub enum SolanaAccountData {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SolanaAccountData {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireAccountData>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
@@ -477,7 +480,6 @@ impl SolanaAccount {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decodes one account DTO from the Solana JSON wire shape.
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireAccount>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
@@ -526,7 +528,6 @@ impl SolanaKeyedAccount {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decodes one keyed account from the Solana JSON wire shape.
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireKeyedAccount>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
@@ -568,7 +569,6 @@ impl SolanaAccountBalance {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decodes one account-balance entry from the Solana JSON wire shape.
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireAccountBalance>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
@@ -592,7 +592,361 @@ pub enum SolanaProgramAccountsResult {
|
|
|
|
|
Context(crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
const MAX_MULTIPLE_ACCOUNTS: usize = 100;
|
|
|
|
|
const MAX_PROGRAM_ACCOUNT_FILTERS: usize = 4;
|
|
|
|
|
const MAX_MEMCMP_BYTES: usize = 128;
|
|
|
|
|
|
|
|
|
|
impl crate::HttpTransportPool {
|
|
|
|
|
/// Executes typed `getAccountInfo` through the common KSP HTTP transport path.
|
|
|
|
|
pub async fn get_account_info(
|
|
|
|
|
&self,
|
|
|
|
|
role: &crate::HttpRoleName,
|
|
|
|
|
account: &ksp_core_lib::Pubkey,
|
|
|
|
|
config: std::option::Option<&crate::SolanaAccountInfoConfig>,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::option::Option<crate::SolanaAccount>>> {
|
|
|
|
|
let method_result = account_descriptor("getAccountInfo");
|
|
|
|
|
let method = match method_result {
|
|
|
|
|
std::result::Result::Ok(method) => method,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let mut params = std::vec![serde_json::Value::String(account.to_string())];
|
|
|
|
|
if let std::option::Option::Some(config) = config
|
|
|
|
|
&& !config.is_empty()
|
|
|
|
|
{
|
|
|
|
|
params.push(config.to_json_value());
|
|
|
|
|
}
|
|
|
|
|
let result = self.execute_standard_rpc(role, method, params).await;
|
|
|
|
|
let value = match result {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return decode_account_info_response("getAccountInfo", value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Executes typed `getLargestAccounts` through the common KSP HTTP transport path.
|
|
|
|
|
pub async fn get_largest_accounts(
|
|
|
|
|
&self,
|
|
|
|
|
role: &crate::HttpRoleName,
|
|
|
|
|
config: std::option::Option<&crate::SolanaLargestAccountsConfig>,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaAccountBalance>>> {
|
|
|
|
|
let method_result = account_descriptor("getLargestAccounts");
|
|
|
|
|
let method = match method_result {
|
|
|
|
|
std::result::Result::Ok(method) => method,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let mut params = std::vec::Vec::new();
|
|
|
|
|
if let std::option::Option::Some(config) = config
|
|
|
|
|
&& !config.is_empty()
|
|
|
|
|
{
|
|
|
|
|
params.push(config.to_json_value());
|
|
|
|
|
}
|
|
|
|
|
let result = self.execute_standard_rpc(role, method, params).await;
|
|
|
|
|
let value = match result {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return decode_largest_accounts_response("getLargestAccounts", value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Executes typed `getMinimumBalanceForRentExemption` through the common KSP HTTP transport path.
|
|
|
|
|
pub async fn get_minimum_balance_for_rent_exemption(
|
|
|
|
|
&self,
|
|
|
|
|
role: &crate::HttpRoleName,
|
|
|
|
|
data_len: usize,
|
|
|
|
|
config: std::option::Option<&crate::SolanaCommitmentConfig>,
|
|
|
|
|
) -> ksp_core_lib::Result<u64> {
|
|
|
|
|
let method_result = account_descriptor("getMinimumBalanceForRentExemption");
|
|
|
|
|
let method = match method_result {
|
|
|
|
|
std::result::Result::Ok(method) => method,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let data_len_value = serde_json::to_value(data_len);
|
|
|
|
|
let data_len_value = match data_len_value {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => {
|
|
|
|
|
return std::result::Result::Err(
|
|
|
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_ENCODE_FAILED, "rent-exemption data length could not be encoded")
|
|
|
|
|
.with_context("rpc_method", "getMinimumBalanceForRentExemption")
|
|
|
|
|
.with_source(error),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
let mut params = std::vec![data_len_value];
|
|
|
|
|
if let std::option::Option::Some(config) = config
|
|
|
|
|
&& config.commitment().is_some()
|
|
|
|
|
{
|
|
|
|
|
params.push(config.to_json_value());
|
|
|
|
|
}
|
|
|
|
|
let result = self.execute_standard_rpc(role, method, params).await;
|
|
|
|
|
let value = match result {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let decoded = crate::decode_wire_json::<u64>("getMinimumBalanceForRentExemption", value);
|
|
|
|
|
return match decoded {
|
|
|
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
|
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Executes typed `getMultipleAccounts` through the common KSP HTTP transport path.
|
|
|
|
|
pub async fn get_multiple_accounts(
|
|
|
|
|
&self,
|
|
|
|
|
role: &crate::HttpRoleName,
|
|
|
|
|
accounts: &[ksp_core_lib::Pubkey],
|
|
|
|
|
config: std::option::Option<&crate::SolanaAccountInfoConfig>,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<std::option::Option<crate::SolanaAccount>>>> {
|
|
|
|
|
if accounts.len() > MAX_MULTIPLE_ACCOUNTS {
|
|
|
|
|
return invalid_account_parameters("getMultipleAccounts", "getMultipleAccounts accepts at most 100 public keys", "account_count", accounts.len());
|
|
|
|
|
}
|
|
|
|
|
let method_result = account_descriptor("getMultipleAccounts");
|
|
|
|
|
let method = match method_result {
|
|
|
|
|
std::result::Result::Ok(method) => method,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let addresses = accounts.iter().map(std::string::ToString::to_string).map(serde_json::Value::String).collect::<std::vec::Vec<_>>();
|
|
|
|
|
let mut params = std::vec![serde_json::Value::Array(addresses)];
|
|
|
|
|
if let std::option::Option::Some(config) = config
|
|
|
|
|
&& !config.is_empty()
|
|
|
|
|
{
|
|
|
|
|
params.push(config.to_json_value());
|
|
|
|
|
}
|
|
|
|
|
let result = self.execute_standard_rpc(role, method, params).await;
|
|
|
|
|
let value = match result {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return decode_multiple_accounts_response("getMultipleAccounts", value, accounts.len());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Executes typed `getProgramAccounts` through the common KSP HTTP transport path.
|
|
|
|
|
pub async fn get_program_accounts(
|
|
|
|
|
&self,
|
|
|
|
|
role: &crate::HttpRoleName,
|
|
|
|
|
program_id: &ksp_core_lib::Pubkey,
|
|
|
|
|
config: std::option::Option<&crate::SolanaProgramAccountsConfig>,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaProgramAccountsResult> {
|
|
|
|
|
if let std::option::Option::Some(config) = config {
|
|
|
|
|
let validation = validate_program_account_filters(config.filters());
|
|
|
|
|
if let std::result::Result::Err(error) = validation {
|
|
|
|
|
return std::result::Result::Err(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let method_result = account_descriptor("getProgramAccounts");
|
|
|
|
|
let method = match method_result {
|
|
|
|
|
std::result::Result::Ok(method) => method,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let mut params = std::vec![serde_json::Value::String(program_id.to_string())];
|
|
|
|
|
if let std::option::Option::Some(config) = config
|
|
|
|
|
&& !config.is_empty()
|
|
|
|
|
{
|
|
|
|
|
params.push(config.to_json_value());
|
|
|
|
|
}
|
|
|
|
|
let result = self.execute_standard_rpc(role, method, params).await;
|
|
|
|
|
let value = match result {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return decode_program_accounts_response("getProgramAccounts", value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn account_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpRpcMethodDescriptor> {
|
|
|
|
|
let descriptor = crate::find_http_rpc_method(method);
|
|
|
|
|
return match descriptor {
|
|
|
|
|
std::option::Option::Some(descriptor)
|
|
|
|
|
if descriptor.category() == crate::HttpRpcCategory::Accounts && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_2 =>
|
|
|
|
|
{
|
|
|
|
|
std::result::Result::Ok(descriptor)
|
|
|
|
|
},
|
|
|
|
|
_ => std::result::Result::Err(
|
|
|
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Accounts descriptor is missing from the audited 0.2.2 registry")
|
|
|
|
|
.with_context("rpc_method", method),
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn invalid_account_parameters<T>(method: &str, message: &str, field: &'static str, value: usize) -> ksp_core_lib::Result<T> {
|
|
|
|
|
return std::result::Result::Err(
|
|
|
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, message)
|
|
|
|
|
.with_context("rpc_method", method)
|
|
|
|
|
.with_context(field, value.to_string()),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_program_account_filters(filters: &[crate::SolanaProgramAccountFilter]) -> ksp_core_lib::Result<()> {
|
|
|
|
|
if filters.len() > MAX_PROGRAM_ACCOUNT_FILTERS {
|
|
|
|
|
return invalid_account_parameters(
|
|
|
|
|
"getProgramAccounts",
|
|
|
|
|
"getProgramAccounts accepts at most 4 filters on the targeted Agave runtime",
|
|
|
|
|
"filter_count",
|
|
|
|
|
filters.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
for filter in filters {
|
|
|
|
|
if let crate::SolanaProgramAccountFilter::Memcmp(memcmp) = filter
|
|
|
|
|
&& let crate::SolanaMemcmpBytes::Bytes(bytes) = memcmp.bytes()
|
|
|
|
|
&& bytes.len() > MAX_MEMCMP_BYTES
|
|
|
|
|
{
|
|
|
|
|
return invalid_account_parameters(
|
|
|
|
|
"getProgramAccounts",
|
|
|
|
|
"raw getProgramAccounts memcmp data accepts at most 128 bytes",
|
|
|
|
|
"memcmp_byte_count",
|
|
|
|
|
bytes.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::result::Result::Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_account_info_response(
|
|
|
|
|
method: &str,
|
|
|
|
|
value: serde_json::Value,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::option::Option<crate::SolanaAccount>>> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireRpcResponse<std::option::Option<serde_json::Value>>>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
std::result::Result::Ok(wire) => wire,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
|
|
|
|
let context = match context {
|
|
|
|
|
std::result::Result::Ok(context) => context,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let account = match wire.value {
|
|
|
|
|
std::option::Option::Some(value) => {
|
|
|
|
|
let account = crate::SolanaAccount::decode_wire(method, value);
|
|
|
|
|
match account {
|
|
|
|
|
std::result::Result::Ok(account) => std::option::Option::Some(account),
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
std::option::Option::None => std::option::Option::None,
|
|
|
|
|
};
|
|
|
|
|
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, account));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_largest_accounts_response(
|
|
|
|
|
method: &str,
|
|
|
|
|
value: serde_json::Value,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaAccountBalance>>> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireRpcResponse<std::vec::Vec<serde_json::Value>>>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
std::result::Result::Ok(wire) => wire,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
|
|
|
|
let context = match context {
|
|
|
|
|
std::result::Result::Ok(context) => context,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let mut values = std::vec::Vec::with_capacity(wire.value.len());
|
|
|
|
|
for value in wire.value {
|
|
|
|
|
let decoded = crate::SolanaAccountBalance::decode_wire(method, value);
|
|
|
|
|
match decoded {
|
|
|
|
|
std::result::Result::Ok(decoded) => values.push(decoded),
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, values));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_multiple_accounts_response(
|
|
|
|
|
method: &str,
|
|
|
|
|
value: serde_json::Value,
|
|
|
|
|
expected_count: usize,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<std::option::Option<crate::SolanaAccount>>>> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireRpcResponse<std::vec::Vec<std::option::Option<serde_json::Value>>>>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
std::result::Result::Ok(wire) => wire,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
|
|
|
|
let context = match context {
|
|
|
|
|
std::result::Result::Ok(context) => context,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let mut values = std::vec::Vec::with_capacity(wire.value.len());
|
|
|
|
|
for value in wire.value {
|
|
|
|
|
match value {
|
|
|
|
|
std::option::Option::Some(value) => {
|
|
|
|
|
let decoded = crate::SolanaAccount::decode_wire(method, value);
|
|
|
|
|
match decoded {
|
|
|
|
|
std::result::Result::Ok(decoded) => values.push(std::option::Option::Some(decoded)),
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
std::option::Option::None => values.push(std::option::Option::None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if values.len() != expected_count {
|
|
|
|
|
return std::result::Result::Err(
|
|
|
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "getMultipleAccounts result count does not match the requested account count")
|
|
|
|
|
.with_context("rpc_method", method)
|
|
|
|
|
.with_context("expected_count", expected_count.to_string())
|
|
|
|
|
.with_context("actual_count", values.len().to_string()),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, values));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_program_accounts_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaProgramAccountsResult> {
|
|
|
|
|
let decoded = crate::decode_wire_json::<WireProgramAccountsResult>(method, value);
|
|
|
|
|
let wire = match decoded {
|
|
|
|
|
std::result::Result::Ok(wire) => wire,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return match wire {
|
|
|
|
|
WireProgramAccountsResult::Accounts(values) => {
|
|
|
|
|
let decoded = decode_keyed_accounts(method, values);
|
|
|
|
|
match decoded {
|
|
|
|
|
std::result::Result::Ok(values) => std::result::Result::Ok(crate::SolanaProgramAccountsResult::Accounts(values)),
|
|
|
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
WireProgramAccountsResult::Context(wire) => {
|
|
|
|
|
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
|
|
|
|
|
let context = match context {
|
|
|
|
|
std::result::Result::Ok(context) => context,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let values = decode_keyed_accounts(method, wire.value);
|
|
|
|
|
match values {
|
|
|
|
|
std::result::Result::Ok(values) => {
|
|
|
|
|
std::result::Result::Ok(crate::SolanaProgramAccountsResult::Context(crate::SolanaRpcResponse::new(context, values)))
|
|
|
|
|
},
|
|
|
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn decode_keyed_accounts(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaKeyedAccount>> {
|
|
|
|
|
let mut decoded_values = std::vec::Vec::with_capacity(values.len());
|
|
|
|
|
for value in values {
|
|
|
|
|
let decoded = crate::SolanaKeyedAccount::decode_wire(method, value);
|
|
|
|
|
match decoded {
|
|
|
|
|
std::result::Result::Ok(decoded) => decoded_values.push(decoded),
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::result::Result::Ok(decoded_values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct WireRpcResponse<T> {
|
|
|
|
|
context: serde_json::Value,
|
|
|
|
|
value: T,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
|
enum WireProgramAccountsResult {
|
|
|
|
|
Context(WireRpcResponse<std::vec::Vec<serde_json::Value>>),
|
|
|
|
|
Accounts(std::vec::Vec<serde_json::Value>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
|
enum WireAccountData {
|
|
|
|
|
@@ -601,7 +955,6 @@ enum WireAccountData {
|
|
|
|
|
Encoded((std::string::String, std::string::String)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct WireParsedAccountData {
|
|
|
|
|
program: std::string::String,
|
|
|
|
|
@@ -609,7 +962,6 @@ struct WireParsedAccountData {
|
|
|
|
|
space: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct WireAccount {
|
|
|
|
|
lamports: u64,
|
|
|
|
|
@@ -622,14 +974,12 @@ struct WireAccount {
|
|
|
|
|
space: std::option::Option<u64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct WireKeyedAccount {
|
|
|
|
|
pubkey: std::string::String,
|
|
|
|
|
account: serde_json::Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
struct WireAccountBalance {
|
|
|
|
|
address: std::string::String,
|
|
|
|
|
|