1#![allow(unused_imports, dead_code, unused_mut, unused_variables)]
2
3use std::{
4 collections::{HashMap, HashSet},
5 net::SocketAddr,
6 path::PathBuf,
7 sync::{Arc, RwLock},
8 thread::{JoinHandle, sleep},
9 time::{Duration, Instant},
10};
11
12use agave_geyser_plugin_interface::geyser_plugin_interface::{
13 GeyserPlugin, ReplicaBlockInfoV4, ReplicaBlockInfoVersions, ReplicaEntryInfoV2,
14 ReplicaEntryInfoVersions, ReplicaTransactionInfoV3, ReplicaTransactionInfoVersions, SlotStatus,
15};
16use chrono::{Local, Utc};
17use crossbeam::select;
18use crossbeam_channel::{Receiver, Sender, unbounded};
19use itertools::Itertools;
20use jsonrpc_core::MetaIoHandler;
21use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
22use jsonrpc_pubsub::{PubSubHandler, Session};
23use jsonrpc_ws_server::{RequestContext, ServerBuilder as WsServerBuilder};
24use libloading::{Library, Symbol};
25use serde::Serialize;
26use solana_commitment_config::CommitmentConfig;
27#[cfg(feature = "geyser_plugin")]
28use solana_geyser_plugin_manager::geyser_plugin_manager::{
29 GeyserPluginManager, LoadedGeyserPlugin,
30};
31use solana_message::SimpleAddressLoader;
32use solana_transaction::sanitized::{MessageHash, SanitizedTransaction};
33use solana_transaction_status::RewardsAndNumPartitions;
34use surfpool_types::{
35 BlockProductionMode, ClockCommand, ClockEvent, DEFAULT_MAINNET_RPC_URL, DataIndexingCommand,
36 SimnetCommand, SimnetConfig, SimnetEvent, SurfpoolConfig,
37};
38type PluginConstructor = unsafe fn() -> *mut dyn GeyserPlugin;
39use txtx_addon_kit::helpers::fs::FileLocation;
40
41use crate::{
42 PluginInfo,
43 rpc::{
44 self, RunloopContext, SurfpoolMiddleware, SurfpoolWebsocketMeta,
45 SurfpoolWebsocketMiddleware, accounts_data::AccountsData, accounts_scan::AccountsScan,
46 admin::AdminRpc, bank_data::BankData, full::Full, jito::Jito, minimal::Minimal,
47 surfnet_cheatcodes::SurfnetCheatcodes, ws::Rpc,
48 },
49 surfnet::{GeyserEvent, PluginCommand, locker::SurfnetSvmLocker, remote::SurfnetRemoteClient},
50};
51
52const BLOCKHASH_SLOT_TTL: u64 = 75;
53
54pub(crate) struct ManagedPlugin {
58 pub(crate) uuid: crate::Uuid,
59 pub(crate) name: String,
60 pub(crate) plugin: Box<dyn GeyserPlugin>,
61 pub(crate) _library: Library,
62 pub(crate) config_path: String,
63}
64
65pub(crate) fn load_plugin_from_config(
66 config_file: &str,
67 is_reload: bool,
68) -> Result<ManagedPlugin, String> {
69 let plugin_manifest_location = FileLocation::from_path(PathBuf::from(config_file));
70 let config_content = plugin_manifest_location
71 .read_content_as_utf8()
72 .map_err(|e| format!("Unable to read plugin config '{}': {}", config_file, e))?;
73 let result: serde_json::Value = serde_json::from_str(&config_content)
74 .map_err(|e| format!("Unable to parse plugin manifest '{}': {}", config_file, e))?;
75
76 let plugin_dylib_path = result
77 .get("libpath")
78 .and_then(|p| p.as_str())
79 .ok_or_else(|| {
80 format!(
81 "Plugin config file should include a 'libpath' field: {}",
82 plugin_manifest_location
83 )
84 })?;
85
86 let mut plugin_dylib_location = plugin_manifest_location
87 .get_parent_location()
88 .expect("path invalid");
89 plugin_dylib_location
90 .append_path(plugin_dylib_path)
91 .expect("path invalid");
92
93 let (plugin, library) = unsafe {
94 let lib = Library::new(&plugin_dylib_location.to_string())
95 .map_err(|e| format!("Unable to load plugin {}: {}", plugin_dylib_location, e))?;
96 let constructor: Symbol<PluginConstructor> = lib
97 .get(b"_create_plugin")
98 .map_err(|e| format!("Plugin missing _create_plugin symbol: {}", e))?;
99 let plugin_raw = constructor();
100 (Box::from_raw(plugin_raw), lib)
101 };
102
103 let name = plugin.name().to_string();
104
105 let mut plugin = plugin;
106 plugin
107 .on_load(config_file, is_reload)
108 .map_err(|e| format!("Failed to call on_load for plugin '{}': {}", name, e))?;
109
110 let uuid = crate::Uuid::new_v4();
111
112 Ok(ManagedPlugin {
113 uuid,
114 name,
115 plugin,
116 _library: library,
117 config_path: config_file.to_string(),
118 })
119}
120
121pub(crate) fn unload_plugin_by_name(
122 name: &str,
123 managed_plugins: &mut Vec<ManagedPlugin>,
124) -> Result<(), String> {
125 let idx = managed_plugins
126 .iter()
127 .position(|p| p.name == name)
128 .ok_or_else(|| format!("Plugin '{}' not found", name))?;
129
130 let mut plugin = managed_plugins.remove(idx);
131 plugin.plugin.on_unload();
132 Ok(())
134}
135
136fn check_port_availability(addr: SocketAddr, server_type: &str) -> Result<(), String> {
138 match std::net::TcpListener::bind(addr) {
139 Ok(_listener) => Ok(()),
140 Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
141 let msg = format!(
142 "{} port {} is already in use. Try --port or --ws-port to use a different port.",
143 server_type,
144 addr.port()
145 );
146 eprintln!("Error: {}", msg);
147 Err(msg)
148 }
149 Err(e) => {
150 let msg = format!("Failed to bind {} server to {}: {}", server_type, addr, e);
151 eprintln!("Error: {}", msg);
152 Err(msg)
153 }
154 }
155}
156
157pub async fn start_local_surfnet_runloop(
158 svm_locker: SurfnetSvmLocker,
159 config: SurfpoolConfig,
160 simnet_commands_tx: Sender<SimnetCommand>,
161 simnet_commands_rx: Receiver<SimnetCommand>,
162 geyser_events_rx: Receiver<GeyserEvent>,
163) -> Result<(), Box<dyn std::error::Error>> {
164 let Some(simnet) = config.simnets.first() else {
165 return Ok(());
166 };
167 let block_production_mode = simnet.block_production_mode.clone();
168
169 let remote_rpc_client = match simnet.offline_mode {
170 true => None,
171 false => SurfnetRemoteClient::new_unsafe(
172 simnet
173 .remote_rpc_url
174 .as_ref()
175 .unwrap_or(&DEFAULT_MAINNET_RPC_URL.to_string()),
176 ),
177 };
178
179 svm_locker.initialize(&remote_rpc_client).await?;
180
181 if let Some(remote_client) = &remote_rpc_client {
182 let _ = svm_locker
183 .simnet_events_tx()
184 .send(SimnetEvent::Connected(remote_client.client.url()));
185 }
186 let _ = svm_locker
187 .simnet_events_tx()
188 .send(SimnetEvent::EpochInfoUpdate(svm_locker.get_epoch_info()));
189
190 svm_locker.airdrop_pubkeys(simnet.airdrop_token_amount, &simnet.airdrop_addresses);
191
192 if !simnet.snapshot.is_empty() {
194 match svm_locker
195 .load_snapshot(
196 &simnet.snapshot,
197 remote_rpc_client.as_ref(),
198 CommitmentConfig::confirmed(),
199 )
200 .await
201 {
202 Ok(loaded_count) => {
203 let _ = svm_locker.with_svm_reader(|svm| {
204 svm.simnet_events_tx.send(SimnetEvent::info(format!(
205 "Preloaded {} accounts from snapshot(s) into SVM",
206 loaded_count
207 )))
208 });
209 }
210 Err(e) => {
211 let _ = svm_locker.with_svm_reader(|svm| {
212 svm.simnet_events_tx.send(SimnetEvent::warn(format!(
213 "Error loading snapshot accounts: {}",
214 e
215 )))
216 });
217 }
218 }
219 }
220
221 let simnet_events_tx_cc = svm_locker.simnet_events_tx();
222
223 let (plugin_commands_tx, plugin_commands_rx) = unbounded::<PluginCommand>();
224
225 let (_rpc_handle, _ws_handle, shutdown_rpc_servers) = start_rpc_servers_runloop(
226 &config,
227 &simnet_commands_tx,
228 svm_locker.clone(),
229 &remote_rpc_client,
230 plugin_commands_tx,
231 )
232 .await?;
233
234 let simnet_config = simnet.clone();
235
236 match start_geyser_runloop(
237 config.plugin_config_path.clone(),
238 simnet_events_tx_cc.clone(),
239 geyser_events_rx,
240 plugin_commands_rx,
241 ) {
242 Ok(_) => {}
243 Err(e) => {
244 let _ =
245 simnet_events_tx_cc.send(SimnetEvent::error(format!("Geyser plugin failed: {e}")));
246 }
247 };
248
249 let (clock_event_rx, clock_command_tx) =
250 start_clock_runloop(simnet_config.slot_time, Some(simnet_events_tx_cc.clone()));
251
252 let initial_transaction_count = svm_locker.with_svm_reader(|svm| {
254 let iter_result = svm.transactions.into_iter();
255 let mut count: u64 = 0;
256
257 if let Ok(iter) = iter_result {
258 let mut events = vec![];
259 for (_, status) in iter {
260 if let Some((tx_meta, _updated_accounts)) = status.as_processed() {
261 let signature = tx_meta.transaction.signatures[0];
262 let err = tx_meta.meta.status.clone().err();
263
264 let meta = surfpool_types::TransactionMetadata {
266 signature,
267 logs: tx_meta.meta.log_messages.clone().unwrap_or_default(),
268 inner_instructions: tx_meta
269 .meta
270 .inner_instructions
271 .clone()
272 .unwrap_or_default()
273 .into_iter()
274 .map(|inner_ixs| {
275 inner_ixs
276 .instructions
277 .into_iter()
278 .map(|ix| solana_message::inner_instruction::InnerInstruction {
279 instruction: ix.instruction,
280 stack_height: ix.stack_height.unwrap_or(1) as u8,
281 })
282 .collect()
283 })
284 .collect(),
285 compute_units_consumed: tx_meta.meta.compute_units_consumed.unwrap_or(0),
286 return_data: tx_meta.meta.return_data.clone().unwrap_or_default(),
287 fee: tx_meta.meta.fee,
288 };
289
290 events.push((
291 tx_meta.slot,
292 SimnetEvent::TransactionProcessed(Local::now(), meta, err.clone()),
293 ));
294
295 count += 1;
296 }
297 }
298 for (_, event) in events
299 .into_iter()
300 .sorted_by(|(a_slot, _), (b_slot, _)| a_slot.cmp(b_slot))
301 {
302 let _ = svm.simnet_events_tx.send(event);
303 }
304 }
305
306 count
307 });
308 let _ = simnet_events_tx_cc.send(SimnetEvent::Ready(initial_transaction_count));
309
310 let _ = svm_locker.with_svm_reader(|svm| svm.geyser_events_tx.send(GeyserEvent::EndOfStartup));
312
313 start_block_production_runloop(
314 clock_event_rx,
315 clock_command_tx,
316 simnet_commands_rx,
317 simnet_commands_tx.clone(),
318 svm_locker,
319 block_production_mode,
320 &remote_rpc_client,
321 simnet_config.expiry.map(|e| e * 1000),
322 &simnet_config,
323 Some(shutdown_rpc_servers),
324 )
325 .await
326}
327
328#[allow(clippy::too_many_arguments)]
329pub async fn start_block_production_runloop(
330 clock_event_rx: Receiver<ClockEvent>,
331 clock_command_tx: Sender<ClockCommand>,
332 simnet_commands_rx: Receiver<SimnetCommand>,
333 simnet_commands_tx: Sender<SimnetCommand>,
334 svm_locker: SurfnetSvmLocker,
335 mut block_production_mode: BlockProductionMode,
336 remote_rpc_client: &Option<SurfnetRemoteClient>,
337 expiry_duration_ms: Option<u64>,
338 simnet_config: &SimnetConfig,
339 mut shutdown_rpc_servers: Option<Box<dyn FnOnce() + Send>>,
340) -> Result<(), Box<dyn std::error::Error>> {
341 let remote_client_with_commitment = remote_rpc_client.as_ref().map(|c| {
342 (
343 c.clone(),
344 solana_commitment_config::CommitmentConfig::confirmed(),
345 )
346 });
347 let mut next_scheduled_expiry_check: Option<u64> =
348 expiry_duration_ms.map(|expiry_val| Utc::now().timestamp_millis() as u64 + expiry_val);
349 let global_skip_sig_verify = simnet_config.skip_signature_verification;
350 let ix_profiling_initially_enabled = simnet_config.instruction_profiling_enabled;
351 loop {
352 let mut do_produce_block = false;
353
354 select! {
355 recv(clock_event_rx) -> msg => if let Ok(event) = msg {
356 match event {
357 ClockEvent::Tick => {
358 if block_production_mode.eq(&BlockProductionMode::Clock) {
359 do_produce_block = true;
360 }
361
362 if let Some(expiry_ms) = expiry_duration_ms {
363 if let Some(scheduled_time_ref) = &mut next_scheduled_expiry_check {
364 let now_ms = Utc::now().timestamp_millis() as u64;
365 if now_ms >= *scheduled_time_ref {
366 let svm = svm_locker.0.read().await;
367 if svm.updated_at + expiry_ms < now_ms {
368 let _ = simnet_commands_tx.send(SimnetCommand::Terminate(None));
369 } else {
370 *scheduled_time_ref = svm.updated_at + expiry_ms;
371 }
372 }
373 }
374 }
375 }
376 ClockEvent::ExpireBlockHash => {
377 do_produce_block = true;
378 }
379 }
380 },
381 recv(simnet_commands_rx) -> msg => if let Ok(event) = msg {
382 match event {
383 SimnetCommand::SlotForward(_key) => {
384 block_production_mode = BlockProductionMode::Manual;
385 do_produce_block = true;
386 }
387 SimnetCommand::SlotBackward(_key) => {
388
389 }
390 SimnetCommand::CommandClock(_, update) => {
391 if let ClockCommand::UpdateSlotInterval(updated_slot_time) = update {
392 svm_locker.with_svm_writer(|svm_writer| {
393 svm_writer.slot_time = updated_slot_time;
394 });
395 }
396
397 if let ClockCommand::PauseWithConfirmation(response_tx) = update {
399 let (current_slot, slot_time) = svm_locker.with_svm_reader(|svm_reader| {
401 (svm_reader.latest_epoch_info.absolute_slot, svm_reader.slot_time)
402 });
403
404 let _ = clock_command_tx.send(ClockCommand::Pause);
406
407 tokio::time::sleep(tokio::time::Duration::from_millis(slot_time / 2)).await;
409
410 let max_attempts = 10;
412 let mut attempts = 0;
413 loop {
414 tokio::time::sleep(tokio::time::Duration::from_millis(slot_time)).await;
415
416 let new_slot = svm_locker.with_svm_reader(|svm_reader| {
417 svm_reader.latest_epoch_info.absolute_slot
418 });
419
420 if new_slot == current_slot || attempts >= max_attempts {
422 break;
423 }
424
425 attempts += 1;
426 }
427
428 let epoch_info = svm_locker.with_svm_reader(|svm_reader| {
430 svm_reader.latest_epoch_info.clone()
431 });
432 let _ = response_tx.send(epoch_info);
434 } else {
435 let _ = clock_command_tx.send(update);
436 }
437 continue
438 }
439 SimnetCommand::UpdateInternalClock(_, clock) => {
440 if let Err(e) = svm_locker.confirm_current_block(&remote_client_with_commitment).await {
442 let _ = svm_locker.simnet_events_tx().send(SimnetEvent::error(format!(
443 "Failed to confirm block after time travel: {}", e
444 )));
445 }
446
447 svm_locker.with_svm_writer(|svm_writer| {
448 svm_writer.inner.set_sysvar(&clock);
449 svm_writer.updated_at = clock.unix_timestamp as u64 * 1_000;
450 svm_writer.latest_epoch_info.absolute_slot = clock.slot;
451 svm_writer.latest_epoch_info.epoch = clock.epoch;
452 svm_writer.latest_epoch_info.slot_index = clock.slot;
453 svm_writer.latest_epoch_info.epoch = clock.epoch;
454 svm_writer.latest_epoch_info.absolute_slot = clock.slot + clock.epoch * svm_writer.latest_epoch_info.slots_in_epoch;
455 let _ = svm_writer.simnet_events_tx.send(SimnetEvent::SystemClockUpdated(clock));
456 });
457 }
458 SimnetCommand::UpdateInternalClockWithConfirmation(_, clock, response_tx) => {
459 if let Err(e) = svm_locker.confirm_current_block(&remote_client_with_commitment).await {
461 let _ = svm_locker.simnet_events_tx().send(SimnetEvent::error(format!(
462 "Failed to confirm block after time travel: {}", e
463 )));
464 }
465
466 let epoch_info = svm_locker.with_svm_writer(|svm_writer| {
467 svm_writer.inner.set_sysvar(&clock);
468 svm_writer.updated_at = clock.unix_timestamp as u64 * 1_000;
469 svm_writer.latest_epoch_info.absolute_slot = clock.slot;
470 svm_writer.latest_epoch_info.epoch = clock.epoch;
471 svm_writer.latest_epoch_info.slot_index = clock.slot;
472 svm_writer.latest_epoch_info.epoch = clock.epoch;
473 svm_writer.latest_epoch_info.absolute_slot = clock.slot + clock.epoch * svm_writer.latest_epoch_info.slots_in_epoch;
474 let _ = svm_writer.simnet_events_tx.send(SimnetEvent::SystemClockUpdated(clock));
475 svm_writer.latest_epoch_info.clone()
476 });
477
478 let _ = response_tx.send(epoch_info);
480 }
481 SimnetCommand::UpdateBlockProductionMode(update) => {
482 block_production_mode = update;
483 continue
484 }
485 SimnetCommand::ProcessTransaction(_key, transaction, status_tx, skip_preflight, skip_sig_verify_override) => {
486 let skip_sig_verify = skip_sig_verify_override.unwrap_or(global_skip_sig_verify);
487 let sigverify = !skip_sig_verify;
488 if let Err(e) = svm_locker.process_transaction(&remote_client_with_commitment, transaction, status_tx, skip_preflight, sigverify).await {
489 let _ = svm_locker.simnet_events_tx().send(SimnetEvent::error(format!("Failed to process transaction: {}", e)));
490 }
491 if block_production_mode.eq(&BlockProductionMode::Transaction) {
492 do_produce_block = true;
493 }
494 }
495 SimnetCommand::Terminate(_) => {
496 svm_locker.shutdown();
498 if let Some(shutdown_fn) = shutdown_rpc_servers.take() {
501 std::thread::spawn(shutdown_fn);
502 }
503 break;
504 }
505 SimnetCommand::StartRunbookExecution(runbook_id) => {
506 svm_locker.start_runbook_execution(runbook_id);
507 }
508
509 SimnetCommand::CompleteRunbookExecution(runbook_id, error) => {
510 svm_locker.complete_runbook_execution(runbook_id, error, ix_profiling_initially_enabled);
511 }
512 SimnetCommand::FetchRemoteAccounts(pubkeys, remote_url) => {
513 let remote_client = SurfnetRemoteClient::new_unsafe(&remote_url);
514 if let Some(remote_client) = remote_client {
515 match svm_locker.get_multiple_accounts_with_remote_fallback(&remote_client, &pubkeys, CommitmentConfig::confirmed()).await {
516 Ok(account_updates) => {
517 svm_locker.write_multiple_account_updates(&account_updates.inner);
518 }
519 Err(e) => {
520 svm_locker.simnet_events_tx().try_send(SimnetEvent::error(format!("Failed to fetch remote accounts {:?}: {}", pubkeys, e))).ok();
521 }
522 };
523 }
524 }
525 SimnetCommand::AirdropProcessed => {
526 if block_production_mode.eq(&BlockProductionMode::Transaction) {
527 do_produce_block = true;
528 }
529 }
530 }
531 },
532 }
533
534 {
535 if do_produce_block {
536 svm_locker
537 .confirm_current_block(&remote_client_with_commitment)
538 .await?;
539 }
540 }
541 }
542 Ok(())
543}
544
545pub fn start_clock_runloop(
546 mut slot_time: u64,
547 simnet_events_tx: Option<Sender<SimnetEvent>>,
548) -> (Receiver<ClockEvent>, Sender<ClockCommand>) {
549 let (clock_event_tx, clock_event_rx) = unbounded::<ClockEvent>();
550 let (clock_command_tx, clock_command_rx) = unbounded::<ClockCommand>();
551
552 let _handle = hiro_system_kit::thread_named("clock").spawn(move || {
553 let mut enabled = true;
554 let mut block_hash_timeout = Instant::now();
555
556 loop {
557 match clock_command_rx.try_recv() {
558 Ok(ClockCommand::Pause) => {
559 enabled = false;
560 if let Some(ref simnet_events_tx) = simnet_events_tx {
561 let _ =
562 simnet_events_tx.send(SimnetEvent::ClockUpdate(ClockCommand::Pause));
563 }
564 }
565 Ok(ClockCommand::Resume) => {
566 enabled = true;
567 if let Some(ref simnet_events_tx) = simnet_events_tx {
568 let _ =
569 simnet_events_tx.send(SimnetEvent::ClockUpdate(ClockCommand::Resume));
570 }
571 }
572 Ok(ClockCommand::Toggle) => {
573 enabled = !enabled;
574 if let Some(ref simnet_events_tx) = simnet_events_tx {
575 let _ =
576 simnet_events_tx.send(SimnetEvent::ClockUpdate(ClockCommand::Toggle));
577 }
578 }
579 Ok(ClockCommand::UpdateSlotInterval(updated_slot_time)) => {
580 slot_time = updated_slot_time;
581 }
582 Ok(ClockCommand::PauseWithConfirmation(_)) => {
583 enabled = false;
586 if let Some(ref simnet_events_tx) = simnet_events_tx {
587 let _ =
588 simnet_events_tx.send(SimnetEvent::ClockUpdate(ClockCommand::Pause));
589 }
590 }
591 Err(_e) => {}
592 }
593 sleep(Duration::from_millis(slot_time));
594 if enabled {
595 let _ = clock_event_tx.send(ClockEvent::Tick);
596 if block_hash_timeout.elapsed()
598 > Duration::from_millis(BLOCKHASH_SLOT_TTL * slot_time)
599 {
600 let _ = clock_event_tx.send(ClockEvent::ExpireBlockHash);
601 block_hash_timeout = Instant::now();
602 }
603 }
604 }
605 });
606
607 (clock_event_rx, clock_command_tx)
608}
609
610fn start_geyser_runloop(
611 plugin_config_paths: Vec<PathBuf>,
612 simnet_events_tx: Sender<SimnetEvent>,
613 geyser_events_rx: Receiver<GeyserEvent>,
614 plugin_commands_rx: Receiver<PluginCommand>,
615) -> Result<JoinHandle<Result<(), String>>, String> {
616 let handle: JoinHandle<Result<(), String>> = hiro_system_kit::thread_named("Geyser Plugins Handler").spawn(move || {
617 let mut indexing_enabled = false;
618
619 #[cfg(feature = "geyser_plugin")]
620 let mut plugin_manager = GeyserPluginManager::new();
621 #[cfg(not(feature = "geyser_plugin"))]
622 let mut plugin_manager = ();
623
624 let mut managed_plugins: Vec<ManagedPlugin> = vec![];
625
626 let log_error = |msg:String|{
628 let _ = simnet_events_tx.send(SimnetEvent::error(msg));
629 };
630
631 let log_warn = |msg:String|{
632 let _ = simnet_events_tx.send(SimnetEvent::warn(msg));
633 };
634
635 let log_info = |msg:String|{
636 let _ = simnet_events_tx.send(SimnetEvent::info(msg));
637 };
638
639 for plugin_config_path in plugin_config_paths.iter() {
641 let config_file = plugin_config_path.to_string_lossy().to_string();
642 match load_plugin_from_config(&config_file, false) {
643 Ok(managed) => {
644 indexing_enabled = true;
645 log_info(format!("Loaded geyser plugin '{}'", managed.name));
646 managed_plugins.push(managed);
647 }
648 Err(e) => {
649 log_error(format!("Failed to load geyser plugin from '{}': {}", config_file, e));
650 }
651 }
652 }
653
654 #[cfg(feature = "geyser_plugin")]
655 for plugin_config_path in plugin_config_paths.into_iter() {
656 let plugin_manifest_location = FileLocation::from_path(plugin_config_path);
657 let config_file = plugin_manifest_location.read_content_as_utf8()?;
658 let result: serde_json::Value = match json5::from_str(&config_file) {
659 Ok(res) => res,
660 Err(e) => {
661 let error = format!("Unable to read manifest: {}", e);
662 let _ = simnet_events_tx.send(SimnetEvent::error(error.clone()));
663 return Err(error)
664 }
665 };
666
667 let plugin_dylib_path = match result.get("libpath").map(|p| p.as_str()) {
668 Some(Some(name)) => name,
669 _ => {
670 let error = format!("Plugin config file should include a 'libpath' field: {}", plugin_manifest_location);
671 let _ = simnet_events_tx.send(SimnetEvent::error(error.clone()));
672 return Err(error)
673 }
674 };
675
676 let mut plugin_dylib_location = plugin_manifest_location.get_parent_location().expect("path invalid");
677 plugin_dylib_location.append_path(&plugin_dylib_path).expect("path invalid");
678
679 let (plugin, lib) = unsafe {
680 let lib = match Library::new(&plugin_dylib_location.to_string()) {
681 Ok(lib) => lib,
682 Err(e) => {
683 log_error(format!("Unable to load plugin {}: {}", plugin_dylib_location.to_string(), e.to_string()));
684 continue;
685 }
686 };
687 let constructor: Symbol<PluginConstructor> = lib
688 .get(b"_create_plugin")
689 .map_err(|e| format!("{}", e.to_string()))?;
690 let plugin_raw = constructor();
691 (Box::from_raw(plugin_raw), lib)
692 };
693 indexing_enabled = true;
694
695 let mut plugin = LoadedGeyserPlugin::new(lib, plugin, None);
696 if let Err(e) = plugin.on_load(&plugin_manifest_location.to_string(), false) {
697 let error = format!("Unable to load plugin:: {}", e.to_string());
698 let _ = simnet_events_tx.send(SimnetEvent::error(error.clone()));
699 return Err(error)
700 }
701
702 plugin_manager.plugins.push(plugin);
703 }
704
705 let err = loop {
706 use agave_geyser_plugin_interface::geyser_plugin_interface::{ReplicaAccountInfoV3, ReplicaAccountInfoVersions};
707
708 use crate::types::GeyserAccountUpdate;
709
710 select! {
711 recv(geyser_events_rx) -> msg => match msg {
712 Err(e) => {
713 break format!("Failed to read new transaction to send to Geyser plugin: {e}");
714 },
715 Ok(GeyserEvent::NotifyTransaction(transaction_with_status_meta, versioned_transaction)) => {
716
717 if !indexing_enabled {
718 continue;
719 }
720
721 let transaction = match versioned_transaction {
722 Some(tx) => tx,
723 None => {
724 log_warn("Unable to index sanitized transaction".to_string());
725 continue;
726 }
727 };
728
729 let transaction_replica = ReplicaTransactionInfoV3 {
730 signature: &transaction.signatures[0],
731 is_vote: false,
732 transaction: &transaction,
733 transaction_status_meta: &transaction_with_status_meta.meta,
734 index: 0,
735 message_hash: &transaction.message.hash(),
736 };
737
738 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
739 if let Err(e) = plugin.notify_transaction(ReplicaTransactionInfoVersions::V0_0_3(&transaction_replica), transaction_with_status_meta.slot) {
740 log_error(format!("Failed to notify Geyser plugin of new transaction: {:?}", e))
741 };
742 }
743
744 #[cfg(feature = "geyser_plugin")]
745 for plugin in plugin_manager.plugins.iter() {
746 if let Err(e) = plugin.notify_transaction(ReplicaTransactionInfoVersions::V0_0_3(&transaction_replica), transaction_with_status_meta.slot) {
747 log_error(format!("Failed to notify Geyser plugin of new transaction: {:?}", e))
748 };
749 }
750 }
751 Ok(GeyserEvent::UpdateAccount(account_update)) => {
752 let GeyserAccountUpdate {
753 pubkey,
754 account,
755 slot,
756 sanitized_transaction,
757 write_version,
758 } = account_update;
759
760 let account_replica = ReplicaAccountInfoV3 {
761 pubkey: pubkey.as_ref(),
762 lamports: account.lamports,
763 owner: account.owner.as_ref(),
764 executable: account.executable,
765 rent_epoch: account.rent_epoch,
766 data: account.data.as_ref(),
767 write_version,
768 txn: sanitized_transaction.as_ref(),
769 };
770
771 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
772 if let Err(e) = plugin.update_account(ReplicaAccountInfoVersions::V0_0_3(&account_replica), slot, false) {
773 log_error(format!("Failed to update account in Geyser plugin: {:?}", e));
774 }
775 }
776
777 #[cfg(feature = "geyser_plugin")]
778 for plugin in plugin_manager.plugins.iter() {
779 if let Err(e) = plugin.update_account(ReplicaAccountInfoVersions::V0_0_3(&account_replica), slot, false) {
780 log_error(format!("Failed to update account in Geyser plugin: {:?}", e))
781 }
782 }
783 }
784 Ok(GeyserEvent::StartupAccountUpdate(account_update)) => {
785 let GeyserAccountUpdate {
786 pubkey,
787 account,
788 slot,
789 sanitized_transaction,
790 write_version,
791 } = account_update;
792
793 let account_replica = ReplicaAccountInfoV3 {
794 pubkey: pubkey.as_ref(),
795 lamports: account.lamports,
796 owner: account.owner.as_ref(),
797 executable: account.executable,
798 rent_epoch: account.rent_epoch,
799 data: account.data.as_ref(),
800 write_version,
801 txn: sanitized_transaction.as_ref(),
802 };
803
804 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
806 if let Err(e) = plugin.update_account(ReplicaAccountInfoVersions::V0_0_3(&account_replica), slot, true) {
807 log_error(format!("Failed to send startup account update to Geyser plugin: {:?}", e));
808 }
809 }
810
811 #[cfg(feature = "geyser_plugin")]
812 for plugin in plugin_manager.plugins.iter() {
813 if let Err(e) = plugin.update_account(ReplicaAccountInfoVersions::V0_0_3(&account_replica), slot, true) {
814 log_error(format!("Failed to send startup account update to Geyser plugin: {:?}", e))
815 }
816 }
817 }
818 Ok(GeyserEvent::EndOfStartup) => {
819 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
820 if let Err(e) = plugin.notify_end_of_startup() {
821 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify end of startup to Geyser plugin: {:?}", e)));
822 }
823 }
824
825 #[cfg(feature = "geyser_plugin")]
826 for plugin in plugin_manager.plugins.iter() {
827 if let Err(e) = plugin.notify_end_of_startup() {
828 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify end of startup to Geyser plugin: {:?}", e)));
829 }
830 }
831 }
832 Ok(GeyserEvent::UpdateSlotStatus { slot, parent, status }) => {
833 let slot_status = match status {
834 crate::surfnet::GeyserSlotStatus::Processed => SlotStatus::Processed,
835 crate::surfnet::GeyserSlotStatus::Confirmed => SlotStatus::Confirmed,
836 crate::surfnet::GeyserSlotStatus::Rooted => SlotStatus::Rooted,
837 };
838
839 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
840 if let Err(e) = plugin.update_slot_status(slot, parent, &slot_status) {
841 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to update slot status in Geyser plugin: {:?}", e)));
842 }
843 }
844
845 #[cfg(feature = "geyser_plugin")]
846 for plugin in plugin_manager.plugins.iter() {
847 if let Err(e) = plugin.update_slot_status(slot, parent, &slot_status) {
848 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to update slot status in Geyser plugin: {:?}", e)));
849 }
850 }
851 }
852 Ok(GeyserEvent::NotifyBlockMetadata(block_metadata)) => {
853 let rewards = RewardsAndNumPartitions {
854 rewards: vec![],
855 num_partitions: None,
856 };
857
858 let block_info = ReplicaBlockInfoV4 {
859 slot: block_metadata.slot,
860 blockhash: &block_metadata.blockhash,
861 rewards: &rewards,
862 block_time: block_metadata.block_time,
863 block_height: block_metadata.block_height,
864 parent_slot: block_metadata.parent_slot,
865 parent_blockhash: &block_metadata.parent_blockhash,
866 executed_transaction_count: block_metadata.executed_transaction_count,
867 entry_count: block_metadata.entry_count,
868 };
869
870 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
871 if let Err(e) = plugin.notify_block_metadata(ReplicaBlockInfoVersions::V0_0_4(&block_info)) {
872 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify block metadata to Geyser plugin: {:?}", e)));
873 }
874 }
875
876 #[cfg(feature = "geyser_plugin")]
877 for plugin in plugin_manager.plugins.iter() {
878 if let Err(e) = plugin.notify_block_metadata(ReplicaBlockInfoVersions::V0_0_4(&block_info)) {
879 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify block metadata to Geyser plugin: {:?}", e)));
880 }
881 }
882 }
883 Ok(GeyserEvent::NotifyEntry(entry_info)) => {
884 let entry_replica = ReplicaEntryInfoV2 {
885 slot: entry_info.slot,
886 index: entry_info.index,
887 num_hashes: entry_info.num_hashes,
888 hash: &entry_info.hash,
889 executed_transaction_count: entry_info.executed_transaction_count,
890 starting_transaction_index: entry_info.starting_transaction_index,
891 };
892
893 for plugin in managed_plugins.iter().map(|p| &*p.plugin) {
894 if let Err(e) = plugin.notify_entry(ReplicaEntryInfoVersions::V0_0_2(&entry_replica)) {
895 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify entry to Geyser plugin: {:?}", e)));
896 }
897 }
898
899 #[cfg(feature = "geyser_plugin")]
900 for plugin in plugin_manager.plugins.iter() {
901 if let Err(e) = plugin.notify_entry(ReplicaEntryInfoVersions::V0_0_2(&entry_replica)) {
902 let _ = simnet_events_tx.send(SimnetEvent::error(format!("Failed to notify entry to Geyser plugin: {:?}", e)));
903 }
904 }
905 }
906 },
907 recv(plugin_commands_rx) -> cmd => match cmd {
908 Err(_) => {
909 break "Plugin command channel closed".to_string();
910 }
911 Ok(PluginCommand::Load { config_file, response_tx }) => {
912 let result = match load_plugin_from_config(&config_file, false) {
913 Ok(managed) => {
914 indexing_enabled = true;
915 let info = PluginInfo {
916 plugin_name: managed.name.clone(),
917 uuid: managed.uuid.to_string(),
918 };
919 log_info(format!("Loaded geyser plugin '{}'", managed.name));
920 managed_plugins.push(managed);
921 Ok(info)
922 }
923 Err(e) => {
924 log_error(format!("Failed to load plugin: {}", e));
925 Err(e)
926 }
927 };
928 let _ = response_tx.send(result);
929 }
930 Ok(PluginCommand::Unload { name, response_tx }) => {
931 let result = unload_plugin_by_name(&name, &mut managed_plugins);
932 if result.is_ok() {
933 log_info(format!("Unloaded geyser plugin '{}'", name));
934 }
935 let _ = response_tx.send(result);
936 }
937 Ok(PluginCommand::Reload { name, config_file, response_tx }) => {
938 let result = unload_plugin_by_name(&name, &mut managed_plugins)
939 .and_then(|_| {
940 let managed = load_plugin_from_config(&config_file, true)?;
941 log_info(format!("Reloaded geyser plugin '{}'", managed.name));
942 managed_plugins.push(managed);
943 Ok(())
944 });
945 if let Err(ref e) = result {
946 log_error(format!("Failed to reload plugin '{}': {}", name, e));
947 }
948 let _ = response_tx.send(result);
949 }
950 Ok(PluginCommand::List { response_tx }) => {
951 let infos = managed_plugins.iter().map(|p| PluginInfo {
952 plugin_name: p.name.clone(),
953 uuid: p.uuid.to_string(),
954 }).collect();
955 let _ = response_tx.send(infos);
956 }
957 }
958 }
959 };
960 Err(err)
961 }).map_err(|e| format!("Failed to spawn Geyser Plugins Handler thread: {:?}", e))?;
962 Ok(handle)
963}
964
965async fn start_rpc_servers_runloop(
966 config: &SurfpoolConfig,
967 simnet_commands_tx: &Sender<SimnetCommand>,
968 svm_locker: SurfnetSvmLocker,
969 remote_rpc_client: &Option<SurfnetRemoteClient>,
970 plugin_commands_tx: Sender<PluginCommand>,
971) -> Result<(JoinHandle<()>, JoinHandle<()>, Box<dyn FnOnce() + Send>), String> {
972 let rpc_addr: SocketAddr = config
973 .rpc
974 .get_rpc_base_url()
975 .parse()
976 .map_err(|e: std::net::AddrParseError| e.to_string())?;
977 let ws_addr: SocketAddr = config
978 .rpc
979 .get_ws_base_url()
980 .parse()
981 .map_err(|e: std::net::AddrParseError| e.to_string())?;
982
983 check_port_availability(rpc_addr, "RPC")?;
984 check_port_availability(ws_addr, "WebSocket")?;
985
986 let simnet_events_tx = svm_locker.simnet_events_tx();
987
988 let middleware = SurfpoolMiddleware::new(
989 svm_locker,
990 simnet_commands_tx,
991 &config.rpc,
992 remote_rpc_client,
993 plugin_commands_tx,
994 );
995
996 let (rpc_handle, rpc_close_handle) =
997 start_http_rpc_server_runloop(config, middleware.clone(), simnet_events_tx.clone()).await?;
998 let (ws_handle, ws_close_handle) =
999 start_ws_rpc_server_runloop(config, middleware, simnet_events_tx).await?;
1000
1001 let shutdown_rpc_servers: Box<dyn FnOnce() + Send> = Box::new(move || {
1002 rpc_close_handle.close();
1003 ws_close_handle.close();
1004 });
1005
1006 Ok((rpc_handle, ws_handle, shutdown_rpc_servers))
1007}
1008
1009async fn start_http_rpc_server_runloop(
1010 config: &SurfpoolConfig,
1011 middleware: SurfpoolMiddleware,
1012 simnet_events_tx: Sender<SimnetEvent>,
1013) -> Result<(JoinHandle<()>, jsonrpc_http_server::CloseHandle), String> {
1014 let server_bind: SocketAddr = config
1015 .rpc
1016 .get_rpc_base_url()
1017 .parse::<SocketAddr>()
1018 .map_err(|e| e.to_string())?;
1019
1020 let mut io = MetaIoHandler::with_middleware(middleware);
1021
1022 {
1026 let cheatcode_methods: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(vec![]));
1027 let mut cheatcodes_impl = rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc {
1028 registered_methods: Arc::clone(&cheatcode_methods),
1029 };
1030 io.extend_with(cheatcodes_impl.to_delegate());
1031
1032 cheatcode_methods
1033 .write()
1034 .unwrap()
1035 .extend(io.iter().map(|(n, _)| n.clone()).collect::<Vec<String>>());
1036 }
1037
1038 io.extend_with(rpc::minimal::SurfpoolMinimalRpc.to_delegate());
1039 io.extend_with(rpc::full::SurfpoolFullRpc.to_delegate());
1040 io.extend_with(rpc::jito::SurfpoolJitoRpc.to_delegate());
1041 io.extend_with(rpc::accounts_data::SurfpoolAccountsDataRpc.to_delegate());
1042 io.extend_with(rpc::accounts_scan::SurfpoolAccountsScanRpc.to_delegate());
1043 io.extend_with(rpc::bank_data::SurfpoolBankDataRpc.to_delegate());
1044 io.extend_with(rpc::admin::SurfpoolAdminRpc.to_delegate());
1045
1046 let (close_handle_tx, close_handle_rx) =
1047 crossbeam_channel::bounded::<Result<jsonrpc_http_server::CloseHandle, String>>(1);
1048
1049 let _handle = hiro_system_kit::thread_named("RPC Handler")
1050 .spawn(move || {
1051 let server = match ServerBuilder::new(io)
1052 .cors(DomainsValidation::Disabled)
1053 .threads(6)
1054 .max_request_body_size(15 * 1024 * 1024)
1055 .start_http(&server_bind)
1056 {
1057 Ok(server) => server,
1058 Err(e) => {
1059 let error = format!("Failed to start RPC server: {:?}", e);
1060 let _ = close_handle_tx.send(Err(error.clone()));
1061 let _ = simnet_events_tx.send(SimnetEvent::Aborted(error));
1062 return;
1063 }
1064 };
1065
1066 let _ = close_handle_tx.send(Ok(server.close_handle()));
1067 server.wait();
1068 let _ = simnet_events_tx.send(SimnetEvent::Shutdown);
1069 })
1070 .map_err(|e| format!("Failed to spawn RPC Handler thread: {:?}", e))?;
1071
1072 let close_handle = close_handle_rx
1073 .recv()
1074 .map_err(|_| "Failed to receive HTTP RPC server startup result".to_string())??;
1075
1076 Ok((_handle, close_handle))
1077}
1078async fn start_ws_rpc_server_runloop(
1079 config: &SurfpoolConfig,
1080 middleware: SurfpoolMiddleware,
1081 simnet_events_tx: Sender<SimnetEvent>,
1082) -> Result<(JoinHandle<()>, jsonrpc_ws_server::CloseHandle), String> {
1083 let ws_server_bind: SocketAddr = config
1084 .rpc
1085 .get_ws_base_url()
1086 .parse::<SocketAddr>()
1087 .map_err(|e| e.to_string())?;
1088
1089 let uid = std::sync::atomic::AtomicUsize::new(0);
1090 let ws_middleware = SurfpoolWebsocketMiddleware::new(middleware.clone(), None);
1091
1092 let mut rpc_io = PubSubHandler::new(MetaIoHandler::with_middleware(ws_middleware));
1093
1094 let (close_handle_tx, close_handle_rx) =
1095 crossbeam_channel::bounded::<Result<jsonrpc_ws_server::CloseHandle, String>>(1);
1096
1097 let _ws_handle = hiro_system_kit::thread_named("WebSocket RPC Handler")
1098 .spawn(move || {
1099 let runtime = tokio::runtime::Builder::new_multi_thread()
1101 .enable_all()
1102 .build()
1103 .expect("Failed to build Tokio runtime");
1104
1105 let tokio_handle = runtime.handle();
1106 rpc_io.extend_with(
1107 rpc::ws::SurfpoolWsRpc {
1108 uid,
1109 signature_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1110 account_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1111 program_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1112 slot_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1113 logs_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1114 snapshot_subscription_map: Arc::new(RwLock::new(HashMap::new())),
1115 tokio_handle: tokio_handle.clone(),
1116 }
1117 .to_delegate(),
1118 );
1119 runtime.block_on(async move {
1120 let server = match WsServerBuilder::new(rpc_io)
1121 .session_meta_extractor(move |ctx: &RequestContext| {
1122 let runloop_context = RunloopContext {
1124 id: None,
1125 svm_locker: middleware.surfnet_svm.clone(),
1126 simnet_commands_tx: middleware.simnet_commands_tx.clone(),
1127 remote_rpc_client: middleware.remote_rpc_client.clone(),
1128 rpc_config: middleware.config.clone(),
1129 cheatcode_config: middleware.cheatcode_config.clone(),
1130 plugin_commands_tx: middleware.plugin_commands_tx.clone(),
1131 };
1132 Some(SurfpoolWebsocketMeta::new(
1133 runloop_context,
1134 Some(Arc::new(Session::new(ctx.sender()))),
1135 ))
1136 })
1137 .start(&ws_server_bind)
1138 {
1139 Ok(server) => server,
1140 Err(e) => {
1141 let error = format!("Failed to start WebSocket RPC server: {:?}", e);
1142 let _ = close_handle_tx.send(Err(error.clone()));
1143 let _ = simnet_events_tx.send(SimnetEvent::Aborted(error));
1144 return;
1145 }
1146 };
1147 let _ = close_handle_tx.send(Ok(server.close_handle()));
1148 tokio::task::spawn_blocking(move || {
1150 server.wait().unwrap();
1151 })
1152 .await
1153 .ok();
1154
1155 let _ = simnet_events_tx.send(SimnetEvent::Shutdown);
1156 });
1157 })
1158 .map_err(|e| format!("Failed to spawn WebSocket RPC Handler thread: {:?}", e))?;
1159
1160 let close_handle = close_handle_rx
1161 .recv()
1162 .map_err(|_| "Failed to receive WebSocket RPC server startup result".to_string())??;
1163
1164 Ok((_ws_handle, close_handle))
1165}