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