1use std::{
2 future::Future,
3 sync::{Arc, Mutex},
4};
5
6use blake3::Hash;
7use crossbeam_channel::Sender;
8use jsonrpc_core::{
9 BoxFuture, Error, ErrorCode, FutureResponse, Metadata, Middleware, Request, Response,
10 futures::{FutureExt, future::Either},
11 middleware,
12};
13use jsonrpc_pubsub::{PubSubMetadata, Session};
14use solana_clock::Slot;
15use surfpool_types::{CheatcodeConfig, SimnetCommand, SimnetEvent, types::RpcConfig};
16
17use crate::{
18 error::{SurfpoolError, SurfpoolResult},
19 surfnet::{
20 PluginCommand,
21 locker::SurfnetSvmLocker,
22 remote::{SomeRemoteCtx, SurfnetRemoteClient},
23 svm::SurfnetSvm,
24 },
25};
26
27pub mod accounts_data;
28pub mod accounts_scan;
29pub mod admin;
30pub mod bank_data;
31pub mod full;
32pub mod jito;
33pub mod minimal;
34pub mod surfnet_cheatcodes;
35pub mod utils;
36pub mod ws;
37
38#[derive(PartialEq, Eq, Clone, Copy, Debug)]
39pub enum RpcHealthStatus {
40 Ok,
41 Behind { num_slots: Slot },
42 Unknown,
43}
44
45pub struct SurfpoolRpc;
46
47#[derive(Clone)]
48pub struct RunloopContext {
49 pub id: Option<(Hash, String)>,
50 pub svm_locker: SurfnetSvmLocker,
51 pub simnet_commands_tx: Sender<SimnetCommand>,
52 pub remote_rpc_client: Option<SurfnetRemoteClient>,
53 pub rpc_config: RpcConfig,
54 pub cheatcode_config: Arc<Mutex<CheatcodeConfig>>,
55 pub plugin_commands_tx: Sender<PluginCommand>,
56}
57
58pub struct SurfnetRpcContext<T> {
59 pub svm_locker: SurfnetSvmLocker,
60 pub remote_ctx: Option<(SurfnetRemoteClient, T)>,
61}
62
63trait State {
64 fn get_svm_locker(&self) -> SurfpoolResult<SurfnetSvmLocker>;
65 fn with_svm_reader<T, F>(&self, reader: F) -> Result<T, SurfpoolError>
66 where
67 F: Fn(&SurfnetSvm) -> T + Send + Sync,
68 T: Send + 'static;
69 fn get_rpc_context<T>(&self, input: T) -> SurfpoolResult<SurfnetRpcContext<T>>;
70 fn get_surfnet_command_tx(&self) -> SurfpoolResult<Sender<SimnetCommand>>;
71}
72
73impl State for Option<RunloopContext> {
74 fn get_svm_locker(&self) -> SurfpoolResult<SurfnetSvmLocker> {
75 let Some(ctx) = self else {
77 return Err(SurfpoolError::missing_context());
78 };
79 Ok(ctx.svm_locker.clone())
80 }
81
82 fn with_svm_reader<T, F>(&self, reader: F) -> Result<T, SurfpoolError>
83 where
84 F: Fn(&SurfnetSvm) -> T + Send + Sync,
85 T: Send + 'static,
86 {
87 let Some(ctx) = self else {
88 return Err(SurfpoolError::missing_context());
89 };
90 Ok(ctx.svm_locker.with_svm_reader(reader))
91 }
92
93 fn get_rpc_context<T>(&self, input: T) -> SurfpoolResult<SurfnetRpcContext<T>> {
94 let Some(ctx) = self else {
95 return Err(SurfpoolError::missing_context());
96 };
97
98 Ok(SurfnetRpcContext {
99 svm_locker: ctx.svm_locker.clone(),
100 remote_ctx: ctx.remote_rpc_client.get_remote_ctx(input),
101 })
102 }
103
104 fn get_surfnet_command_tx(&self) -> SurfpoolResult<Sender<SimnetCommand>> {
105 let Some(ctx) = self else {
106 return Err(SurfpoolError::missing_context());
107 };
108 Ok(ctx.simnet_commands_tx.clone())
109 }
110}
111
112impl Metadata for RunloopContext {}
113
114#[derive(Clone)]
115pub struct SurfpoolMiddleware {
116 pub surfnet_svm: SurfnetSvmLocker,
117 pub simnet_commands_tx: Sender<SimnetCommand>,
118 pub config: RpcConfig,
119 pub remote_rpc_client: Option<SurfnetRemoteClient>,
120 pub cheatcode_config: Arc<Mutex<CheatcodeConfig>>,
121 pub plugin_commands_tx: Sender<PluginCommand>,
122}
123
124impl SurfpoolMiddleware {
125 pub fn new(
126 surfnet_svm: SurfnetSvmLocker,
127 simnet_commands_tx: &Sender<SimnetCommand>,
128 config: &RpcConfig,
129 remote_rpc_client: &Option<SurfnetRemoteClient>,
130 plugin_commands_tx: Sender<PluginCommand>,
131 ) -> Self {
132 Self {
133 surfnet_svm,
134 simnet_commands_tx: simnet_commands_tx.clone(),
135 config: config.clone(),
136 remote_rpc_client: remote_rpc_client.clone(),
137 cheatcode_config: CheatcodeConfig::new(),
138 plugin_commands_tx,
139 }
140 }
141}
142
143impl Middleware<Option<RunloopContext>> for SurfpoolMiddleware {
144 type Future = FutureResponse;
145 type CallFuture = middleware::NoopCallFuture;
146
147 fn on_request<F, X>(
148 &self,
149 request: Request,
150 _meta: Option<RunloopContext>,
151 next: F,
152 ) -> Either<Self::Future, X>
153 where
154 F: FnOnce(Request, Option<RunloopContext>) -> X + Send,
155 X: Future<Output = Option<Response>> + Send + 'static,
156 {
157 let Request::Single(jsonrpc_core::Call::MethodCall(ref method_call)) = request else {
158 let error = Response::from(
159 Error {
160 code: ErrorCode::InvalidRequest,
161 message: "Only method calls are supported".into(),
162 data: None,
163 },
164 None,
165 );
166 warn!("Request rejected due to not being a single method call");
167
168 return Either::Left(Box::pin(async move { Some(error) }));
169 };
170
171 let method_name = method_call.method.clone();
172 debug!("Processing request '{}'", method_name);
173
174 let meta = Some(RunloopContext {
175 id: None,
176 svm_locker: self.surfnet_svm.clone(),
177 simnet_commands_tx: self.simnet_commands_tx.clone(),
178 remote_rpc_client: self.remote_rpc_client.clone(),
179 rpc_config: self.config.clone(),
180 cheatcode_config: self.cheatcode_config.clone(),
181 plugin_commands_tx: self.plugin_commands_tx.clone(),
182 });
183
184 if method_name.starts_with("surfnet_")
186 && let Some(meta_val) = meta.clone()
187 {
188 let Ok(meta_val) = meta_val.cheatcode_config.lock() else {
189 let error = Response::from(
190 Error {
191 code: ErrorCode::InternalError,
192 message: "An internal server error occured".to_string(),
193 data: None,
194 },
195 None,
196 );
197 warn!("Request rejected due to cheatcode being disabled");
198
199 return Either::Left(Box::pin(async move { Some(error) }));
200 };
201 if meta_val.is_cheatcode_disabled(&method_name) {
202 let error = Response::from(
203 Error {
204 code: ErrorCode::InvalidRequest,
205 message: format!(
206 "Cheatcode rpc method: {method_name} is currently disabled"
207 ),
208 data: None,
209 },
210 None,
211 );
212 warn!("Request rejected due to cheatcode rpc method being disabled");
213
214 return Either::Left(Box::pin(async move { Some(error) }));
215 }
216 }
217
218 Either::Left(Box::pin(next(request, meta).map(move |res| {
219 if let Some(Response::Single(output)) = &res {
220 if let jsonrpc_core::Output::Failure(failure) = output {
221 debug!(
222 "RPC error for method '{}': code={:?}, message={}",
223 method_name, failure.error.code, failure.error.message
224 );
225 }
226 }
227 res
228 })))
229 }
230}
231
232#[derive(Clone)]
233pub struct SurfpoolWebsocketMiddleware {
234 pub surfpool_middleware: SurfpoolMiddleware,
235 pub session: Option<Arc<Session>>,
236}
237
238impl SurfpoolWebsocketMiddleware {
239 pub fn new(surfpool_middleware: SurfpoolMiddleware, session: Option<Arc<Session>>) -> Self {
240 Self {
241 surfpool_middleware,
242 session,
243 }
244 }
245}
246
247impl Middleware<Option<SurfpoolWebsocketMeta>> for SurfpoolWebsocketMiddleware {
248 type Future = FutureResponse;
249 type CallFuture = middleware::NoopCallFuture;
250
251 fn on_request<F, X>(
252 &self,
253 request: Request,
254 meta: Option<SurfpoolWebsocketMeta>,
255 next: F,
256 ) -> Either<Self::Future, X>
257 where
258 F: FnOnce(Request, Option<SurfpoolWebsocketMeta>) -> X + Send,
259 X: Future<Output = Option<Response>> + Send + 'static,
260 {
261 let runloop_context = RunloopContext {
262 id: None,
263 svm_locker: self.surfpool_middleware.surfnet_svm.clone(),
264 simnet_commands_tx: self.surfpool_middleware.simnet_commands_tx.clone(),
265 remote_rpc_client: self.surfpool_middleware.remote_rpc_client.clone(),
266 rpc_config: self.surfpool_middleware.config.clone(),
267 cheatcode_config: self.surfpool_middleware.cheatcode_config.clone(),
268 plugin_commands_tx: self.surfpool_middleware.plugin_commands_tx.clone(),
269 };
270 let session = meta
271 .as_ref()
272 .and_then(|m| m.session.clone())
273 .or(self.session.clone());
274 let meta = Some(SurfpoolWebsocketMeta::new(runloop_context, session));
275 Either::Left(Box::pin(next(request, meta).map(move |res| res)))
276 }
277}
278
279#[derive(Clone)]
280pub struct SurfpoolWebsocketMeta {
281 pub runloop_context: RunloopContext,
282 pub session: Option<Arc<Session>>,
283}
284
285impl SurfpoolWebsocketMeta {
286 pub fn new(runloop_context: RunloopContext, session: Option<Arc<Session>>) -> Self {
287 Self {
288 runloop_context,
289 session,
290 }
291 }
292
293 pub fn log_debug(&self, msg: &str) {
294 let _ = self
295 .runloop_context
296 .svm_locker
297 .simnet_events_tx()
298 .send(SimnetEvent::debug(msg));
299 }
300
301 pub fn log_warn(&self, msg: &str) {
302 let _ = self
303 .runloop_context
304 .svm_locker
305 .simnet_events_tx()
306 .send(SimnetEvent::warn(msg));
307 }
308}
309
310impl State for Option<SurfpoolWebsocketMeta> {
311 fn get_svm_locker(&self) -> SurfpoolResult<SurfnetSvmLocker> {
312 let Some(ctx) = self else {
313 return Err(SurfpoolError::missing_context());
314 };
315 Ok(ctx.runloop_context.svm_locker.clone())
316 }
317
318 fn with_svm_reader<T, F>(&self, reader: F) -> Result<T, SurfpoolError>
319 where
320 F: Fn(&SurfnetSvm) -> T + Send + Sync,
321 T: Send + 'static,
322 {
323 let Some(ctx) = self else {
324 return Err(SurfpoolError::missing_context());
325 };
326 Ok(ctx.runloop_context.svm_locker.with_svm_reader(reader))
327 }
328
329 fn get_rpc_context<T>(&self, input: T) -> SurfpoolResult<SurfnetRpcContext<T>> {
330 let Some(ctx) = self else {
331 return Err(SurfpoolError::missing_context());
332 };
333
334 Ok(SurfnetRpcContext {
335 svm_locker: ctx.runloop_context.svm_locker.clone(),
336 remote_ctx: ctx.runloop_context.remote_rpc_client.get_remote_ctx(input),
337 })
338 }
339
340 fn get_surfnet_command_tx(&self) -> SurfpoolResult<Sender<SimnetCommand>> {
341 let Some(ctx) = self else {
342 return Err(SurfpoolError::missing_context());
343 };
344 Ok(ctx.runloop_context.simnet_commands_tx.clone())
345 }
346}
347
348impl Metadata for SurfpoolWebsocketMeta {}
349impl PubSubMetadata for SurfpoolWebsocketMeta {
350 fn session(&self) -> Option<Arc<jsonrpc_pubsub::Session>> {
351 self.session.clone()
352 }
353}
354
355pub const NOT_IMPLEMENTED_CODE: i64 = -32051; pub const NOT_IMPLEMENTED_MSG: &str = "Method not yet implemented. If this endpoint is a priority for you, please open an issue here so we can prioritize: https://github.com/solana-foundation/surfpool/issues";
357fn not_implemented_msg(method: &str) -> String {
358 format!(
359 "Method `{}` is not yet implemented. If this endpoint is a priority for you, please open an issue here so we can prioritize: https://github.com/solana-foundation/surfpool/issues",
360 method
361 )
362}
363pub fn not_implemented_err<T>(method: &str) -> Result<T, Error> {
365 Err(Error {
366 code: jsonrpc_core::types::ErrorCode::ServerError(NOT_IMPLEMENTED_CODE),
367 message: not_implemented_msg(method),
368 data: None,
369 })
370}
371
372pub fn not_implemented_err_async<T>(method: &str) -> BoxFuture<Result<T, Error>> {
373 let method = method.to_string();
374 Box::pin(async move {
375 Err(Error {
376 code: jsonrpc_core::types::ErrorCode::ServerError(NOT_IMPLEMENTED_CODE),
377 message: not_implemented_msg(&method),
378 data: None,
379 })
380 })
381}