implementing snapshots and adding rpc server to the quic_geyser plugin to get snapshot
This commit is contained in:
parent
471c20686b
commit
181071acb7
File diff suppressed because it is too large
Load Diff
|
@ -27,6 +27,8 @@ solana-program = "~1.18.15"
|
||||||
solana-transaction-status = "~1.18.15"
|
solana-transaction-status = "~1.18.15"
|
||||||
solana-logger = "~1.18.15"
|
solana-logger = "~1.18.15"
|
||||||
solana-rpc-client = "~1.18.15"
|
solana-rpc-client = "~1.18.15"
|
||||||
|
solana-rpc-client-api = "~1.18.15"
|
||||||
|
solana-account-decoder = "~1.18.15"
|
||||||
|
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
serde = "1.0.201"
|
serde = "1.0.201"
|
||||||
|
|
|
@ -20,13 +20,25 @@ serde = { workspace = true }
|
||||||
solana-sdk = { workspace = true }
|
solana-sdk = { workspace = true }
|
||||||
solana-logger = { workspace = true }
|
solana-logger = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
solana-rpc-client-api = { workspace = true }
|
||||||
|
solana-account-decoder = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = {workspace = true}
|
thiserror = {workspace = true}
|
||||||
|
|
||||||
|
jsonrpsee = { version = "0.20.0", features = ["macros", "full"] }
|
||||||
|
tower = "0.4.13"
|
||||||
|
tower-http = { version = "0.4.0", features = ["full"] }
|
||||||
|
|
||||||
quic-geyser-common = { workspace = true }
|
quic-geyser-common = { workspace = true }
|
||||||
quic-geyser-server = { workspace = true }
|
quic-geyser-server = { workspace = true }
|
||||||
quic-geyser-block-builder = { workspace = true }
|
quic-geyser-block-builder = { workspace = true }
|
||||||
|
quic-geyser-snapshot = { workspace = true }
|
||||||
|
|
||||||
|
lite-account-manager-common = { workspace = true }
|
||||||
|
itertools = { workspace = true }
|
||||||
|
tokio = {workspace = true}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::{fs::read_to_string, path::Path};
|
||||||
|
|
||||||
use agave_geyser_plugin_interface::geyser_plugin_interface::GeyserPluginError;
|
use agave_geyser_plugin_interface::geyser_plugin_interface::GeyserPluginError;
|
||||||
use quic_geyser_common::config::ConfigQuicPlugin;
|
use quic_geyser_common::config::ConfigQuicPlugin;
|
||||||
|
use quic_geyser_snapshot::snapshot_config::SnapshotConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -10,6 +11,8 @@ pub struct Config {
|
||||||
pub libpath: String,
|
pub libpath: String,
|
||||||
|
|
||||||
pub quic_plugin: ConfigQuicPlugin,
|
pub quic_plugin: ConfigQuicPlugin,
|
||||||
|
|
||||||
|
pub rpc_server: RpcServiceConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -24,3 +27,23 @@ impl Config {
|
||||||
Self::load_from_str(&config)
|
Self::load_from_str(&config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct RpcServiceConfig {
|
||||||
|
#[serde(default = "RpcServiceConfig::default_rpc_service_enable")]
|
||||||
|
pub enable: bool,
|
||||||
|
#[serde(default = "RpcServiceConfig::default_port")]
|
||||||
|
pub port: u16,
|
||||||
|
#[serde(default)]
|
||||||
|
pub snapshot_config: SnapshotConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcServiceConfig {
|
||||||
|
pub fn default_rpc_service_enable() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
pub fn default_port() -> u16 {
|
||||||
|
10801
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod quic_plugin;
|
pub mod quic_plugin;
|
||||||
|
pub mod rpc_server;
|
||||||
|
|
|
@ -13,17 +13,21 @@ use quic_geyser_common::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use quic_geyser_server::quic_server::QuicServer;
|
use quic_geyser_server::quic_server::QuicServer;
|
||||||
|
use quic_geyser_snapshot::snapshot_creator::SnapshotCreator;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account, clock::Slot, commitment_config::CommitmentConfig, message::v0::Message,
|
account::Account, clock::Slot, commitment_config::CommitmentConfig, message::v0::Message,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::{config::Config, rpc_server::RpcServerImpl};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct QuicGeyserPlugin {
|
pub struct QuicGeyserPlugin {
|
||||||
|
runtime: Option<Runtime>,
|
||||||
quic_server: Option<QuicServer>,
|
quic_server: Option<QuicServer>,
|
||||||
block_builder_channel: Option<std::sync::mpsc::Sender<ChannelMessage>>,
|
block_builder_channel: Option<std::sync::mpsc::Sender<ChannelMessage>>,
|
||||||
|
rpc_server_message_channel: Option<tokio::sync::mpsc::UnboundedSender<ChannelMessage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GeyserPlugin for QuicGeyserPlugin {
|
impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
|
@ -34,9 +38,11 @@ impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
fn on_load(&mut self, config_file: &str, _is_reload: bool) -> PluginResult<()> {
|
fn on_load(&mut self, config_file: &str, _is_reload: bool) -> PluginResult<()> {
|
||||||
log::info!("loading quic_geyser plugin");
|
log::info!("loading quic_geyser plugin");
|
||||||
let config = Config::load_from_file(config_file)?;
|
let config = Config::load_from_file(config_file)?;
|
||||||
|
let compression_params = config.quic_plugin.compression_parameters.clone();
|
||||||
let compression_type = config.quic_plugin.compression_parameters.compression_type;
|
let compression_type = config.quic_plugin.compression_parameters.compression_type;
|
||||||
let enable_block_builder = config.quic_plugin.enable_block_builder;
|
let enable_block_builder = config.quic_plugin.enable_block_builder;
|
||||||
let build_blocks_with_accounts = config.quic_plugin.build_blocks_with_accounts;
|
let build_blocks_with_accounts = config.quic_plugin.build_blocks_with_accounts;
|
||||||
|
let snapshot_config = config.rpc_server.snapshot_config;
|
||||||
log::info!("Quic plugin config correctly loaded");
|
log::info!("Quic plugin config correctly loaded");
|
||||||
solana_logger::setup_with_default(&config.quic_plugin.log_level);
|
solana_logger::setup_with_default(&config.quic_plugin.log_level);
|
||||||
let quic_server = QuicServer::new(config.quic_plugin).map_err(|_| {
|
let quic_server = QuicServer::new(config.quic_plugin).map_err(|_| {
|
||||||
|
@ -52,6 +58,26 @@ impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
);
|
);
|
||||||
self.block_builder_channel = Some(sx);
|
self.block_builder_channel = Some(sx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.rpc_server.enable {
|
||||||
|
let runtime =
|
||||||
|
Runtime::new().map_err(|error| GeyserPluginError::Custom(Box::new(error)))?;
|
||||||
|
let port = config.rpc_server.port;
|
||||||
|
|
||||||
|
let (server_sx, server_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
self.rpc_server_message_channel = Some(server_sx);
|
||||||
|
|
||||||
|
runtime.block_on(async move {
|
||||||
|
let snapshot_creator = SnapshotCreator::new(snapshot_config, compression_params);
|
||||||
|
snapshot_creator.start_listening(server_rx);
|
||||||
|
|
||||||
|
let rpc_server = RpcServerImpl::new(snapshot_creator);
|
||||||
|
if let Err(e) = RpcServerImpl::start_serving(rpc_server, port).await {
|
||||||
|
log::error!("Error starting http server: {e:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.runtime = Some(runtime);
|
||||||
|
}
|
||||||
self.quic_server = Some(quic_server);
|
self.quic_server = Some(quic_server);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -104,6 +130,10 @@ impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
let _ = block_channel.send(channel_message.clone());
|
let _ = block_channel.send(channel_message.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(rpc_server_message_channel) = &self.rpc_server_message_channel {
|
||||||
|
let _ = rpc_server_message_channel.send(channel_message.clone());
|
||||||
|
}
|
||||||
|
|
||||||
quic_server
|
quic_server
|
||||||
.send_message(channel_message)
|
.send_message(channel_message)
|
||||||
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
||||||
|
@ -135,6 +165,10 @@ impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
let _ = block_channel.send(slot_message.clone());
|
let _ = block_channel.send(slot_message.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(rpc_server_message_channel) = &self.rpc_server_message_channel {
|
||||||
|
let _ = rpc_server_message_channel.send(slot_message.clone());
|
||||||
|
}
|
||||||
|
|
||||||
quic_server
|
quic_server
|
||||||
.send_message(slot_message)
|
.send_message(slot_message)
|
||||||
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
||||||
|
@ -245,6 +279,10 @@ impl GeyserPlugin for QuicGeyserPlugin {
|
||||||
let _ = block_channel.send(block_meta_message.clone());
|
let _ = block_channel.send(block_meta_message.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(rpc_server_message_channel) = &self.rpc_server_message_channel {
|
||||||
|
let _ = rpc_server_message_channel.send(block_meta_message.clone());
|
||||||
|
}
|
||||||
|
|
||||||
quic_server
|
quic_server
|
||||||
.send_message(block_meta_message)
|
.send_message(block_meta_message)
|
||||||
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
.map_err(|e| GeyserPluginError::Custom(Box::new(e)))?;
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use jsonrpsee::server::ServerBuilder;
|
||||||
|
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||||
|
use lite_account_manager_common::account_filter::AccountFilterType as AmAccountFilterType;
|
||||||
|
use lite_account_manager_common::{account_data::AccountData, commitment::Commitment};
|
||||||
|
use quic_geyser_snapshot::snapshot_creator::SnapshotCreator;
|
||||||
|
use solana_account_decoder::UiAccount;
|
||||||
|
use solana_rpc_client_api::client_error::reqwest::Method;
|
||||||
|
use solana_rpc_client_api::{
|
||||||
|
config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||||
|
response::{OptionalContext, Response as RpcResponse, RpcKeyedAccount, RpcResponseContext},
|
||||||
|
};
|
||||||
|
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
|
||||||
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
|
|
||||||
|
#[rpc(server)]
|
||||||
|
pub trait PluginRpc {
|
||||||
|
#[method(name = "getProgramAccounts")]
|
||||||
|
async fn get_program_accounts(
|
||||||
|
&self,
|
||||||
|
program_id_str: String,
|
||||||
|
config: Option<RpcProgramAccountsConfig>,
|
||||||
|
) -> RpcResult<OptionalContext<Vec<RpcKeyedAccount>>>;
|
||||||
|
|
||||||
|
#[method(name = "getSnapshot")]
|
||||||
|
async fn get_snapshot(&self, program_id_str: String) -> RpcResult<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RpcServerImpl {
|
||||||
|
snapshot_creator: SnapshotCreator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcServerImpl {
|
||||||
|
pub fn new(snapshot_creator: SnapshotCreator) -> Self {
|
||||||
|
Self { snapshot_creator }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_serving(rpc_impl: RpcServerImpl, port: u16) -> anyhow::Result<()> {
|
||||||
|
let http_addr = format!("[::]:{port}");
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.max_age(Duration::from_secs(86400))
|
||||||
|
// Allow `POST` when accessing the resource
|
||||||
|
.allow_methods([Method::POST, Method::GET, Method::OPTIONS])
|
||||||
|
// Allow requests from any origin
|
||||||
|
.allow_origin(Any)
|
||||||
|
.allow_headers(Any);
|
||||||
|
|
||||||
|
let middleware = tower::ServiceBuilder::new().layer(cors);
|
||||||
|
|
||||||
|
let http_server_handle = ServerBuilder::default()
|
||||||
|
.set_middleware(middleware)
|
||||||
|
.max_connections(10)
|
||||||
|
.max_request_body_size(16 * 1024 * 1024) // 16 MB
|
||||||
|
.max_response_body_size(64 * 1024 * 1024 * 1024) // 64 GB
|
||||||
|
.http_only()
|
||||||
|
.build(http_addr.clone())
|
||||||
|
.await?
|
||||||
|
.start(rpc_impl.into_rpc());
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
log::info!("HTTP Server started at {http_addr:?}");
|
||||||
|
http_server_handle.stopped().await;
|
||||||
|
log::error!("QUIC GEYSER PLUGIN HTTP SERVER STOPPED");
|
||||||
|
panic!("QUIC GEYSER PLUGIN HTTP SERVER STOPPED")
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[jsonrpsee::core::async_trait]
|
||||||
|
impl PluginRpcServer for RpcServerImpl {
|
||||||
|
async fn get_program_accounts(
|
||||||
|
&self,
|
||||||
|
program_id_str: String,
|
||||||
|
config: Option<RpcProgramAccountsConfig>,
|
||||||
|
) -> RpcResult<OptionalContext<Vec<RpcKeyedAccount>>> {
|
||||||
|
let Ok(program_id) = Pubkey::from_str(&program_id_str) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorCode::InternalError.into());
|
||||||
|
};
|
||||||
|
let with_context = config
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| value.with_context.unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let commitment: CommitmentConfig = config
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.account_config.commitment)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let account_filters = config
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| {
|
||||||
|
x.filters
|
||||||
|
.as_ref()
|
||||||
|
.map(|filters| filters.iter().map(AmAccountFilterType::from).collect_vec())
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let commitment = Commitment::from(commitment);
|
||||||
|
|
||||||
|
let gpa = self
|
||||||
|
.snapshot_creator
|
||||||
|
.get_program_accounts(program_id, account_filters, commitment)
|
||||||
|
.await
|
||||||
|
.map_err(|_| jsonrpsee::types::error::ErrorCode::InternalError)?;
|
||||||
|
|
||||||
|
let min_context_slot = config
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| {
|
||||||
|
if c.with_context.unwrap_or_default() {
|
||||||
|
c.account_config.min_context_slot
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let slot = gpa
|
||||||
|
.iter()
|
||||||
|
.map(|program_account| program_account.updated_slot)
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let acc_config = config.map(|c| c.account_config);
|
||||||
|
|
||||||
|
let rpc_keyed_accounts = gpa
|
||||||
|
.iter()
|
||||||
|
.filter_map(|account_data| {
|
||||||
|
if account_data.updated_slot >= min_context_slot {
|
||||||
|
Some(RpcKeyedAccount {
|
||||||
|
pubkey: account_data.pubkey.to_string(),
|
||||||
|
account: convert_account_data_to_ui_account(
|
||||||
|
account_data,
|
||||||
|
acc_config.clone(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
if with_context {
|
||||||
|
Ok(OptionalContext::Context(RpcResponse {
|
||||||
|
context: RpcResponseContext {
|
||||||
|
slot,
|
||||||
|
api_version: None,
|
||||||
|
},
|
||||||
|
value: rpc_keyed_accounts,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(OptionalContext::NoContext(rpc_keyed_accounts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_snapshot(&self, program_id_str: String) -> RpcResult<Vec<u8>> {
|
||||||
|
let program_id = Pubkey::from_str(program_id_str.as_str())
|
||||||
|
.map_err(|_| jsonrpsee::types::error::ErrorCode::InvalidParams)?;
|
||||||
|
let res = self
|
||||||
|
.snapshot_creator
|
||||||
|
.create_snapshot(program_id)
|
||||||
|
.await
|
||||||
|
.map_err(|_| jsonrpsee::types::error::ErrorCode::InternalError)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_account_data_to_ui_account(
|
||||||
|
account_data: &AccountData,
|
||||||
|
config: Option<RpcAccountInfoConfig>,
|
||||||
|
) -> UiAccount {
|
||||||
|
let encoding = config
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.encoding)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.unwrap_or(solana_account_decoder::UiAccountEncoding::Base64);
|
||||||
|
let data_slice = config.as_ref().map(|c| c.data_slice).unwrap_or_default();
|
||||||
|
UiAccount::encode(
|
||||||
|
&account_data.pubkey,
|
||||||
|
&account_data.account.to_solana_account(),
|
||||||
|
encoding,
|
||||||
|
None,
|
||||||
|
data_slice,
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue