Skip to main content

surfpool_core/rpc/
ws.rs

1use std::{
2    collections::HashMap,
3    str::FromStr,
4    sync::{Arc, RwLock, atomic},
5};
6
7use crossbeam_channel::TryRecvError;
8use jsonrpc_core::{Error, ErrorCode, Result};
9use jsonrpc_derive::rpc;
10use jsonrpc_pubsub::{
11    SubscriptionId,
12    typed::{Sink, Subscriber},
13};
14use solana_account_decoder::{UiAccount, UiAccountEncoding};
15use solana_client::{
16    rpc_config::{RpcSignatureSubscribeConfig, RpcTransactionConfig, RpcTransactionLogsFilter},
17    rpc_filter::RpcFilterType,
18    rpc_response::{
19        ProcessedSignatureResult, ReceivedSignatureResult, RpcKeyedAccount, RpcLogsResponse,
20        RpcResponseContext, RpcSignatureResult,
21    },
22};
23use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
24use solana_pubkey::Pubkey;
25use solana_rpc_client_api::response::{Response as RpcResponse, SlotInfo, SlotUpdate};
26use solana_signature::Signature;
27use solana_transaction_status::{TransactionConfirmationStatus, UiTransactionEncoding};
28
29use super::{State, SurfnetRpcContext, SurfpoolWebsocketMeta};
30use crate::{
31    rpc::utils::MAX_SUPPORTED_TRANSACTION_VERSION,
32    surfnet::{GetTransactionResult, SignatureSubscriptionType},
33};
34
35/// Configuration for account subscription requests.
36///
37/// This struct defines the parameters that clients can specify when subscribing
38/// to account change notifications through WebSocket connections. It allows customization
39/// of both the commitment level for updates and the encoding format for account data.
40///
41/// ## Fields
42/// - `commitment`: Optional commitment configuration specifying when to send notifications
43///   (processed, confirmed, or finalized). Defaults to the node's default commitment level.
44/// - `encoding`: Optional encoding format for account data serialization (base58, base64, jsonParsed, etc.).
45///   Defaults to base58 encoding if not specified.
46///
47/// ## Usage
48/// Clients can provide this configuration to customize their subscription behavior:
49/// - Set commitment level to control notification timing based on confirmation status
50/// - Set encoding to specify the preferred format for receiving account data
51///
52/// ## Example Usage
53/// ```json
54/// {
55///   "commitment": "confirmed",
56///   "encoding": "base64"
57/// }
58/// ```
59#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct RpcAccountSubscribeConfig {
62    #[serde(flatten)]
63    pub commitment: Option<CommitmentConfig>,
64    pub encoding: Option<UiAccountEncoding>,
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
68#[serde(rename_all = "camelCase")]
69pub struct RpcProgramSubscribeConfig {
70    #[serde(flatten)]
71    pub commitment: Option<CommitmentConfig>,
72    pub encoding: Option<UiAccountEncoding>,
73    pub filters: Option<Vec<RpcFilterType>>,
74}
75
76#[rpc]
77pub trait Rpc {
78    type Metadata;
79
80    /// Subscribe to signature status notifications via WebSocket.
81    ///
82    /// This method allows clients to subscribe to status updates for a specific transaction signature.
83    /// The subscriber will receive notifications when the transaction reaches the desired confirmation level
84    /// or when it's initially received by the network (if configured).
85    ///
86    /// ## Parameters
87    /// - `meta`: WebSocket metadata containing RPC context and connection information.
88    /// - `subscriber`: The subscription sink for sending signature status notifications to the client.
89    /// - `signature_str`: The transaction signature to monitor, as a base-58 encoded string.
90    /// - `config`: Optional configuration specifying commitment level and notification preferences.
91    ///
92    /// ## Returns
93    /// This method does not return a value directly. Instead, it establishes a WebSocket subscription
94    /// that will send `RpcResponse<RpcSignatureResult>` notifications to the subscriber when the
95    /// transaction status changes.
96    ///
97    /// ## Example WebSocket Request
98    /// ```json
99    /// {
100    ///   "jsonrpc": "2.0",
101    ///   "id": 1,
102    ///   "method": "signatureSubscribe",
103    ///   "params": [
104    ///     "2id3YC2jK9G5Wo2phDx4gJVAew8DcY5NAojnVuao8rkxwPYPe8cSwE5GzhEgJA2y8fVjDEo6iR6ykBvDxrTQrtpb",
105    ///     {
106    ///       "commitment": "finalized",
107    ///       "enableReceivedNotification": false
108    ///     }
109    ///   ]
110    /// }
111    /// ```
112    ///
113    /// ## Example WebSocket Response (Subscription Confirmation)
114    /// ```json
115    /// {
116    ///   "jsonrpc": "2.0",
117    ///   "result": 0,
118    ///   "id": 1
119    /// }
120    /// ```
121    ///
122    /// ## Example WebSocket Notification
123    /// ```json
124    /// {
125    ///   "jsonrpc": "2.0",
126    ///   "method": "signatureNotification",
127    ///   "params": {
128    ///     "result": {
129    ///       "context": {
130    ///         "slot": 5207624
131    ///       },
132    ///       "value": {
133    ///         "err": null
134    ///       }
135    ///     },
136    ///     "subscription": 0
137    ///   }
138    /// }
139    /// ```
140    ///
141    /// ## Notes
142    /// - If the transaction already exists with the desired confirmation status, the subscriber
143    ///   will be notified immediately and the subscription will complete.
144    /// - The subscription automatically terminates after sending the first matching notification.
145    /// - Invalid signature formats will cause the subscription to be rejected with an error.
146    /// - Each subscription runs in its own async task for optimal performance.
147    ///
148    /// ## See Also
149    /// - `signatureUnsubscribe`: Remove an active signature subscription
150    /// - `getSignatureStatuses`: Get current status of multiple signatures
151    #[pubsub(
152        subscription = "signatureNotification",
153        subscribe,
154        name = "signatureSubscribe"
155    )]
156    fn signature_subscribe(
157        &self,
158        meta: Self::Metadata,
159        subscriber: Subscriber<RpcResponse<RpcSignatureResult>>,
160        signature_str: String,
161        config: Option<RpcSignatureSubscribeConfig>,
162    );
163
164    /// Unsubscribe from signature status notifications.
165    ///
166    /// This method removes an active signature subscription, stopping further notifications
167    /// for the specified subscription ID.
168    ///
169    /// ## Parameters
170    /// - `meta`: Optional WebSocket metadata containing connection information.
171    /// - `subscription`: The subscription ID to remove, as returned by `signatureSubscribe`.
172    ///
173    /// ## Returns
174    /// A `Result<bool>` indicating whether the unsubscription was successful:
175    /// - `Ok(true)` if the subscription was successfully removed
176    /// - `Err(Error)` with `InvalidParams` if the subscription ID doesn't exist
177    ///
178    /// ## Example WebSocket Request
179    /// ```json
180    /// {
181    ///   "jsonrpc": "2.0",
182    ///   "id": 1,
183    ///   "method": "signatureUnsubscribe",
184    ///   "params": [0]
185    /// }
186    /// ```
187    ///
188    /// ## Example WebSocket Response
189    /// ```json
190    /// {
191    ///   "jsonrpc": "2.0",
192    ///   "result": true,
193    ///   "id": 1
194    /// }
195    /// ```
196    ///
197    /// ## Notes
198    /// - Attempting to unsubscribe from a non-existent subscription will return an error.
199    /// - Successfully unsubscribed connections will no longer receive notifications.
200    /// - This method is thread-safe and can be called concurrently.
201    ///
202    /// ## See Also
203    /// - `signatureSubscribe`: Create a signature status subscription
204    #[pubsub(
205        subscription = "signatureNotification",
206        unsubscribe,
207        name = "signatureUnsubscribe"
208    )]
209    fn signature_unsubscribe(
210        &self,
211        meta: Option<Self::Metadata>,
212        subscription: SubscriptionId,
213    ) -> Result<bool>;
214
215    /// Subscribe to account change notifications via WebSocket.
216    ///
217    /// This method allows clients to subscribe to updates for a specific account.
218    /// The subscriber will receive notifications whenever the account's data, lamports balance,
219    /// ownership, or other properties change.
220    ///
221    /// ## Parameters
222    /// - `meta`: WebSocket metadata containing RPC context and connection information.
223    /// - `subscriber`: The subscription sink for sending account update notifications to the client.
224    /// - `pubkey_str`: The account public key to monitor, as a base-58 encoded string.
225    /// - `config`: Optional configuration specifying commitment level and encoding format for account data.
226    ///
227    /// ## Returns
228    /// This method does not return a value directly. Instead, it establishes a continuous WebSocket
229    /// subscription that will send `RpcResponse<UiAccount>` notifications to the subscriber whenever
230    /// the account state changes.
231    ///
232    /// ## Example WebSocket Request
233    /// ```json
234    /// {
235    ///   "jsonrpc": "2.0",
236    ///   "id": 1,
237    ///   "method": "accountSubscribe",
238    ///   "params": [
239    ///     "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12",
240    ///     {
241    ///       "commitment": "finalized",
242    ///       "encoding": "base64"
243    ///     }
244    ///   ]
245    /// }
246    /// ```
247    ///
248    /// ## Example WebSocket Response (Subscription Confirmation)
249    /// ```json
250    /// {
251    ///   "jsonrpc": "2.0",
252    ///   "result": 23784,
253    ///   "id": 1
254    /// }
255    /// ```
256    ///
257    /// ## Example WebSocket Notification
258    /// ```json
259    /// {
260    ///   "jsonrpc": "2.0",
261    ///   "method": "accountNotification",
262    ///   "params": {
263    ///     "result": {
264    ///       "context": {
265    ///         "slot": 5208469
266    ///       },
267    ///       "value": {
268    ///         "data": ["base64EncodedAccountData", "base64"],
269    ///         "executable": false,
270    ///         "lamports": 33594,
271    ///         "owner": "11111111111111111111111111111112",
272    ///         "rentEpoch": 636
273    ///       }
274    ///     },
275    ///     "subscription": 23784
276    ///   }
277    /// }
278    /// ```
279    ///
280    /// ## Notes
281    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
282    /// - Account notifications are sent whenever any aspect of the account changes.
283    /// - The encoding format specified in the config determines how account data is serialized.
284    /// - Invalid public key formats will cause the subscription to be rejected with an error.
285    /// - Each subscription runs in its own async task to ensure optimal performance.
286    ///
287    /// ## See Also
288    /// - `accountUnsubscribe`: Remove an active account subscription
289    /// - `getAccountInfo`: Get current account information
290    #[pubsub(
291        subscription = "accountNotification",
292        subscribe,
293        name = "accountSubscribe"
294    )]
295    fn account_subscribe(
296        &self,
297        meta: Self::Metadata,
298        subscriber: Subscriber<RpcResponse<UiAccount>>,
299        pubkey_str: String,
300        config: Option<RpcAccountSubscribeConfig>,
301    );
302
303    /// Unsubscribe from account change notifications.
304    ///
305    /// This method removes an active account subscription, stopping further notifications
306    /// for the specified subscription ID. The monitoring task will automatically terminate
307    /// when the subscription is removed.
308    ///
309    /// ## Parameters
310    /// - `meta`: Optional WebSocket metadata containing connection information.
311    /// - `subscription`: The subscription ID to remove, as returned by `accountSubscribe`.
312    ///
313    /// ## Returns
314    /// A `Result<bool>` indicating whether the unsubscription was successful:
315    /// - `Ok(true)` if the subscription was successfully removed
316    /// - `Err(Error)` with `InvalidParams` if the subscription ID doesn't exist
317    ///
318    /// ## Example WebSocket Request
319    /// ```json
320    /// {
321    ///   "jsonrpc": "2.0",
322    ///   "id": 1,
323    ///   "method": "accountUnsubscribe",
324    ///   "params": [23784]
325    /// }
326    /// ```
327    ///
328    /// ## Example WebSocket Response
329    /// ```json
330    /// {
331    ///   "jsonrpc": "2.0",
332    ///   "result": true,
333    ///   "id": 1
334    /// }
335    /// ```
336    ///
337    /// ## Notes
338    /// - Attempting to unsubscribe from a non-existent subscription will return an error.
339    /// - Successfully unsubscribed connections will no longer receive account notifications.
340    /// - The monitoring task automatically detects subscription removal and terminates gracefully.
341    /// - This method is thread-safe and can be called concurrently.
342    ///
343    /// ## See Also
344    /// - `accountSubscribe`: Create an account change subscription
345    #[pubsub(
346        subscription = "accountNotification",
347        unsubscribe,
348        name = "accountUnsubscribe"
349    )]
350    fn account_unsubscribe(
351        &self,
352        meta: Option<Self::Metadata>,
353        subscription: SubscriptionId,
354    ) -> Result<bool>;
355
356    /// Subscribe to slot notifications.
357    ///
358    /// This method allows clients to subscribe to updates for a specific slot.
359    /// The subscriber will receive notifications whenever the slot changes.
360    ///
361    /// ## Parameters
362    /// - `meta`: WebSocket metadata containing RPC context and connection information.
363    /// - `subscriber`: The subscription sink for sending slot update notifications to the client.
364    ///
365    /// ## Returns
366    /// This method does not return a value directly. Instead, it establishes a continuous WebSocket
367    /// subscription that will send `SlotInfo` notifications to the subscriber whenever
368    /// the slot changes.
369    ///
370    /// ## Example WebSocket Request
371    /// ```json
372    /// {
373    ///   "jsonrpc": "2.0",
374    ///   "id": 1,
375    ///   "method": "slotSubscribe",
376    ///   "params": [
377    ///     {
378    ///       "commitment": "finalized"
379    ///     }
380    ///   ]
381    /// }
382    /// ```
383    ///
384    /// ## Example WebSocket Response (Subscription Confirmation)
385    /// ```json
386    /// {
387    ///   "jsonrpc": "2.0",
388    ///   "result": 5207624,
389    ///   "id": 1
390    /// }
391    /// ```
392    ///
393    /// ## Example WebSocket Notification
394    /// ```json
395    /// {
396    ///   "jsonrpc": "2.0",
397    ///   "method": "slotNotification",
398    ///   "params": {
399    ///     "result": {
400    ///       "slot": 5207624
401    ///     },
402    ///     "subscription": 5207624
403    ///   }
404    /// }
405    /// ```
406    ///
407    /// ## Notes
408    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
409    /// - Slot notifications are sent whenever the slot changes.
410    /// - The subscription automatically terminates when the slot changes.
411    /// - Each subscription runs in its own async task for optimal performance.
412    ///
413    /// ## See Also
414    /// - `slotUnsubscribe`: Remove an active slot subscription
415    #[pubsub(subscription = "slotNotification", subscribe, name = "slotSubscribe")]
416    fn slot_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<SlotInfo>);
417
418    /// Unsubscribe from slot notifications.
419    ///
420    /// This method removes an active slot subscription, stopping further notifications
421    /// for the specified subscription ID.
422    ///
423    /// ## Parameters
424    /// - `meta`: Optional WebSocket metadata containing connection information.
425    /// - `subscription`: The subscription ID to remove, as returned by `slotSubscribe`.
426    ///
427    /// ## Returns
428    /// A `Result<bool>` indicating whether the unsubscription was successful:
429    /// - `Ok(true)` if the subscription was successfully removed
430    /// - `Err(Error)` with `InvalidParams` if the subscription ID doesn't exist
431    ///
432    /// ## Example WebSocket Request
433    /// ```json
434    /// {
435    ///   "jsonrpc": "2.0",
436    ///   "id": 1,
437    ///   "method": "slotUnsubscribe",
438    ///   "params": [0]
439    /// }
440    /// ```
441    ///
442    /// ## Example WebSocket Response
443    /// ```json
444    /// {
445    ///   "jsonrpc": "2.0",
446    ///   "result": true,
447    ///   "id": 1
448    /// }
449    /// ```
450    ///
451    /// ## Notes
452    /// - Attempting to unsubscribe from a non-existent subscription will return an error.
453    /// - Successfully unsubscribed connections will no longer receive notifications.
454    /// - This method is thread-safe and can be called concurrently.
455    ///
456    /// ## See Also
457    /// - `slotSubscribe`: Create a slot subscription
458    #[pubsub(
459        subscription = "slotNotification",
460        unsubscribe,
461        name = "slotUnsubscribe"
462    )]
463    fn slot_unsubscribe(
464        &self,
465        meta: Option<Self::Metadata>,
466        subscription: SubscriptionId,
467    ) -> Result<bool>;
468
469    /// Subscribe to logs notifications.
470    ///
471    /// This method allows clients to subscribe to transaction log messages
472    /// emitted during transaction execution. It supports filtering by signature,
473    /// account mentions, or all transactions.
474    ///
475    /// ## Parameters
476    /// - `meta`: WebSocket metadata containing RPC context and connection information.
477    /// - `subscriber`: The subscription sink for sending log notifications to the client.
478    /// - `mentions`: Optional filter for the subscription: can be a specific signature, account, or `"all"`.
479    /// - `commitment`: Optional commitment level for filtering logs by block finality.
480    ///
481    /// ## Returns
482    /// This method establishes a continuous WebSocket subscription that streams
483    /// `RpcLogsResponse` notifications as new transactions are processed.
484    ///
485    /// ## Example WebSocket Request
486    /// ```json
487    /// {
488    ///   "jsonrpc": "2.0",
489    ///   "id": 1,
490    ///   "method": "logsSubscribe",
491    ///   "params": [
492    ///     {
493    ///       "mentions": ["11111111111111111111111111111111"]
494    ///     },
495    ///     {
496    ///       "commitment": "finalized"
497    ///     }
498    ///   ]
499    /// }
500    /// ```
501    ///
502    /// ## Example WebSocket Response (Subscription Confirmation)
503    /// ```json
504    /// {
505    ///   "jsonrpc": "2.0",
506    ///   "result": 42,
507    ///   "id": 1
508    /// }
509    /// ```
510    ///
511    /// ## Example WebSocket Notification
512    /// ```json
513    /// {
514    ///   "jsonrpc": "2.0",
515    ///   "method": "logsNotification",
516    ///   "params": {
517    ///     "result": {
518    ///       "signature": "3s6n...",
519    ///       "err": null,
520    ///       "logs": ["Program 111111... invoke [1]", "Program 111111... success"]
521    ///     },
522    ///     "subscription": 42
523    ///   }
524    /// }
525    /// ```
526    ///
527    /// ## Notes
528    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
529    /// - Each log subscription runs independently and supports filtering.
530    /// - Log messages may be truncated depending on cluster configuration.
531    ///
532    /// ## See Also
533    /// - `logsUnsubscribe`: Remove an active logs subscription.
534    #[pubsub(subscription = "logsNotification", subscribe, name = "logsSubscribe")]
535    fn logs_subscribe(
536        &self,
537        meta: Self::Metadata,
538        subscriber: Subscriber<RpcResponse<RpcLogsResponse>>,
539        mentions: Option<RpcTransactionLogsFilter>,
540        commitment: Option<CommitmentConfig>,
541    );
542
543    /// Unsubscribe from logs notifications.
544    ///
545    /// This method removes an active logs subscription, stopping further notifications
546    /// for the specified subscription ID.
547    ///
548    /// ## Parameters
549    /// - `meta`: Optional WebSocket metadata containing connection information.
550    /// - `subscription`: The subscription ID to remove, as returned by `logsSubscribe`.
551    ///
552    /// ## Returns
553    /// A `Result<bool>` indicating whether the unsubscription was successful:
554    /// - `Ok(true)` if the subscription was successfully removed.
555    /// - `Err(Error)` with `InvalidParams` if the subscription ID is not recognized.
556    ///
557    /// ## Example WebSocket Request
558    /// ```json
559    /// {
560    ///   "jsonrpc": "2.0",
561    ///   "id": 1,
562    ///   "method": "logsUnsubscribe",
563    ///   "params": [42]
564    /// }
565    /// ```
566    ///
567    /// ## Example WebSocket Response
568    /// ```json
569    /// {
570    ///   "jsonrpc": "2.0",
571    ///   "result": true,
572    ///   "id": 1
573    /// }
574    /// ```
575    ///
576    /// ## Notes
577    /// - Unsubscribing from a non-existent subscription ID returns an error.
578    /// - Successfully unsubscribed clients will no longer receive logs notifications.
579    /// - This method is thread-safe and may be called concurrently.
580    ///
581    /// ## See Also
582    /// - `logsSubscribe`: Create a logs subscription.
583    #[pubsub(
584        subscription = "logsNotification",
585        unsubscribe,
586        name = "logsUnsubscribe"
587    )]
588    fn logs_unsubscribe(
589        &self,
590        meta: Option<Self::Metadata>,
591        subscription: SubscriptionId,
592    ) -> Result<bool>;
593
594    #[pubsub(subscription = "rootNotification", subscribe, name = "rootSubscribe")]
595    fn root_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<RpcResponse<()>>);
596
597    #[pubsub(
598        subscription = "rootNotification",
599        unsubscribe,
600        name = "rootUnsubscribe"
601    )]
602    fn root_unsubscribe(
603        &self,
604        meta: Option<Self::Metadata>,
605        subscription: SubscriptionId,
606    ) -> Result<bool>;
607
608    /// Subscribe to notifications for all accounts owned by a specific program via WebSocket.
609    ///
610    /// This method allows clients to subscribe to updates for any account whose `owner`
611    /// matches the given program ID. Notifications are sent whenever an account owned by
612    /// the program is created, updated, or deleted.
613    ///
614    /// ## Parameters
615    /// - `meta`: WebSocket metadata containing RPC context and connection information.
616    /// - `subscriber`: The subscription sink for sending program account notifications to the client.
617    /// - `pubkey_str`: The program public key to monitor, as a base-58 encoded string.
618    /// - `config`: Optional configuration specifying commitment level, encoding format, and filters.
619    ///
620    /// ## Returns
621    /// This method does not return a value directly. Instead, it establishes a continuous WebSocket
622    /// subscription that will send `RpcResponse<RpcKeyedAccount>` notifications to the subscriber
623    /// whenever an account owned by the program changes.
624    ///
625    /// ## Filters
626    /// The optional config may include filters to narrow which account updates trigger notifications:
627    /// - `dataSize`: Only notify for accounts with a specific data length (in bytes).
628    /// - `memcmp`: Only notify for accounts whose data matches specific bytes at a given offset.
629    ///
630    /// ## Example WebSocket Request
631    /// ```json
632    /// {
633    ///   "jsonrpc": "2.0",
634    ///   "id": 1,
635    ///   "method": "programSubscribe",
636    ///   "params": [
637    ///     "11111111111111111111111111111111",
638    ///     {
639    ///       "encoding": "base64",
640    ///       "filters": [
641    ///         { "dataSize": 80 }
642    ///       ]
643    ///     }
644    ///   ]
645    /// }
646    /// ```
647    ///
648    /// ## Example WebSocket Response (Subscription Confirmation)
649    /// ```json
650    /// {
651    ///   "jsonrpc": "2.0",
652    ///   "result": 24040,
653    ///   "id": 1
654    /// }
655    /// ```
656    ///
657    /// ## Example WebSocket Notification
658    /// ```json
659    /// {
660    ///   "jsonrpc": "2.0",
661    ///   "method": "programNotification",
662    ///   "params": {
663    ///     "result": {
664    ///       "context": { "slot": 5208469 },
665    ///       "value": {
666    ///         "pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq",
667    ///         "account": {
668    ///           "data": ["base64data", "base64"],
669    ///           "executable": false,
670    ///           "lamports": 33594,
671    ///           "owner": "11111111111111111111111111111111",
672    ///           "rentEpoch": 636,
673    ///           "space": 36
674    ///         }
675    ///       }
676    ///     },
677    ///     "subscription": 24040
678    ///   }
679    /// }
680    /// ```
681    ///
682    /// ## Notes
683    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
684    /// - Notifications include both the account pubkey and the full account data.
685    /// - Invalid public key formats will cause the subscription to be rejected with an error.
686    /// - Each subscription runs in its own async task for optimal performance.
687    ///
688    /// ## See Also
689    /// - `programUnsubscribe`: Remove an active program subscription
690    /// - `getProgramAccounts`: Get current accounts for a program
691    #[pubsub(
692        subscription = "programNotification",
693        subscribe,
694        name = "programSubscribe"
695    )]
696    fn program_subscribe(
697        &self,
698        meta: Self::Metadata,
699        subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
700        pubkey_str: String,
701        config: Option<RpcProgramSubscribeConfig>,
702    );
703
704    /// Unsubscribe from program account change notifications.
705    ///
706    /// This method removes an active program subscription, stopping further notifications
707    /// for the specified subscription ID. The monitoring task will automatically terminate
708    /// when the subscription is removed.
709    ///
710    /// ## Parameters
711    /// - `meta`: Optional WebSocket metadata containing connection information.
712    /// - `subscription`: The subscription ID to remove, as returned by `programSubscribe`.
713    ///
714    /// ## Returns
715    /// A `Result<bool>` indicating whether the unsubscription was successful:
716    /// - `Ok(true)` if the subscription was successfully removed
717    /// - `Err(Error)` with `InternalError` if the subscription map lock could not be acquired
718    ///
719    /// ## Example WebSocket Request
720    /// ```json
721    /// {
722    ///   "jsonrpc": "2.0",
723    ///   "id": 1,
724    ///   "method": "programUnsubscribe",
725    ///   "params": [24040]
726    /// }
727    /// ```
728    ///
729    /// ## Example WebSocket Response
730    /// ```json
731    /// {
732    ///   "jsonrpc": "2.0",
733    ///   "result": true,
734    ///   "id": 1
735    /// }
736    /// ```
737    ///
738    /// ## Notes
739    /// - Successfully unsubscribed connections will no longer receive program account notifications.
740    /// - The monitoring task automatically detects subscription removal and terminates gracefully.
741    /// - This method is thread-safe and can be called concurrently.
742    ///
743    /// ## See Also
744    /// - `programSubscribe`: Create a program account change subscription
745    #[pubsub(
746        subscription = "programNotification",
747        unsubscribe,
748        name = "programUnsubscribe"
749    )]
750    fn program_unsubscribe(
751        &self,
752        meta: Option<Self::Metadata>,
753        subscription: SubscriptionId,
754    ) -> Result<bool>;
755
756    /// Subscribe to tagged slot lifecycle notifications via WebSocket.
757    ///
758    /// This method streams [`SlotUpdate`] notifications produced by the
759    /// underlying surfnet SVM whenever a slot advances through its
760    /// lifecycle. The notification payload follows Solana's reference
761    /// [`slotsUpdatesSubscribe`](https://solana.com/docs/rpc/websocket/slotsupdatessubscribe)
762    /// wire format: a tagged enum serialized with `type` discriminator and
763    /// camelCase variants (e.g. `"createdBank"`, `"frozen"`,
764    /// `"optimisticConfirmation"`, `"root"`).
765    ///
766    /// ## Parameters
767    /// - `meta`: WebSocket metadata containing RPC context and connection information.
768    /// - `subscriber`: The subscription sink for sending slot update notifications to the client.
769    ///
770    /// ## Returns
771    /// This method does not return a value directly. Instead, it establishes a continuous WebSocket
772    /// subscription that streams `SlotUpdate` notifications to the subscriber whenever the slot
773    /// advances through its lifecycle.
774    ///
775    /// ## Variants Emitted by Surfpool
776    /// - `createdBank` – emitted when the simulator opens a new slot.
777    /// - `frozen` – emitted once the previous slot has been confirmed,
778    ///   carrying execution `stats`.
779    /// - `optimisticConfirmation` – emitted alongside Geyser's `Confirmed`
780    ///   slot-status update for the freshly confirmed slot.
781    /// - `root` – emitted alongside Geyser's `Rooted` slot-status update.
782    ///
783    /// Surfpool has no gossip/shred layer, so the `firstShredReceived`,
784    /// `completed`, and `dead` variants documented in the Solana reference
785    /// are not produced by this implementation.
786    ///
787    /// ## Example WebSocket Request
788    /// ```json
789    /// {
790    ///   "jsonrpc": "2.0",
791    ///   "id": 1,
792    ///   "method": "slotsUpdatesSubscribe"
793    /// }
794    /// ```
795    ///
796    /// ## Example WebSocket Response (Subscription Confirmation)
797    /// ```json
798    /// {
799    ///   "jsonrpc": "2.0",
800    ///   "result": 0,
801    ///   "id": 1
802    /// }
803    /// ```
804    ///
805    /// ## Example WebSocket Notification (`frozen` variant)
806    /// ```json
807    /// {
808    ///   "jsonrpc": "2.0",
809    ///   "method": "slotsUpdatesNotification",
810    ///   "params": {
811    ///     "result": {
812    ///       "type": "frozen",
813    ///       "slot": 356,
814    ///       "timestamp": 1774643712834,
815    ///       "stats": {
816    ///         "numTransactionEntries": 1,
817    ///         "numSuccessfulTransactions": 1,
818    ///         "numFailedTransactions": 0,
819    ///         "maxTransactionsPerEntry": 1
820    ///       }
821    ///     },
822    ///     "subscription": 4
823    ///   }
824    /// }
825    /// ```
826    ///
827    /// ## Notes
828    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
829    /// - Notifications are delivered without an `RpcResponse` wrapper – the
830    ///   tagged `SlotUpdate` value appears directly under `params.result`.
831    /// - The `timestamp` field is a wall-clock millisecond Unix timestamp.
832    /// - Each subscription runs in its own async task for optimal performance.
833    ///
834    /// ## See Also
835    /// - [`slotsUpdatesUnsubscribe`]: Remove an active subscription
836    /// - [`slotSubscribe`]: Subscribe to the simpler `SlotInfo` stream
837    #[pubsub(
838        subscription = "slotsUpdatesNotification",
839        subscribe,
840        name = "slotsUpdatesSubscribe"
841    )]
842    fn slots_updates_subscribe(
843        &self,
844        meta: Self::Metadata,
845        subscriber: Subscriber<Arc<SlotUpdate>>,
846    );
847
848    /// Unsubscribe from tagged slot lifecycle notifications.
849    ///
850    /// This method removes an active `slotsUpdatesSubscribe` subscription,
851    /// stopping further notifications for the specified subscription ID. The
852    /// background monitoring task will detect the removal and terminate
853    /// gracefully.
854    ///
855    /// ## Parameters
856    /// - `meta`: Optional WebSocket metadata containing connection information.
857    /// - `subscription`: The subscription ID to remove, as returned by `slotsUpdatesSubscribe`.
858    ///
859    /// ## Returns
860    /// A `Result<bool>` indicating whether the unsubscription was successful:
861    /// - `Ok(true)` if the subscription was successfully removed
862    /// - `Err(Error)` with `InternalError` if the subscription map lock could not be acquired
863    ///
864    /// ## Example WebSocket Request
865    /// ```json
866    /// {
867    ///   "jsonrpc": "2.0",
868    ///   "id": 1,
869    ///   "method": "slotsUpdatesUnsubscribe",
870    ///   "params": [0]
871    /// }
872    /// ```
873    ///
874    /// ## Example WebSocket Response
875    /// ```json
876    /// {
877    ///   "jsonrpc": "2.0",
878    ///   "result": true,
879    ///   "id": 1
880    /// }
881    /// ```
882    ///
883    /// ## See Also
884    /// - `slotsUpdatesSubscribe`: Create a slots-updates subscription.
885    #[pubsub(
886        subscription = "slotsUpdatesNotification",
887        unsubscribe,
888        name = "slotsUpdatesUnsubscribe"
889    )]
890    fn slots_updates_unsubscribe(
891        &self,
892        meta: Option<Self::Metadata>,
893        subscription: SubscriptionId,
894    ) -> Result<bool>;
895
896    #[pubsub(subscription = "blockNotification", subscribe, name = "blockSubscribe")]
897    fn block_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<RpcResponse<()>>);
898
899    #[pubsub(
900        subscription = "blockNotification",
901        unsubscribe,
902        name = "blockUnsubscribe"
903    )]
904    fn block_unsubscribe(
905        &self,
906        meta: Option<Self::Metadata>,
907        subscription: SubscriptionId,
908    ) -> Result<bool>;
909
910    #[pubsub(subscription = "voteNotification", subscribe, name = "voteSubscribe")]
911    fn vote_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<RpcResponse<()>>);
912
913    #[pubsub(
914        subscription = "voteNotification",
915        unsubscribe,
916        name = "voteUnsubscribe"
917    )]
918    fn vote_unsubscribe(
919        &self,
920        meta: Option<Self::Metadata>,
921        subscription: SubscriptionId,
922    ) -> Result<bool>;
923
924    /// Subscribe to snapshot import notifications via WebSocket.
925    ///
926    /// This method allows clients to subscribe to real-time updates about snapshot import operations
927    /// from a specific snapshot URL. The subscriber will receive notifications when the snapshot
928    /// is being imported, including progress updates and completion status.
929    ///
930    /// ## Parameters
931    /// - `meta`: WebSocket metadata containing RPC context and connection information.
932    /// - `subscriber`: The subscription sink for sending snapshot import notifications to the client.
933    /// - `snapshot_url`: The URL of the snapshot to import and monitor.
934    ///
935    /// ## Returns
936    /// This method does not return a value directly. Instead, it establishes a continuous WebSocket
937    /// subscription that will send `SnapshotImportNotification` notifications to the subscriber whenever
938    /// the snapshot import operation status changes.
939    ///
940    /// ## Example WebSocket Request
941    /// ```json
942    /// {
943    ///   "jsonrpc": "2.0",
944    ///   "id": 1,
945    ///   "method": "snapshotSubscribe",
946    ///   "params": ["https://example.com/snapshot.json"]
947    /// }
948    /// ```
949    ///
950    /// ## Example WebSocket Response (Subscription Confirmation)
951    /// ```json
952    /// {
953    ///   "jsonrpc": "2.0",
954    ///   "result": 12345,
955    ///   "id": 1
956    /// }
957    /// ```
958    ///
959    /// ## Example WebSocket Notification
960    /// ```json
961    /// {
962    ///   "jsonrpc": "2.0",
963    ///   "method": "snapshotNotification",
964    ///   "params": {
965    ///     "result": {
966    ///       "snapshotId": "snapshot_20240107_123456",
967    ///       "status": "InProgress",
968    ///       "accountsLoaded": 1500,
969    ///       "totalAccounts": 3000,
970    ///       "error": null
971    ///     },
972    ///     "subscription": 12345
973    ///   }
974    /// }
975    /// ```
976    ///
977    /// ## Notes
978    /// - The subscription remains active until explicitly unsubscribed or the connection is closed.
979    /// - Multiple clients can subscribe to different snapshot notifications simultaneously.
980    /// - The snapshot URL must be accessible and contain a valid snapshot format.
981    /// - Each subscription runs in its own async task for optimal performance.
982    ///
983    /// ## See Also
984    /// - `snapshotUnsubscribe`: Remove an active snapshot subscription
985    #[pubsub(
986        subscription = "snapshotNotification",
987        subscribe,
988        name = "snapshotSubscribe"
989    )]
990    fn snapshot_subscribe(
991        &self,
992        meta: Self::Metadata,
993        subscriber: Subscriber<crate::surfnet::SnapshotImportNotification>,
994        snapshot_url: String,
995    );
996
997    /// Unsubscribe from snapshot import notifications.
998    ///
999    /// This method removes an active snapshot subscription, stopping further notifications
1000    /// for the specified subscription ID.
1001    ///
1002    /// ## Parameters
1003    /// - `meta`: Optional WebSocket metadata containing connection information.
1004    /// - `subscription`: The subscription ID to remove, as returned by `snapshotSubscribe`.
1005    ///
1006    /// ## Returns
1007    /// A `Result<bool>` indicating whether the unsubscription was successful:
1008    /// - `Ok(true)` if the subscription was successfully removed
1009    /// - `Err(Error)` with `InvalidParams` if the subscription ID doesn't exist
1010    ///
1011    /// ## Example WebSocket Request
1012    /// ```json
1013    /// {
1014    ///   "jsonrpc": "2.0",
1015    ///   "id": 1,
1016    ///   "method": "snapshotUnsubscribe",
1017    ///   "params": [12345]
1018    /// }
1019    /// ```
1020    ///
1021    /// ## Example WebSocket Response
1022    /// ```json
1023    /// {
1024    ///   "jsonrpc": "2.0",
1025    ///   "result": true,
1026    ///   "id": 1
1027    /// }
1028    /// ```
1029    ///
1030    /// ## Notes
1031    /// - Attempting to unsubscribe from a non-existent subscription will return an error.
1032    /// - Successfully unsubscribed connections will no longer receive snapshot notifications.
1033    /// - This method is thread-safe and can be called concurrently.
1034    ///
1035    /// ## See Also
1036    /// - `snapshotSubscribe`: Create a snapshot import subscription
1037    #[pubsub(
1038        subscription = "snapshotNotification",
1039        unsubscribe,
1040        name = "snapshotUnsubscribe"
1041    )]
1042    fn snapshot_unsubscribe(
1043        &self,
1044        meta: Option<Self::Metadata>,
1045        subscription: SubscriptionId,
1046    ) -> Result<bool>;
1047}
1048
1049/// WebSocket RPC server implementation for Surfpool.
1050///
1051/// This struct manages WebSocket subscriptions for both signature status updates
1052/// and account change notifications in the Surfpool environment. It provides a complete
1053/// WebSocket RPC interface that allows clients to subscribe to real-time updates
1054/// from the Solana Virtual Machine (SVM) and handles the lifecycle of WebSocket connections.
1055///
1056/// ## Fields
1057/// - `uid`: Atomic counter for generating unique subscription IDs across all subscription types.
1058/// - `signature_subscription_map`: Thread-safe HashMap containing active signature subscriptions, mapping subscription IDs to their notification sinks.
1059/// - `account_subscription_map`: Thread-safe HashMap containing active account subscriptions, mapping subscription IDs to their notification sinks.
1060/// - `program_subscription_map`: Thread-safe HashMap containing active program subscriptions, mapping subscription IDs to their notification sinks.
1061/// - `slot_subscription_map`: Thread-safe HashMap containing active slot subscriptions, mapping subscription IDs to their notification sinks.
1062/// - `slots_updates_subscription_map`: Thread-safe HashMap containing active fine-grained slot-lifecycle subscriptions (`slotsUpdatesSubscribe`), mapping subscription IDs to their `SlotUpdate` notification sinks.
1063/// - `logs_subscription_map`: Thread-safe HashMap containing active logs subscriptions, mapping subscription IDs to their notification sinks.
1064/// - `snapshot_subscription_map`: Thread-safe HashMap containing active snapshot-import subscriptions, mapping subscription IDs to their notification sinks.
1065/// - `tokio_handle`: Runtime handle for spawning asynchronous subscription monitoring tasks.
1066///
1067/// ## Features
1068/// - **Concurrent Subscriptions**: Supports multiple simultaneous subscriptions without blocking.
1069/// - **Thread Safety**: All subscription management operations are thread-safe using RwLock.
1070/// - **Automatic Cleanup**: Subscriptions are automatically cleaned up when completed or unsubscribed.
1071/// - **Efficient Monitoring**: Each subscription runs in its own async task for optimal performance.
1072/// - **Real-time Updates**: Provides immediate notifications when monitored conditions are met.
1073///
1074/// ## Usage
1075/// This struct implements the `Rpc` trait and is typically used as part of a larger
1076/// WebSocket server infrastructure to provide real-time blockchain data to clients.
1077///
1078/// ## Notes
1079/// - Each subscription is assigned a unique numeric ID for tracking and management.
1080/// - The struct maintains separate maps for different subscription types to optimize performance.
1081/// - All async operations are managed through the provided Tokio runtime handle.
1082///
1083/// ## See Also
1084/// - `Rpc`: The trait interface this struct implements
1085/// - `RpcAccountSubscribeConfig`: Configuration options for account subscriptions
1086pub struct SurfpoolWsRpc {
1087    pub uid: atomic::AtomicUsize,
1088    pub signature_subscription_map:
1089        Arc<RwLock<HashMap<SubscriptionId, Sink<RpcResponse<RpcSignatureResult>>>>>,
1090    pub account_subscription_map:
1091        Arc<RwLock<HashMap<SubscriptionId, Sink<RpcResponse<UiAccount>>>>>,
1092    pub program_subscription_map:
1093        Arc<RwLock<HashMap<SubscriptionId, Sink<RpcResponse<RpcKeyedAccount>>>>>,
1094    pub slot_subscription_map: Arc<RwLock<HashMap<SubscriptionId, Sink<SlotInfo>>>>,
1095    pub slots_updates_subscription_map: Arc<RwLock<HashMap<SubscriptionId, Sink<Arc<SlotUpdate>>>>>,
1096    pub logs_subscription_map:
1097        Arc<RwLock<HashMap<SubscriptionId, Sink<RpcResponse<RpcLogsResponse>>>>>,
1098    pub snapshot_subscription_map:
1099        Arc<RwLock<HashMap<SubscriptionId, Sink<crate::surfnet::SnapshotImportNotification>>>>,
1100    pub tokio_handle: tokio::runtime::Handle,
1101}
1102
1103impl Rpc for SurfpoolWsRpc {
1104    type Metadata = Option<SurfpoolWebsocketMeta>;
1105
1106    /// Implementation of signature subscription for WebSocket clients.
1107    ///
1108    /// This method handles the complete lifecycle of signature subscriptions:
1109    /// 1. Validates the provided signature string format
1110    /// 2. Determines the subscription type (received vs commitment-based)
1111    /// 3. Checks if the transaction already exists in the desired state
1112    /// 4. If found and confirmed, immediately notifies the subscriber
1113    /// 5. Otherwise, sets up a continuous monitoring loop
1114    /// 6. Spawns an async task to handle ongoing subscription management
1115    ///
1116    /// # Error Handling
1117    /// - Rejects subscription with `InvalidParams` for malformed signatures
1118    /// - Handles RPC context retrieval failures
1119    /// - Manages subscription cleanup on completion or failure
1120    ///
1121    /// # Concurrency
1122    /// Each subscription runs in its own async task, allowing multiple
1123    /// concurrent subscriptions without blocking each other.
1124    fn signature_subscribe(
1125        &self,
1126        meta: Self::Metadata,
1127        subscriber: Subscriber<RpcResponse<RpcSignatureResult>>,
1128        signature_str: String,
1129        config: Option<RpcSignatureSubscribeConfig>,
1130    ) {
1131        let _ = meta
1132            .as_ref()
1133            .map(|m| m.log_debug("Websocket 'signature_subscribe' connection established"));
1134
1135        let signature = match Signature::from_str(&signature_str) {
1136            Ok(sig) => sig,
1137            Err(_) => {
1138                let error = Error {
1139                    code: ErrorCode::InvalidParams,
1140                    message: "Invalid signature format.".into(),
1141                    data: None,
1142                };
1143                if let Err(e) = subscriber.reject(error.clone()) {
1144                    log::error!("Failed to reject subscriber: {:?}", e);
1145                }
1146                return;
1147            }
1148        };
1149        let config = config.unwrap_or_default();
1150        let rpc_transaction_config = RpcTransactionConfig {
1151            encoding: Some(UiTransactionEncoding::Json),
1152            commitment: config.commitment,
1153            max_supported_transaction_version: Some(MAX_SUPPORTED_TRANSACTION_VERSION),
1154        };
1155
1156        let subscription_type = if config.enable_received_notification.unwrap_or(false) {
1157            SignatureSubscriptionType::Received
1158        } else {
1159            SignatureSubscriptionType::Commitment(config.commitment.unwrap_or_default().commitment)
1160        };
1161
1162        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1163        let sub_id = SubscriptionId::Number(id as u64);
1164        let sink = match subscriber.assign_id(sub_id.clone()) {
1165            Ok(sink) => sink,
1166            Err(e) => {
1167                log::error!("Failed to assign subscription ID: {:?}", e);
1168                return;
1169            }
1170        };
1171        let active = Arc::clone(&self.signature_subscription_map);
1172        let meta = meta.clone();
1173        self.tokio_handle.spawn(async move {
1174            if let Ok(mut guard) = active.write() {
1175                guard.insert(sub_id.clone(), sink);
1176            } else {
1177                log::error!("Failed to acquire write lock on signature_subscription_map");
1178                return;
1179            }
1180
1181            let SurfnetRpcContext {
1182                svm_locker,
1183                remote_ctx,
1184            } = match meta.get_rpc_context(()) {
1185                Ok(res) => res,
1186                Err(e) => {
1187                    log::error!("Failed to get RPC context: {:?}", e);
1188                    if let Ok(mut guard) = active.write() {
1189                        if let Some(sink) = guard.remove(&sub_id) {
1190                            if let Err(e) = sink.notify(Err(e.into())) {
1191                                log::error!("Failed to notify client about RPC context error: {e}");
1192                            }
1193                        }
1194                    }
1195                    return;
1196                }
1197            };
1198            // get the signature from the SVM to see if it's already been processed
1199            let tx_result = match svm_locker
1200                .get_transaction(
1201                    &remote_ctx.map(|(r, _)| r),
1202                    &signature,
1203                    rpc_transaction_config,
1204                )
1205                .await
1206            {
1207                Ok(res) => res,
1208                Err(e) => {
1209                    if let Ok(mut guard) = active.write() {
1210                        if let Some(sink) = guard.remove(&sub_id) {
1211                            let _ = sink.notify(Err(e.into()));
1212                        }
1213                    }
1214                    return;
1215                }
1216            };
1217
1218            // if we already had the transaction, check if its confirmation status matches the desired status set by the subscription
1219            // if so, notify the user and complete the subscription
1220            // otherwise, subscribe to the transaction updates
1221            if let GetTransactionResult::FoundTransaction(_, _, tx) = tx_result {
1222                match (&subscription_type, tx.confirmation_status) {
1223                    (&SignatureSubscriptionType::Received, _)
1224                    | (
1225                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Processed),
1226                        Some(TransactionConfirmationStatus::Processed),
1227                    )
1228                    | (
1229                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Processed),
1230                        Some(TransactionConfirmationStatus::Confirmed),
1231                    )
1232                    | (
1233                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Processed),
1234                        Some(TransactionConfirmationStatus::Finalized),
1235                    )
1236                    | (
1237                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Confirmed),
1238                        Some(TransactionConfirmationStatus::Confirmed),
1239                    )
1240                    | (
1241                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Confirmed),
1242                        Some(TransactionConfirmationStatus::Finalized),
1243                    )
1244                    | (
1245                        &SignatureSubscriptionType::Commitment(CommitmentLevel::Finalized),
1246                        Some(TransactionConfirmationStatus::Finalized),
1247                    ) => {
1248                        if let Ok(mut guard) = active.write() {
1249                            if let Some(sink) = guard.remove(&sub_id) {
1250                                let _ = sink.notify(Ok(RpcResponse {
1251                                    context: RpcResponseContext::new(tx.slot),
1252                                    value: RpcSignatureResult::ProcessedSignature(
1253                                        ProcessedSignatureResult {
1254                                            err: tx.err.map(|e| e.into()),
1255                                        },
1256                                    ),
1257                                }));
1258                            }
1259                        }
1260                        return;
1261                    }
1262                    _ => {}
1263                }
1264            }
1265
1266            // update our surfnet SVM to subscribe to the signature updates
1267            let rx =
1268                svm_locker.subscribe_for_signature_updates(&signature, subscription_type.clone());
1269
1270            loop {
1271                let (slot, some_err) = match rx.try_recv() {
1272                    Ok(msg) => msg,
1273                    Err(e) => {
1274                        match e {
1275                            TryRecvError::Empty => {
1276                                // no update yet, continue
1277                                tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1278                                continue;
1279                            }
1280                            TryRecvError::Disconnected => {
1281                                warn!(
1282                                    "Signature subscription channel closed for sub id {:?}",
1283                                    sub_id
1284                                );
1285                                // channel closed, exit loop
1286                                break;
1287                            }
1288                        }
1289                    }
1290                };
1291
1292                let Ok(mut guard) = active.write() else {
1293                    log::error!("Failed to acquire read lock on signature_subscription_map");
1294                    break;
1295                };
1296
1297                let Some(sink) = guard.remove(&sub_id) else {
1298                    log::error!("Failed to get sink for subscription ID");
1299                    break;
1300                };
1301
1302                let res = match subscription_type {
1303                    SignatureSubscriptionType::Received => sink.notify(Ok(RpcResponse {
1304                        context: RpcResponseContext::new(slot),
1305                        value: RpcSignatureResult::ReceivedSignature(
1306                            ReceivedSignatureResult::ReceivedSignature,
1307                        ),
1308                    })),
1309                    SignatureSubscriptionType::Commitment(_) => sink.notify(Ok(RpcResponse {
1310                        context: RpcResponseContext::new(slot),
1311                        value: RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult {
1312                            err: some_err.map(|e| e.into()),
1313                        }),
1314                    })),
1315                };
1316
1317                if guard.is_empty() {
1318                    break;
1319                }
1320
1321                if let Err(e) = res {
1322                    log::error!("Failed to notify client about account update: {e}");
1323                    break;
1324                }
1325            }
1326        });
1327    }
1328
1329    /// Implementation of signature unsubscription for WebSocket clients.
1330    ///
1331    /// This method removes an active signature subscription from the internal
1332    /// tracking maps, effectively stopping further notifications for that subscription.
1333    ///
1334    /// # Implementation Details
1335    /// - Attempts to remove the subscription from the active subscriptions map
1336    /// - Returns success if the subscription existed and was removed
1337    /// - Returns an error if the subscription ID was not found
1338    ///
1339    /// # Thread Safety
1340    /// Uses write locks to ensure thread-safe removal from the subscription map.
1341    fn signature_unsubscribe(
1342        &self,
1343        _meta: Option<Self::Metadata>,
1344        subscription: SubscriptionId,
1345    ) -> Result<bool> {
1346        if let Ok(mut guard) = self.signature_subscription_map.write() {
1347            guard.remove(&subscription);
1348        } else {
1349            log::error!("Failed to acquire write lock on signature_subscription_map");
1350            return Err(Error {
1351                code: ErrorCode::InternalError,
1352                message: "Internal error.".into(),
1353                data: None,
1354            });
1355        };
1356        Ok(true)
1357    }
1358
1359    /// Implementation of account subscription for WebSocket clients.
1360    ///
1361    /// This method handles the complete lifecycle of account subscriptions:
1362    /// 1. Validates the provided public key string format
1363    /// 2. Parses the subscription configuration (commitment and encoding)
1364    /// 3. Generates a unique subscription ID and assigns it to the subscriber
1365    /// 4. Spawns an async task to continuously monitor account changes
1366    /// 5. Sends notifications whenever the account state changes
1367    ///
1368    /// # Monitoring Loop
1369    /// The spawned task runs a continuous loop that:
1370    /// - Checks if the subscription is still active (not unsubscribed)
1371    /// - Polls for account updates from the SVM
1372    /// - Sends notifications to the subscriber when changes occur
1373    /// - Automatically terminates when the subscription is removed
1374    ///
1375    /// # Error Handling
1376    /// - Rejects subscription with `InvalidParams` for malformed public keys
1377    /// - Handles encoding configuration for account data serialization
1378    /// - Manages subscription cleanup through the monitoring loop
1379    ///
1380    /// # Performance
1381    /// Uses efficient polling with minimal CPU overhead and automatic
1382    /// cleanup when subscriptions are no longer needed.
1383    fn account_subscribe(
1384        &self,
1385        meta: Self::Metadata,
1386        subscriber: Subscriber<RpcResponse<UiAccount>>,
1387        pubkey_str: String,
1388        config: Option<RpcAccountSubscribeConfig>,
1389    ) {
1390        let _ = meta
1391            .as_ref()
1392            .map(|m| m.log_debug("Websocket 'account_subscribe' connection established"));
1393
1394        let pubkey = match Pubkey::from_str(&pubkey_str) {
1395            Ok(pk) => pk,
1396            Err(_) => {
1397                let error = Error {
1398                    code: ErrorCode::InvalidParams,
1399                    message: "Invalid pubkey format.".into(),
1400                    data: None,
1401                };
1402                if subscriber.reject(error.clone()).is_err() {
1403                    log::error!("Failed to reject subscriber for invalid pubkey format.");
1404                }
1405                return;
1406            }
1407        };
1408
1409        let config = config.unwrap_or(RpcAccountSubscribeConfig {
1410            commitment: None,
1411            encoding: None,
1412        });
1413
1414        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1415        let sub_id = SubscriptionId::Number(id as u64);
1416        let sink = match subscriber.assign_id(sub_id.clone()) {
1417            Ok(sink) => sink,
1418            Err(e) => {
1419                log::error!("Failed to assign subscription ID: {:?}", e);
1420                return;
1421            }
1422        };
1423
1424        let account_active = Arc::clone(&self.account_subscription_map);
1425        let meta = meta.clone();
1426        let svm_locker = match meta.get_svm_locker() {
1427            Ok(locker) => locker,
1428            Err(e) => {
1429                log::error!("Failed to get SVM locker for account subscription: {e}");
1430                if let Err(e) = sink.notify(Err(e.into())) {
1431                    log::error!(
1432                        "Failed to send error notification to client for SVM locker failure: {e}"
1433                    );
1434                }
1435                return;
1436            }
1437        };
1438        let slot = svm_locker.with_svm_reader(|svm| svm.get_latest_absolute_slot());
1439
1440        self.tokio_handle.spawn(async move {
1441            if let Ok(mut guard) = account_active.write() {
1442                guard.insert(sub_id.clone(), sink);
1443            } else {
1444                log::error!("Failed to acquire write lock on account_subscription_map");
1445                return;
1446            }
1447
1448            // subscribe to account updates
1449            let rx = svm_locker.subscribe_for_account_updates(&pubkey, config.encoding);
1450
1451            loop {
1452                // if the subscription has been removed, break the loop
1453                if let Ok(guard) = account_active.read() {
1454                    if guard.get(&sub_id).is_none() {
1455                        break;
1456                    }
1457                } else {
1458                    log::error!("Failed to acquire read lock on account_subscription_map");
1459                    break;
1460                }
1461
1462                let Ok(ui_account) = rx.try_recv() else {
1463                    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1464                    continue;
1465                };
1466
1467                let Ok(guard) = account_active.read() else {
1468                    log::error!("Failed to acquire read lock on account_subscription_map");
1469                    break;
1470                };
1471
1472                let Some(sink) = guard.get(&sub_id) else {
1473                    log::error!("Failed to get sink for subscription ID");
1474                    break;
1475                };
1476
1477                if let Err(e) = sink.notify(Ok(RpcResponse {
1478                    context: RpcResponseContext::new(slot),
1479                    value: ui_account,
1480                })) {
1481                    log::error!("Failed to notify client about account update: {e}");
1482                    break;
1483                }
1484            }
1485        });
1486    }
1487
1488    /// Implementation of account unsubscription for WebSocket clients.
1489    ///
1490    /// This method removes an active account subscription from the internal
1491    /// tracking maps, effectively stopping further notifications for that subscription.
1492    /// The monitoring loop in the corresponding subscription task will detect this
1493    /// removal and automatically terminate.
1494    ///
1495    /// # Implementation Details
1496    /// - Attempts to remove the subscription from the account subscriptions map
1497    /// - Returns success if the subscription existed and was removed
1498    /// - Returns an error if the subscription ID was not found
1499    /// - The removal triggers automatic cleanup of the monitoring task
1500    ///
1501    /// # Thread Safety
1502    /// Uses write locks to ensure thread-safe removal from the subscription map.
1503    /// The monitoring task uses read locks to check subscription status, creating
1504    /// a clean synchronization pattern.
1505    fn account_unsubscribe(
1506        &self,
1507        _meta: Option<Self::Metadata>,
1508        subscription: SubscriptionId,
1509    ) -> Result<bool> {
1510        if let Ok(mut guard) = self.account_subscription_map.write() {
1511            guard.remove(&subscription)
1512        } else {
1513            log::error!("Failed to acquire write lock on account_subscription_map");
1514            return Err(Error {
1515                code: ErrorCode::InternalError,
1516                message: "Internal error.".into(),
1517                data: None,
1518            });
1519        };
1520        Ok(true)
1521    }
1522
1523    fn slot_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<SlotInfo>) {
1524        let _ = meta
1525            .as_ref()
1526            .map(|m| m.log_debug("Websocket 'slot_subscribe' connection established"));
1527
1528        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1529        let sub_id = SubscriptionId::Number(id as u64);
1530        let sink = match subscriber.assign_id(sub_id.clone()) {
1531            Ok(sink) => sink,
1532            Err(e) => {
1533                log::error!("Failed to assign subscription ID: {:?}", e);
1534                return;
1535            }
1536        };
1537
1538        let slot_active = Arc::clone(&self.slot_subscription_map);
1539        let meta = meta.clone();
1540
1541        let svm_locker = match meta.get_svm_locker() {
1542            Ok(locker) => locker,
1543            Err(e) => {
1544                log::error!("Failed to get SVM locker for slot subscription: {e}");
1545                if let Err(e) = sink.notify(Err(e.into())) {
1546                    log::error!(
1547                        "Failed to send error notification to client for SVM locker failure: {e}"
1548                    );
1549                }
1550                return;
1551            }
1552        };
1553
1554        self.tokio_handle.spawn(async move {
1555            if let Ok(mut guard) = slot_active.write() {
1556                guard.insert(sub_id.clone(), sink);
1557            } else {
1558                log::error!("Failed to acquire write lock on slot_subscription_map");
1559                return;
1560            }
1561
1562            let rx = svm_locker.subscribe_for_slot_updates();
1563
1564            loop {
1565                // if the subscription has been removed, break the loop
1566                if let Ok(guard) = slot_active.read() {
1567                    if guard.get(&sub_id).is_none() {
1568                        break;
1569                    }
1570                } else {
1571                    log::error!("Failed to acquire read lock on slot_subscription_map");
1572                    break;
1573                }
1574
1575                let Ok(slot_info) = rx.try_recv() else {
1576                    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1577                    continue;
1578                };
1579
1580                let Ok(guard) = slot_active.read() else {
1581                    log::error!("Failed to acquire read lock on slots_subscription_map");
1582                    break;
1583                };
1584
1585                let Some(sink) = guard.get(&sub_id) else {
1586                    log::error!("Failed to get sink for subscription ID");
1587                    break;
1588                };
1589
1590                if let Err(e) = sink.notify(Ok(slot_info)) {
1591                    log::error!("Failed to notify client about slots update: {e}");
1592                    break;
1593                }
1594            }
1595        });
1596    }
1597
1598    fn slot_unsubscribe(
1599        &self,
1600        _meta: Option<Self::Metadata>,
1601        subscription: SubscriptionId,
1602    ) -> Result<bool> {
1603        if let Ok(mut guard) = self.slot_subscription_map.write() {
1604            guard.remove(&subscription)
1605        } else {
1606            log::error!("Failed to acquire write lock on slot_subscription_map");
1607            return Err(Error {
1608                code: ErrorCode::InternalError,
1609                message: "Internal error.".into(),
1610                data: None,
1611            });
1612        };
1613        Ok(true)
1614    }
1615
1616    fn logs_subscribe(
1617        &self,
1618        meta: Self::Metadata,
1619        subscriber: Subscriber<RpcResponse<RpcLogsResponse>>,
1620        mentions: Option<RpcTransactionLogsFilter>,
1621        commitment: Option<CommitmentConfig>,
1622    ) {
1623        let _ = meta
1624            .as_ref()
1625            .map(|m| m.log_debug("Websocket 'logs_subscribe' connection established"));
1626
1627        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1628        let sub_id = SubscriptionId::Number(id as u64);
1629        let sink = match subscriber.assign_id(sub_id.clone()) {
1630            Ok(sink) => sink,
1631            Err(e) => {
1632                log::error!("Failed to assign subscription ID: {:?}", e);
1633                return;
1634            }
1635        };
1636
1637        let mentions = mentions.unwrap_or(RpcTransactionLogsFilter::All);
1638        let commitment = commitment.unwrap_or_default().commitment;
1639
1640        let logs_active = Arc::clone(&self.logs_subscription_map);
1641        let meta = meta.clone();
1642
1643        let svm_locker = match meta.get_svm_locker() {
1644            Ok(locker) => locker,
1645            Err(e) => {
1646                log::error!("Failed to get SVM locker for slot subscription: {e}");
1647                if let Err(e) = sink.notify(Err(e.into())) {
1648                    log::error!(
1649                        "Failed to send error notification to client for SVM locker failure: {e}"
1650                    );
1651                }
1652                return;
1653            }
1654        };
1655
1656        self.tokio_handle.spawn(async move {
1657            if let Ok(mut guard) = logs_active.write() {
1658                guard.insert(sub_id.clone(), sink);
1659            } else {
1660                log::error!("Failed to acquire write lock on slot_subscription_map");
1661                return;
1662            }
1663
1664            let rx = svm_locker.subscribe_for_logs_updates(&commitment, &mentions);
1665
1666            loop {
1667                // if the subscription has been removed, break the loop
1668                if let Ok(guard) = logs_active.read() {
1669                    if guard.get(&sub_id).is_none() {
1670                        break;
1671                    }
1672                } else {
1673                    log::error!("Failed to acquire read lock on slot_subscription_map");
1674                    break;
1675                }
1676
1677                let Ok((slot, value)) = rx.try_recv() else {
1678                    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1679                    continue;
1680                };
1681
1682                let Ok(guard) = logs_active.read() else {
1683                    log::error!("Failed to acquire read lock on logs_subscription_map");
1684                    break;
1685                };
1686
1687                let Some(sink) = guard.get(&sub_id) else {
1688                    log::error!("Failed to get sink for subscription ID");
1689                    break;
1690                };
1691
1692                if let Err(e) = sink.notify(Ok(RpcResponse {
1693                    context: RpcResponseContext::new(slot),
1694                    value,
1695                })) {
1696                    log::error!("Failed to notify client about logs update: {e}");
1697                    break;
1698                }
1699            }
1700        });
1701    }
1702
1703    fn logs_unsubscribe(
1704        &self,
1705        _meta: Option<Self::Metadata>,
1706        subscription: SubscriptionId,
1707    ) -> Result<bool> {
1708        if let Ok(mut guard) = self.logs_subscription_map.write() {
1709            guard.remove(&subscription);
1710        } else {
1711            log::error!("Failed to acquire write lock on logs_subscription_map");
1712            return Err(Error {
1713                code: ErrorCode::InternalError,
1714                message: "Internal error.".into(),
1715                data: None,
1716            });
1717        };
1718        Ok(true)
1719    }
1720
1721    fn root_subscribe(&self, meta: Self::Metadata, _subscriber: Subscriber<RpcResponse<()>>) {
1722        let _ = meta
1723            .as_ref()
1724            .map(|m| m.log_warn("Websocket method 'root_subscribe' is uninmplemented"));
1725    }
1726
1727    fn root_unsubscribe(
1728        &self,
1729        _meta: Option<Self::Metadata>,
1730        _subscription: SubscriptionId,
1731    ) -> Result<bool> {
1732        Ok(true)
1733    }
1734
1735    /// Implementation of program subscription for WebSocket clients.
1736    ///
1737    /// This method handles the complete lifecycle of program subscriptions:
1738    /// 1. Validates the provided program public key string format
1739    /// 2. Parses the subscription configuration (commitment, encoding, and filters)
1740    /// 3. Generates a unique subscription ID and assigns it to the subscriber
1741    /// 4. Spawns an async task to continuously monitor account changes for the program
1742    /// 5. Sends notifications whenever an account owned by the program changes and matches filters
1743    ///
1744    /// # Monitoring Loop
1745    /// The spawned task runs a continuous loop that:
1746    /// - Checks if the subscription is still active (not unsubscribed)
1747    /// - Polls for program account updates from the SVM
1748    /// - Applies configured filters (dataSize, memcmp) before notifying
1749    /// - Sends `RpcKeyedAccount` notifications (including account pubkey) to the subscriber
1750    /// - Automatically terminates when the subscription is removed
1751    ///
1752    /// # Error Handling
1753    /// - Rejects subscription with `InvalidParams` for malformed public keys
1754    /// - Handles encoding configuration for account data serialization
1755    /// - Manages subscription cleanup through the monitoring loop
1756    ///
1757    /// # Performance
1758    /// Uses efficient polling with minimal CPU overhead and automatic
1759    /// cleanup when subscriptions are no longer needed.
1760    fn program_subscribe(
1761        &self,
1762        meta: Self::Metadata,
1763        subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
1764        pubkey_str: String,
1765        config: Option<RpcProgramSubscribeConfig>,
1766    ) {
1767        let _ = meta
1768            .as_ref()
1769            .map(|m| m.log_debug("Websocket 'program_subscribe' connection established"));
1770
1771        let program_id = match Pubkey::from_str(&pubkey_str) {
1772            Ok(pk) => pk,
1773            Err(_) => {
1774                let error = Error {
1775                    code: ErrorCode::InvalidParams,
1776                    message: "Invalid pubkey format.".into(),
1777                    data: None,
1778                };
1779                if subscriber.reject(error.clone()).is_err() {
1780                    log::error!("Failed to reject subscriber for invalid pubkey format.");
1781                }
1782                return;
1783            }
1784        };
1785
1786        let config = config.unwrap_or_default();
1787
1788        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1789        let sub_id = SubscriptionId::Number(id as u64);
1790        let sink = match subscriber.assign_id(sub_id.clone()) {
1791            Ok(sink) => sink,
1792            Err(e) => {
1793                log::error!("Failed to assign subscription ID: {:?}", e);
1794                return;
1795            }
1796        };
1797
1798        let program_active = Arc::clone(&self.program_subscription_map);
1799        let meta = meta.clone();
1800        let svm_locker = match meta.get_svm_locker() {
1801            Ok(locker) => locker,
1802            Err(e) => {
1803                log::error!("Failed to get SVM locker for program subscription: {e}");
1804                if let Err(e) = sink.notify(Err(e.into())) {
1805                    log::error!(
1806                        "Failed to send error notification to client for SVM locker failure: {e}"
1807                    );
1808                }
1809                return;
1810            }
1811        };
1812        let slot = svm_locker.with_svm_reader(|svm| svm.get_latest_absolute_slot());
1813
1814        self.tokio_handle.spawn(async move {
1815            if let Ok(mut guard) = program_active.write() {
1816                guard.insert(sub_id.clone(), sink);
1817            } else {
1818                log::error!("Failed to acquire write lock on program_subscription_map");
1819                return;
1820            }
1821
1822            let rx = svm_locker.subscribe_for_program_updates(
1823                &program_id,
1824                config.encoding,
1825                config.filters,
1826            );
1827
1828            loop {
1829                // if the subscription has been removed, break the loop
1830                if let Ok(guard) = program_active.read() {
1831                    if guard.get(&sub_id).is_none() {
1832                        break;
1833                    }
1834                } else {
1835                    log::error!("Failed to acquire read lock on program_subscription_map");
1836                    break;
1837                }
1838
1839                let Ok(keyed_account) = rx.try_recv() else {
1840                    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1841                    continue;
1842                };
1843
1844                let Ok(guard) = program_active.read() else {
1845                    log::error!("Failed to acquire read lock on program_subscription_map");
1846                    break;
1847                };
1848
1849                let Some(sink) = guard.get(&sub_id) else {
1850                    log::error!("Failed to get sink for subscription ID");
1851                    break;
1852                };
1853
1854                if let Err(e) = sink.notify(Ok(RpcResponse {
1855                    context: RpcResponseContext::new(slot),
1856                    value: keyed_account,
1857                })) {
1858                    log::error!("Failed to notify client about program account update: {e}");
1859                    break;
1860                }
1861            }
1862        });
1863    }
1864
1865    /// Implementation of program unsubscription for WebSocket clients.
1866    ///
1867    /// This method removes an active program subscription from the internal
1868    /// tracking maps, effectively stopping further notifications for that subscription.
1869    /// The monitoring loop in the corresponding subscription task will detect this
1870    /// removal and automatically terminate.
1871    ///
1872    /// # Implementation Details
1873    /// - Attempts to remove the subscription from the program subscriptions map
1874    /// - Returns success if the subscription existed and was removed
1875    /// - Returns an error if the lock could not be acquired
1876    /// - The removal triggers automatic cleanup of the monitoring task
1877    ///
1878    /// # Thread Safety
1879    /// Uses write locks to ensure thread-safe removal from the subscription map.
1880    /// The monitoring task uses read locks to check subscription status, creating
1881    /// a clean synchronization pattern.
1882    fn program_unsubscribe(
1883        &self,
1884        _meta: Option<Self::Metadata>,
1885        subscription: SubscriptionId,
1886    ) -> Result<bool> {
1887        if let Ok(mut guard) = self.program_subscription_map.write() {
1888            guard.remove(&subscription);
1889        } else {
1890            log::error!("Failed to acquire write lock on program_subscription_map");
1891            return Err(Error {
1892                code: ErrorCode::InternalError,
1893                message: "Internal error.".into(),
1894                data: None,
1895            });
1896        };
1897        Ok(true)
1898    }
1899
1900    fn slots_updates_subscribe(
1901        &self,
1902        meta: Self::Metadata,
1903        subscriber: Subscriber<Arc<SlotUpdate>>,
1904    ) {
1905        let _ = meta
1906            .as_ref()
1907            .map(|m| m.log_debug("Websocket 'slots_updates_subscribe' connection established"));
1908
1909        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
1910        let sub_id = SubscriptionId::Number(id as u64);
1911        let sink = match subscriber.assign_id(sub_id.clone()) {
1912            Ok(sink) => sink,
1913            Err(e) => {
1914                log::error!("Failed to assign subscription ID: {:?}", e);
1915                return;
1916            }
1917        };
1918
1919        let slots_updates_active = Arc::clone(&self.slots_updates_subscription_map);
1920        let meta = meta.clone();
1921
1922        let svm_locker = match meta.get_svm_locker() {
1923            Ok(locker) => locker,
1924            Err(e) => {
1925                log::error!("Failed to get SVM locker for slots updates subscription: {e}");
1926                if let Err(e) = sink.notify(Err(e.into())) {
1927                    log::error!(
1928                        "Failed to send error notification to client for SVM locker failure: {e}"
1929                    );
1930                }
1931                return;
1932            }
1933        };
1934
1935        self.tokio_handle.spawn(async move {
1936            if let Ok(mut guard) = slots_updates_active.write() {
1937                guard.insert(sub_id.clone(), sink);
1938            } else {
1939                log::error!("Failed to acquire write lock on slots_updates_subscription_map");
1940                return;
1941            }
1942
1943            let rx = svm_locker.subscribe_for_slots_updates();
1944
1945            loop {
1946                // if the subscription has been removed, break the loop
1947                if let Ok(guard) = slots_updates_active.read() {
1948                    if guard.get(&sub_id).is_none() {
1949                        break;
1950                    }
1951                } else {
1952                    log::error!("Failed to acquire read lock on slots_updates_subscription_map");
1953                    break;
1954                }
1955
1956                let Ok(slot_update) = rx.try_recv() else {
1957                    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
1958                    continue;
1959                };
1960
1961                let Ok(guard) = slots_updates_active.read() else {
1962                    log::error!("Failed to acquire read lock on slots_updates_subscription_map");
1963                    break;
1964                };
1965
1966                let Some(sink) = guard.get(&sub_id) else {
1967                    // The subscription was removed by `slots_updates_unsubscribe`
1968                    // between the loop-top check and re-acquiring the read lock
1969                    // here. This is the normal clean-exit path, not an error.
1970                    break;
1971                };
1972
1973                if let Err(e) = sink.notify(Ok(slot_update)) {
1974                    log::error!("Failed to notify client about slots update event: {e}");
1975                    break;
1976                }
1977            }
1978        });
1979    }
1980
1981    fn slots_updates_unsubscribe(
1982        &self,
1983        _meta: Option<Self::Metadata>,
1984        subscription: SubscriptionId,
1985    ) -> Result<bool> {
1986        if let Ok(mut guard) = self.slots_updates_subscription_map.write() {
1987            guard.remove(&subscription);
1988        } else {
1989            log::error!("Failed to acquire write lock on slots_updates_subscription_map");
1990            return Err(Error {
1991                code: ErrorCode::InternalError,
1992                message: "Internal error.".into(),
1993                data: None,
1994            });
1995        };
1996        Ok(true)
1997    }
1998
1999    fn block_subscribe(&self, meta: Self::Metadata, _subscriber: Subscriber<RpcResponse<()>>) {
2000        let _ = meta
2001            .as_ref()
2002            .map(|m| m.log_warn("Websocket method 'block_subscribe' is uninmplemented"));
2003    }
2004
2005    fn block_unsubscribe(
2006        &self,
2007        _meta: Option<Self::Metadata>,
2008        _subscription: SubscriptionId,
2009    ) -> Result<bool> {
2010        Ok(true)
2011    }
2012
2013    fn vote_subscribe(&self, meta: Self::Metadata, _subscriber: Subscriber<RpcResponse<()>>) {
2014        let _ = meta
2015            .as_ref()
2016            .map(|m| m.log_warn("Websocket method 'vote_subscribe' is uninmplemented"));
2017    }
2018
2019    fn vote_unsubscribe(
2020        &self,
2021        _meta: Option<Self::Metadata>,
2022        _subscription: SubscriptionId,
2023    ) -> Result<bool> {
2024        Ok(true)
2025    }
2026
2027    fn snapshot_subscribe(
2028        &self,
2029        meta: Self::Metadata,
2030        subscriber: Subscriber<crate::surfnet::SnapshotImportNotification>,
2031        snapshot_url: String,
2032    ) {
2033        let _ = meta
2034            .as_ref()
2035            .map(|m| m.log_debug("Websocket 'snapshot_subscribe' connection established"));
2036
2037        // Validate snapshot URL format
2038        if snapshot_url.is_empty() {
2039            let error = Error {
2040                code: ErrorCode::InvalidParams,
2041                message: "Invalid snapshot URL: URL cannot be empty.".into(),
2042                data: None,
2043            };
2044            if let Err(e) = subscriber.reject(error.clone()) {
2045                log::error!("Failed to reject subscriber: {:?}", e);
2046            }
2047            return;
2048        }
2049
2050        let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
2051        let sub_id = SubscriptionId::Number(id as u64);
2052        let sink = match subscriber.assign_id(sub_id.clone()) {
2053            Ok(sink) => sink,
2054            Err(e) => {
2055                log::error!("Failed to assign subscription ID: {:?}", e);
2056                return;
2057            }
2058        };
2059
2060        let snapshot_active = Arc::clone(&self.snapshot_subscription_map);
2061        let meta = meta.clone();
2062
2063        let svm_locker = match meta.get_svm_locker() {
2064            Ok(locker) => locker,
2065            Err(e) => {
2066                log::error!("Failed to get SVM locker for snapshot subscription: {e}");
2067                if let Err(e) = sink.notify(Err(e.into())) {
2068                    log::error!(
2069                        "Failed to send error notification to client for SVM locker failure: {e}"
2070                    );
2071                }
2072                return;
2073            }
2074        };
2075
2076        self.tokio_handle.spawn(async move {
2077            if let Ok(mut guard) = snapshot_active.write() {
2078                guard.insert(sub_id.clone(), sink);
2079            } else {
2080                log::error!("Failed to acquire write lock on snapshot_subscription_map");
2081                return;
2082            }
2083
2084            // Generate a unique snapshot ID for this import operation
2085            let snapshot_id = format!(
2086                "snapshot_{}",
2087                chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
2088            );
2089
2090            // Subscribe to snapshot import updates
2091            // The locker will send the Started notification through the channel
2092            let rx = svm_locker.subscribe_for_snapshot_import_updates(&snapshot_url, &snapshot_id);
2093
2094            loop {
2095                // if the subscription has been removed, break the loop
2096                if let Ok(guard) = snapshot_active.read() {
2097                    if guard.get(&sub_id).is_none() {
2098                        break;
2099                    }
2100                } else {
2101                    log::error!("Failed to acquire read lock on snapshot_subscription_map");
2102                    break;
2103                }
2104
2105                let Ok(notification) = rx.try_recv() else {
2106                    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2107                    continue;
2108                };
2109
2110                let Ok(guard) = snapshot_active.read() else {
2111                    log::error!("Failed to acquire read lock on snapshot_subscription_map");
2112                    break;
2113                };
2114
2115                let Some(sink) = guard.get(&sub_id) else {
2116                    log::error!("Failed to get sink for subscription ID");
2117                    break;
2118                };
2119
2120                if let Err(e) = sink.notify(Ok(notification)) {
2121                    log::error!("Failed to notify client about snapshot import update: {e}");
2122                    break;
2123                }
2124
2125                // If the import is completed or failed, we can end the subscription
2126                if let Ok(guard) = snapshot_active.read() {
2127                    if let Some(_sink) = guard.get(&sub_id) {
2128                        // Check if this was the final notification
2129                        // We'll determine this by checking the status in the last notification
2130                        // For now, we'll keep the subscription alive in case of multiple imports
2131                    }
2132                }
2133            }
2134        });
2135    }
2136
2137    fn snapshot_unsubscribe(
2138        &self,
2139        _meta: Option<Self::Metadata>,
2140        subscription: SubscriptionId,
2141    ) -> Result<bool> {
2142        if let Ok(mut guard) = self.snapshot_subscription_map.write() {
2143            guard.remove(&subscription);
2144        } else {
2145            log::error!("Failed to acquire write lock on snapshot_subscription_map");
2146            return Err(Error {
2147                code: ErrorCode::InternalError,
2148                message: "Internal error.".into(),
2149                data: None,
2150            });
2151        };
2152        Ok(true)
2153    }
2154}