Add encoding and filters parameters to rpc Subscriptions (#11065)
* Plumb account configs and enable encoding * Enable filters for pubsub program accounts * Update docs
This commit is contained in:
		| @@ -5,7 +5,10 @@ use jsonrpc_core::{Error, ErrorCode, Result}; | |||||||
| use jsonrpc_derive::rpc; | use jsonrpc_derive::rpc; | ||||||
| use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId}; | use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId}; | ||||||
| use solana_account_decoder::UiAccount; | use solana_account_decoder::UiAccount; | ||||||
| use solana_client::rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult}; | use solana_client::{ | ||||||
|  |     rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, | ||||||
|  |     rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult}, | ||||||
|  | }; | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| use solana_runtime::bank_forks::BankForks; | use solana_runtime::bank_forks::BankForks; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
| @@ -38,7 +41,7 @@ pub trait RpcSolPubSub { | |||||||
|         meta: Self::Metadata, |         meta: Self::Metadata, | ||||||
|         subscriber: Subscriber<RpcResponse<UiAccount>>, |         subscriber: Subscriber<RpcResponse<UiAccount>>, | ||||||
|         pubkey_str: String, |         pubkey_str: String, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcAccountInfoConfig>, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // Unsubscribe from account notification subscription. |     // Unsubscribe from account notification subscription. | ||||||
| @@ -62,7 +65,7 @@ pub trait RpcSolPubSub { | |||||||
|         meta: Self::Metadata, |         meta: Self::Metadata, | ||||||
|         subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>, |         subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>, | ||||||
|         pubkey_str: String, |         pubkey_str: String, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcProgramAccountsConfig>, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // Unsubscribe from account notification subscription. |     // Unsubscribe from account notification subscription. | ||||||
| @@ -173,7 +176,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl { | |||||||
|         _meta: Self::Metadata, |         _meta: Self::Metadata, | ||||||
|         subscriber: Subscriber<RpcResponse<UiAccount>>, |         subscriber: Subscriber<RpcResponse<UiAccount>>, | ||||||
|         pubkey_str: String, |         pubkey_str: String, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcAccountInfoConfig>, | ||||||
|     ) { |     ) { | ||||||
|         match param::<Pubkey>(&pubkey_str, "pubkey") { |         match param::<Pubkey>(&pubkey_str, "pubkey") { | ||||||
|             Ok(pubkey) => { |             Ok(pubkey) => { | ||||||
| @@ -181,7 +184,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl { | |||||||
|                 let sub_id = SubscriptionId::Number(id as u64); |                 let sub_id = SubscriptionId::Number(id as u64); | ||||||
|                 info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); |                 info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); | ||||||
|                 self.subscriptions |                 self.subscriptions | ||||||
|                     .add_account_subscription(pubkey, commitment, sub_id, subscriber) |                     .add_account_subscription(pubkey, config, sub_id, subscriber) | ||||||
|             } |             } | ||||||
|             Err(e) => subscriber.reject(e).unwrap(), |             Err(e) => subscriber.reject(e).unwrap(), | ||||||
|         } |         } | ||||||
| @@ -209,7 +212,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl { | |||||||
|         _meta: Self::Metadata, |         _meta: Self::Metadata, | ||||||
|         subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>, |         subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>, | ||||||
|         pubkey_str: String, |         pubkey_str: String, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcProgramAccountsConfig>, | ||||||
|     ) { |     ) { | ||||||
|         match param::<Pubkey>(&pubkey_str, "pubkey") { |         match param::<Pubkey>(&pubkey_str, "pubkey") { | ||||||
|             Ok(pubkey) => { |             Ok(pubkey) => { | ||||||
| @@ -217,7 +220,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl { | |||||||
|                 let sub_id = SubscriptionId::Number(id as u64); |                 let sub_id = SubscriptionId::Number(id as u64); | ||||||
|                 info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id); |                 info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id); | ||||||
|                 self.subscriptions |                 self.subscriptions | ||||||
|                     .add_program_subscription(pubkey, commitment, sub_id, subscriber) |                     .add_program_subscription(pubkey, config, sub_id, subscriber) | ||||||
|             } |             } | ||||||
|             Err(e) => subscriber.reject(e).unwrap(), |             Err(e) => subscriber.reject(e).unwrap(), | ||||||
|         } |         } | ||||||
| @@ -355,6 +358,7 @@ mod tests { | |||||||
|     use jsonrpc_core::{futures::sync::mpsc, Response}; |     use jsonrpc_core::{futures::sync::mpsc, Response}; | ||||||
|     use jsonrpc_pubsub::{PubSubHandler, Session}; |     use jsonrpc_pubsub::{PubSubHandler, Session}; | ||||||
|     use serial_test_derive::serial; |     use serial_test_derive::serial; | ||||||
|  |     use solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding}; | ||||||
|     use solana_budget_program::{self, budget_instruction}; |     use solana_budget_program::{self, budget_instruction}; | ||||||
|     use solana_runtime::{ |     use solana_runtime::{ | ||||||
|         bank::Bank, |         bank::Bank, | ||||||
| @@ -370,7 +374,7 @@ mod tests { | |||||||
|         message::Message, |         message::Message, | ||||||
|         pubkey::Pubkey, |         pubkey::Pubkey, | ||||||
|         signature::{Keypair, Signer}, |         signature::{Keypair, Signer}, | ||||||
|         system_program, system_transaction, |         system_instruction, system_program, system_transaction, | ||||||
|         transaction::{self, Transaction}, |         transaction::{self, Transaction}, | ||||||
|     }; |     }; | ||||||
|     use solana_vote_program::vote_transaction; |     use solana_vote_program::vote_transaction; | ||||||
| @@ -537,7 +541,10 @@ mod tests { | |||||||
|             session, |             session, | ||||||
|             subscriber, |             subscriber, | ||||||
|             contract_state.pubkey().to_string(), |             contract_state.pubkey().to_string(), | ||||||
|             Some(CommitmentConfig::recent()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::recent()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         let tx = system_transaction::transfer(&alice, &contract_funds.pubkey(), 51, blockhash); |         let tx = system_transaction::transfer(&alice, &contract_funds.pubkey(), 51, blockhash); | ||||||
| @@ -610,6 +617,88 @@ mod tests { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     #[serial] | ||||||
|  |     fn test_account_subscribe_with_encoding() { | ||||||
|  |         let GenesisConfigInfo { | ||||||
|  |             genesis_config, | ||||||
|  |             mint_keypair: alice, | ||||||
|  |             .. | ||||||
|  |         } = create_genesis_config(10_000); | ||||||
|  |  | ||||||
|  |         let nonce_account = Keypair::new(); | ||||||
|  |         let bank = Bank::new(&genesis_config); | ||||||
|  |         let blockhash = bank.last_blockhash(); | ||||||
|  |         let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); | ||||||
|  |         let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone(); | ||||||
|  |         let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1); | ||||||
|  |         bank_forks.write().unwrap().insert(bank1); | ||||||
|  |  | ||||||
|  |         let rpc = RpcSolPubSubImpl { | ||||||
|  |             subscriptions: Arc::new(RpcSubscriptions::new( | ||||||
|  |                 &Arc::new(AtomicBool::new(false)), | ||||||
|  |                 bank_forks.clone(), | ||||||
|  |                 Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests_with_slots( | ||||||
|  |                     1, 1, | ||||||
|  |                 ))), | ||||||
|  |             )), | ||||||
|  |             uid: Arc::new(atomic::AtomicUsize::default()), | ||||||
|  |         }; | ||||||
|  |         let session = create_session(); | ||||||
|  |         let (subscriber, _id_receiver, receiver) = Subscriber::new_test("accountNotification"); | ||||||
|  |         rpc.account_subscribe( | ||||||
|  |             session, | ||||||
|  |             subscriber, | ||||||
|  |             nonce_account.pubkey().to_string(), | ||||||
|  |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::recent()), | ||||||
|  |                 encoding: Some(UiAccountEncoding::JsonParsed), | ||||||
|  |             }), | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         let ixs = system_instruction::create_nonce_account( | ||||||
|  |             &alice.pubkey(), | ||||||
|  |             &nonce_account.pubkey(), | ||||||
|  |             &alice.pubkey(), | ||||||
|  |             100, | ||||||
|  |         ); | ||||||
|  |         let message = Message::new(&ixs, Some(&alice.pubkey())); | ||||||
|  |         let tx = Transaction::new(&[&alice, &nonce_account], message, blockhash); | ||||||
|  |         process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap(); | ||||||
|  |         sleep(Duration::from_millis(200)); | ||||||
|  |  | ||||||
|  |         // Test signature confirmation notification #1 | ||||||
|  |         let expected_data = bank_forks | ||||||
|  |             .read() | ||||||
|  |             .unwrap() | ||||||
|  |             .get(1) | ||||||
|  |             .unwrap() | ||||||
|  |             .get_account(&nonce_account.pubkey()) | ||||||
|  |             .unwrap() | ||||||
|  |             .data; | ||||||
|  |         let expected_data = parse_account_data(&system_program::id(), &expected_data).unwrap(); | ||||||
|  |         let expected = json!({ | ||||||
|  |            "jsonrpc": "2.0", | ||||||
|  |            "method": "accountNotification", | ||||||
|  |            "params": { | ||||||
|  |                "result": { | ||||||
|  |                    "context": { "slot": 1 }, | ||||||
|  |                    "value": { | ||||||
|  |                        "owner": system_program::id().to_string(), | ||||||
|  |                        "lamports": 100, | ||||||
|  |                        "data": expected_data, | ||||||
|  |                        "executable": false, | ||||||
|  |                        "rentEpoch": 1, | ||||||
|  |                    }, | ||||||
|  |                }, | ||||||
|  |                "subscription": 0, | ||||||
|  |            } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         let (response, _) = robust_poll_or_panic(receiver); | ||||||
|  |         assert_eq!(serde_json::to_string(&expected).unwrap(), response); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     #[serial] |     #[serial] | ||||||
|     fn test_account_unsubscribe() { |     fn test_account_unsubscribe() { | ||||||
| @@ -675,7 +764,10 @@ mod tests { | |||||||
|             session, |             session, | ||||||
|             subscriber, |             subscriber, | ||||||
|             bob.pubkey().to_string(), |             bob.pubkey().to_string(), | ||||||
|             Some(CommitmentConfig::root()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::root()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); |         let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); | ||||||
| @@ -721,7 +813,10 @@ mod tests { | |||||||
|             session, |             session, | ||||||
|             subscriber, |             subscriber, | ||||||
|             bob.pubkey().to_string(), |             bob.pubkey().to_string(), | ||||||
|             Some(CommitmentConfig::root()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::root()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); |         let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash); | ||||||
|   | |||||||
| @@ -8,8 +8,10 @@ use jsonrpc_pubsub::{ | |||||||
| }; | }; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use solana_account_decoder::{UiAccount, UiAccountEncoding}; | use solana_account_decoder::{UiAccount, UiAccountEncoding}; | ||||||
| use solana_client::rpc_response::{ | use solana_client::{ | ||||||
|     Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult, |     rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, | ||||||
|  |     rpc_filter::RpcFilterType, | ||||||
|  |     rpc_response::{Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult}, | ||||||
| }; | }; | ||||||
| use solana_runtime::{ | use solana_runtime::{ | ||||||
|     bank::Bank, |     bank::Bank, | ||||||
| @@ -79,29 +81,44 @@ impl std::fmt::Debug for NotificationEntry { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| struct SubscriptionData<S> { | struct SubscriptionData<S, T> { | ||||||
|     sink: Sink<S>, |     sink: Sink<S>, | ||||||
|     commitment: CommitmentConfig, |     commitment: CommitmentConfig, | ||||||
|     last_notified_slot: RwLock<Slot>, |     last_notified_slot: RwLock<Slot>, | ||||||
|  |     config: Option<T>, | ||||||
| } | } | ||||||
| type RpcAccountSubscriptions = | #[derive(Default, Clone)] | ||||||
|     RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>>>>>; | struct ProgramConfig { | ||||||
| type RpcProgramSubscriptions = |     filters: Vec<RpcFilterType>, | ||||||
|     RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>>>>>; |     encoding: Option<UiAccountEncoding>, | ||||||
|  | } | ||||||
|  | type RpcAccountSubscriptions = RwLock< | ||||||
|  |     HashMap< | ||||||
|  |         Pubkey, | ||||||
|  |         HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>, UiAccountEncoding>>, | ||||||
|  |     >, | ||||||
|  | >; | ||||||
|  | type RpcProgramSubscriptions = RwLock< | ||||||
|  |     HashMap< | ||||||
|  |         Pubkey, | ||||||
|  |         HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>, ProgramConfig>>, | ||||||
|  |     >, | ||||||
|  | >; | ||||||
| type RpcSignatureSubscriptions = RwLock< | type RpcSignatureSubscriptions = RwLock< | ||||||
|     HashMap<Signature, HashMap<SubscriptionId, SubscriptionData<Response<RpcSignatureResult>>>>, |     HashMap<Signature, HashMap<SubscriptionId, SubscriptionData<Response<RpcSignatureResult>, ()>>>, | ||||||
| >; | >; | ||||||
| type RpcSlotSubscriptions = RwLock<HashMap<SubscriptionId, Sink<SlotInfo>>>; | type RpcSlotSubscriptions = RwLock<HashMap<SubscriptionId, Sink<SlotInfo>>>; | ||||||
| type RpcVoteSubscriptions = RwLock<HashMap<SubscriptionId, Sink<RpcVote>>>; | type RpcVoteSubscriptions = RwLock<HashMap<SubscriptionId, Sink<RpcVote>>>; | ||||||
| type RpcRootSubscriptions = RwLock<HashMap<SubscriptionId, Sink<Slot>>>; | type RpcRootSubscriptions = RwLock<HashMap<SubscriptionId, Sink<Slot>>>; | ||||||
|  |  | ||||||
| fn add_subscription<K, S>( | fn add_subscription<K, S, T>( | ||||||
|     subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S>>>, |     subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S, T>>>, | ||||||
|     hashmap_key: K, |     hashmap_key: K, | ||||||
|     commitment: Option<CommitmentConfig>, |     commitment: Option<CommitmentConfig>, | ||||||
|     sub_id: SubscriptionId, |     sub_id: SubscriptionId, | ||||||
|     subscriber: Subscriber<S>, |     subscriber: Subscriber<S>, | ||||||
|     last_notified_slot: Slot, |     last_notified_slot: Slot, | ||||||
|  |     config: Option<T>, | ||||||
| ) where | ) where | ||||||
|     K: Eq + Hash, |     K: Eq + Hash, | ||||||
|     S: Clone, |     S: Clone, | ||||||
| @@ -112,6 +129,7 @@ fn add_subscription<K, S>( | |||||||
|         sink, |         sink, | ||||||
|         commitment, |         commitment, | ||||||
|         last_notified_slot: RwLock::new(last_notified_slot), |         last_notified_slot: RwLock::new(last_notified_slot), | ||||||
|  |         config, | ||||||
|     }; |     }; | ||||||
|     if let Some(current_hashmap) = subscriptions.get_mut(&hashmap_key) { |     if let Some(current_hashmap) = subscriptions.get_mut(&hashmap_key) { | ||||||
|         current_hashmap.insert(sub_id, subscription_data); |         current_hashmap.insert(sub_id, subscription_data); | ||||||
| @@ -122,8 +140,8 @@ fn add_subscription<K, S>( | |||||||
|     subscriptions.insert(hashmap_key, hashmap); |     subscriptions.insert(hashmap_key, hashmap); | ||||||
| } | } | ||||||
|  |  | ||||||
| fn remove_subscription<K, S>( | fn remove_subscription<K, S, T>( | ||||||
|     subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S>>>, |     subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S, T>>>, | ||||||
|     sub_id: &SubscriptionId, |     sub_id: &SubscriptionId, | ||||||
| ) -> bool | ) -> bool | ||||||
| where | where | ||||||
| @@ -145,8 +163,8 @@ where | |||||||
| } | } | ||||||
|  |  | ||||||
| #[allow(clippy::type_complexity)] | #[allow(clippy::type_complexity)] | ||||||
| fn check_commitment_and_notify<K, S, B, F, X>( | fn check_commitment_and_notify<K, S, B, F, X, T>( | ||||||
|     subscriptions: &HashMap<K, HashMap<SubscriptionId, SubscriptionData<Response<S>>>>, |     subscriptions: &HashMap<K, HashMap<SubscriptionId, SubscriptionData<Response<S>, T>>>, | ||||||
|     hashmap_key: &K, |     hashmap_key: &K, | ||||||
|     bank_forks: &Arc<RwLock<BankForks>>, |     bank_forks: &Arc<RwLock<BankForks>>, | ||||||
|     commitment_slots: &CommitmentSlots, |     commitment_slots: &CommitmentSlots, | ||||||
| @@ -158,8 +176,9 @@ where | |||||||
|     K: Eq + Hash + Clone + Copy, |     K: Eq + Hash + Clone + Copy, | ||||||
|     S: Clone + Serialize, |     S: Clone + Serialize, | ||||||
|     B: Fn(&Bank, &K) -> X, |     B: Fn(&Bank, &K) -> X, | ||||||
|     F: Fn(X, Slot) -> (Box<dyn Iterator<Item = S>>, Slot), |     F: Fn(X, Slot, Option<T>) -> (Box<dyn Iterator<Item = S>>, Slot), | ||||||
|     X: Clone + Serialize + Default, |     X: Clone + Serialize + Default, | ||||||
|  |     T: Clone, | ||||||
| { | { | ||||||
|     let mut notified_set: HashSet<SubscriptionId> = HashSet::new(); |     let mut notified_set: HashSet<SubscriptionId> = HashSet::new(); | ||||||
|     if let Some(hashmap) = subscriptions.get(hashmap_key) { |     if let Some(hashmap) = subscriptions.get(hashmap_key) { | ||||||
| @@ -169,6 +188,7 @@ where | |||||||
|                 sink, |                 sink, | ||||||
|                 commitment, |                 commitment, | ||||||
|                 last_notified_slot, |                 last_notified_slot, | ||||||
|  |                 config, | ||||||
|             }, |             }, | ||||||
|         ) in hashmap.iter() |         ) in hashmap.iter() | ||||||
|         { |         { | ||||||
| @@ -188,7 +208,8 @@ where | |||||||
|                     .unwrap_or_default() |                     .unwrap_or_default() | ||||||
|             }; |             }; | ||||||
|             let mut w_last_notified_slot = last_notified_slot.write().unwrap(); |             let mut w_last_notified_slot = last_notified_slot.write().unwrap(); | ||||||
|             let (filter_results, result_slot) = filter_results(results, *w_last_notified_slot); |             let (filter_results, result_slot) = | ||||||
|  |                 filter_results(results, *w_last_notified_slot, config.as_ref().cloned()); | ||||||
|             for result in filter_results { |             for result in filter_results { | ||||||
|                 notifier.notify( |                 notifier.notify( | ||||||
|                     Response { |                     Response { | ||||||
| @@ -220,16 +241,15 @@ impl RpcNotifier { | |||||||
| fn filter_account_result( | fn filter_account_result( | ||||||
|     result: Option<(Account, Slot)>, |     result: Option<(Account, Slot)>, | ||||||
|     last_notified_slot: Slot, |     last_notified_slot: Slot, | ||||||
|  |     encoding: Option<UiAccountEncoding>, | ||||||
| ) -> (Box<dyn Iterator<Item = UiAccount>>, Slot) { | ) -> (Box<dyn Iterator<Item = UiAccount>>, Slot) { | ||||||
|     if let Some((account, fork)) = result { |     if let Some((account, fork)) = result { | ||||||
|         // If fork < last_notified_slot this means that we last notified for a fork |         // If fork < last_notified_slot this means that we last notified for a fork | ||||||
|         // and should notify that the account state has been reverted. |         // and should notify that the account state has been reverted. | ||||||
|         if fork != last_notified_slot { |         if fork != last_notified_slot { | ||||||
|  |             let encoding = encoding.unwrap_or(UiAccountEncoding::Binary); | ||||||
|             return ( |             return ( | ||||||
|                 Box::new(iter::once(UiAccount::encode( |                 Box::new(iter::once(UiAccount::encode(account, encoding))), | ||||||
|                     account, |  | ||||||
|                     UiAccountEncoding::Binary, |  | ||||||
|                 ))), |  | ||||||
|                 fork, |                 fork, | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
| @@ -240,6 +260,7 @@ fn filter_account_result( | |||||||
| fn filter_signature_result( | fn filter_signature_result( | ||||||
|     result: Option<transaction::Result<()>>, |     result: Option<transaction::Result<()>>, | ||||||
|     last_notified_slot: Slot, |     last_notified_slot: Slot, | ||||||
|  |     _config: Option<()>, | ||||||
| ) -> (Box<dyn Iterator<Item = RpcSignatureResult>>, Slot) { | ) -> (Box<dyn Iterator<Item = RpcSignatureResult>>, Slot) { | ||||||
|     ( |     ( | ||||||
|         Box::new( |         Box::new( | ||||||
| @@ -254,14 +275,24 @@ fn filter_signature_result( | |||||||
| fn filter_program_results( | fn filter_program_results( | ||||||
|     accounts: Vec<(Pubkey, Account)>, |     accounts: Vec<(Pubkey, Account)>, | ||||||
|     last_notified_slot: Slot, |     last_notified_slot: Slot, | ||||||
|  |     config: Option<ProgramConfig>, | ||||||
| ) -> (Box<dyn Iterator<Item = RpcKeyedAccount>>, Slot) { | ) -> (Box<dyn Iterator<Item = RpcKeyedAccount>>, Slot) { | ||||||
|  |     let config = config.unwrap_or_default(); | ||||||
|  |     let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary); | ||||||
|  |     let filters = config.filters; | ||||||
|     ( |     ( | ||||||
|         Box::new( |         Box::new( | ||||||
|             accounts |             accounts | ||||||
|                 .into_iter() |                 .into_iter() | ||||||
|                 .map(|(pubkey, account)| RpcKeyedAccount { |                 .filter(move |(_, account)| { | ||||||
|  |                     filters.iter().all(|filter_type| match filter_type { | ||||||
|  |                         RpcFilterType::DataSize(size) => account.data.len() as u64 == *size, | ||||||
|  |                         RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data), | ||||||
|  |                     }) | ||||||
|  |                 }) | ||||||
|  |                 .map(move |(pubkey, account)| RpcKeyedAccount { | ||||||
|                     pubkey: pubkey.to_string(), |                     pubkey: pubkey.to_string(), | ||||||
|                     account: UiAccount::encode(account, UiAccountEncoding::Binary), |                     account: UiAccount::encode(account, encoding.clone()), | ||||||
|                 }), |                 }), | ||||||
|         ), |         ), | ||||||
|         last_notified_slot, |         last_notified_slot, | ||||||
| @@ -448,11 +479,13 @@ impl RpcSubscriptions { | |||||||
|     pub fn add_account_subscription( |     pub fn add_account_subscription( | ||||||
|         &self, |         &self, | ||||||
|         pubkey: Pubkey, |         pubkey: Pubkey, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcAccountInfoConfig>, | ||||||
|         sub_id: SubscriptionId, |         sub_id: SubscriptionId, | ||||||
|         subscriber: Subscriber<Response<UiAccount>>, |         subscriber: Subscriber<Response<UiAccount>>, | ||||||
|     ) { |     ) { | ||||||
|         let commitment_level = commitment |         let config = config.unwrap_or_default(); | ||||||
|  |         let commitment_level = config | ||||||
|  |             .commitment | ||||||
|             .unwrap_or_else(CommitmentConfig::single) |             .unwrap_or_else(CommitmentConfig::single) | ||||||
|             .commitment; |             .commitment; | ||||||
|         let slot = match commitment_level { |         let slot = match commitment_level { | ||||||
| @@ -498,10 +531,11 @@ impl RpcSubscriptions { | |||||||
|         add_subscription( |         add_subscription( | ||||||
|             &mut subscriptions, |             &mut subscriptions, | ||||||
|             pubkey, |             pubkey, | ||||||
|             commitment, |             config.commitment, | ||||||
|             sub_id, |             sub_id, | ||||||
|             subscriber, |             subscriber, | ||||||
|             last_notified_slot, |             last_notified_slot, | ||||||
|  |             config.encoding, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -522,11 +556,14 @@ impl RpcSubscriptions { | |||||||
|     pub fn add_program_subscription( |     pub fn add_program_subscription( | ||||||
|         &self, |         &self, | ||||||
|         program_id: Pubkey, |         program_id: Pubkey, | ||||||
|         commitment: Option<CommitmentConfig>, |         config: Option<RpcProgramAccountsConfig>, | ||||||
|         sub_id: SubscriptionId, |         sub_id: SubscriptionId, | ||||||
|         subscriber: Subscriber<Response<RpcKeyedAccount>>, |         subscriber: Subscriber<Response<RpcKeyedAccount>>, | ||||||
|     ) { |     ) { | ||||||
|         let commitment_level = commitment |         let config = config.unwrap_or_default(); | ||||||
|  |         let commitment_level = config | ||||||
|  |             .account_config | ||||||
|  |             .commitment | ||||||
|             .unwrap_or_else(CommitmentConfig::recent) |             .unwrap_or_else(CommitmentConfig::recent) | ||||||
|             .commitment; |             .commitment; | ||||||
|         let mut subscriptions = if commitment_level == CommitmentLevel::SingleGossip { |         let mut subscriptions = if commitment_level == CommitmentLevel::SingleGossip { | ||||||
| @@ -540,10 +577,14 @@ impl RpcSubscriptions { | |||||||
|         add_subscription( |         add_subscription( | ||||||
|             &mut subscriptions, |             &mut subscriptions, | ||||||
|             program_id, |             program_id, | ||||||
|             commitment, |             config.account_config.commitment, | ||||||
|             sub_id, |             sub_id, | ||||||
|             subscriber, |             subscriber, | ||||||
|             0, // last_notified_slot is not utilized for program subscriptions |             0, // last_notified_slot is not utilized for program subscriptions | ||||||
|  |             Some(ProgramConfig { | ||||||
|  |                 filters: config.filters.unwrap_or_default(), | ||||||
|  |                 encoding: config.account_config.encoding, | ||||||
|  |             }), | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -586,6 +627,7 @@ impl RpcSubscriptions { | |||||||
|             sub_id, |             sub_id, | ||||||
|             subscriber, |             subscriber, | ||||||
|             0, // last_notified_slot is not utilized for signature subscriptions |             0, // last_notified_slot is not utilized for signature subscriptions | ||||||
|  |             None, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -964,7 +1006,10 @@ pub(crate) mod tests { | |||||||
|         ); |         ); | ||||||
|         subscriptions.add_account_subscription( |         subscriptions.add_account_subscription( | ||||||
|             alice.pubkey(), |             alice.pubkey(), | ||||||
|             Some(CommitmentConfig::recent()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::recent()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|             sub_id.clone(), |             sub_id.clone(), | ||||||
|             subscriber, |             subscriber, | ||||||
|         ); |         ); | ||||||
| @@ -1368,7 +1413,7 @@ pub(crate) mod tests { | |||||||
|     #[test] |     #[test] | ||||||
|     #[serial] |     #[serial] | ||||||
|     fn test_add_and_remove_subscription() { |     fn test_add_and_remove_subscription() { | ||||||
|         let mut subscriptions: HashMap<u64, HashMap<SubscriptionId, SubscriptionData<()>>> = |         let mut subscriptions: HashMap<u64, HashMap<SubscriptionId, SubscriptionData<(), ()>>> = | ||||||
|             HashMap::new(); |             HashMap::new(); | ||||||
|  |  | ||||||
|         let num_keys = 5; |         let num_keys = 5; | ||||||
| @@ -1376,7 +1421,7 @@ pub(crate) mod tests { | |||||||
|             let (subscriber, _id_receiver, _transport_receiver) = |             let (subscriber, _id_receiver, _transport_receiver) = | ||||||
|                 Subscriber::new_test("notification"); |                 Subscriber::new_test("notification"); | ||||||
|             let sub_id = SubscriptionId::Number(key); |             let sub_id = SubscriptionId::Number(key); | ||||||
|             add_subscription(&mut subscriptions, key, None, sub_id, subscriber, 0); |             add_subscription(&mut subscriptions, key, None, sub_id, subscriber, 0, None); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Add another subscription to the "0" key |         // Add another subscription to the "0" key | ||||||
| @@ -1389,6 +1434,7 @@ pub(crate) mod tests { | |||||||
|             extra_sub_id.clone(), |             extra_sub_id.clone(), | ||||||
|             subscriber, |             subscriber, | ||||||
|             0, |             0, | ||||||
|  |             None, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         assert_eq!(subscriptions.len(), num_keys as usize); |         assert_eq!(subscriptions.len(), num_keys as usize); | ||||||
| @@ -1444,7 +1490,10 @@ pub(crate) mod tests { | |||||||
|         let sub_id0 = SubscriptionId::Number(0 as u64); |         let sub_id0 = SubscriptionId::Number(0 as u64); | ||||||
|         subscriptions.add_account_subscription( |         subscriptions.add_account_subscription( | ||||||
|             alice.pubkey(), |             alice.pubkey(), | ||||||
|             Some(CommitmentConfig::single_gossip()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::single_gossip()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|             sub_id0.clone(), |             sub_id0.clone(), | ||||||
|             subscriber0, |             subscriber0, | ||||||
|         ); |         ); | ||||||
| @@ -1509,7 +1558,10 @@ pub(crate) mod tests { | |||||||
|         let sub_id1 = SubscriptionId::Number(1 as u64); |         let sub_id1 = SubscriptionId::Number(1 as u64); | ||||||
|         subscriptions.add_account_subscription( |         subscriptions.add_account_subscription( | ||||||
|             alice.pubkey(), |             alice.pubkey(), | ||||||
|             Some(CommitmentConfig::single_gossip()), |             Some(RpcAccountInfoConfig { | ||||||
|  |                 commitment: Some(CommitmentConfig::single_gossip()), | ||||||
|  |                 encoding: None, | ||||||
|  |             }), | ||||||
|             sub_id1.clone(), |             sub_id1.clone(), | ||||||
|             subscriber1, |             subscriber1, | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -1256,7 +1256,10 @@ Subscribe to an account to receive notifications when the lamports or data for a | |||||||
| #### Parameters: | #### Parameters: | ||||||
|  |  | ||||||
| - `<string>` - account Pubkey, as base-58 encoded string | - `<string>` - account Pubkey, as base-58 encoded string | ||||||
| - `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|  |   - `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|  |   - (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary. | ||||||
|  |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`. | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
|  |  | ||||||
| @@ -1270,13 +1273,16 @@ Subscribe to an account to receive notifications when the lamports or data for a | |||||||
|  |  | ||||||
| {"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", {"commitment": "single"}]} | {"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", {"commitment": "single"}]} | ||||||
|  |  | ||||||
|  | {"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", {"encoding":"jsonParsed"}]} | ||||||
|  |  | ||||||
| // Result | // Result | ||||||
| {"jsonrpc": "2.0","result": 0,"id": 1} | {"jsonrpc": "2.0","result": 23784,"id": 1} | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### Notification Format: | #### Notification Format: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
|  | // Binary encoding | ||||||
| { | { | ||||||
|   "jsonrpc": "2.0", |   "jsonrpc": "2.0", | ||||||
|   "method": "accountNotification", |   "method": "accountNotification", | ||||||
| @@ -1286,10 +1292,41 @@ Subscribe to an account to receive notifications when the lamports or data for a | |||||||
|         "slot": 5199307 |         "slot": 5199307 | ||||||
|       }, |       }, | ||||||
|       "value": { |       "value": { | ||||||
|         "data": "9qRxMDwy1ntDhBBoiy4Na9uDLbRTSzUS989mpwz", |         "data": "11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHPXHRDEHrBesJhZyqnnq9qJeUuF7WHxiuLuL5twc38w2TXNLxnDbjmuR", | ||||||
|         "executable": false, |         "executable": false, | ||||||
|         "lamports": 33594, |         "lamports": 33594, | ||||||
|         "owner": "H9oaJujXETwkmjyweuqKPFtk2no4SumoU9A3hi3dC8U6", |         "owner": "11111111111111111111111111111111", | ||||||
|  |         "rentEpoch": 635 | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "subscription": 23784 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Parsed-JSON encoding | ||||||
|  | { | ||||||
|  |   "jsonrpc": "2.0", | ||||||
|  |   "method": "accountNotification", | ||||||
|  |   "params": { | ||||||
|  |     "result": { | ||||||
|  |       "context": { | ||||||
|  |         "slot": 5199307 | ||||||
|  |       }, | ||||||
|  |       "value": { | ||||||
|  |         "data": { | ||||||
|  |            "nonce": { | ||||||
|  |               "initialized": { | ||||||
|  |                  "authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX", | ||||||
|  |                  "blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k", | ||||||
|  |                  "feeCalculator": { | ||||||
|  |                     "lamportsPerSignature": 5000 | ||||||
|  |                  } | ||||||
|  |               } | ||||||
|  |            } | ||||||
|  |         }, | ||||||
|  |         "executable": false, | ||||||
|  |         "lamports": 33594, | ||||||
|  |         "owner": "11111111111111111111111111111111", | ||||||
|         "rentEpoch": 635 |         "rentEpoch": 635 | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| @@ -1327,7 +1364,11 @@ Subscribe to a program to receive notifications when the lamports or data for a | |||||||
| #### Parameters: | #### Parameters: | ||||||
|  |  | ||||||
| - `<string>` - program_id Pubkey, as base-58 encoded string | - `<string>` - program_id Pubkey, as base-58 encoded string | ||||||
| - `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | - `<object>` - (optional) Configuration object containing the following optional fields: | ||||||
|  |   - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) | ||||||
|  |   - (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary. | ||||||
|  |     Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`. | ||||||
|  |   - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results | ||||||
|  |  | ||||||
| #### Results: | #### Results: | ||||||
|  |  | ||||||
| @@ -1337,17 +1378,22 @@ Subscribe to a program to receive notifications when the lamports or data for a | |||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| // Request | // Request | ||||||
| {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq"]} | {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111"]} | ||||||
|  |  | ||||||
| {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq", {"commitment": "single"}]} | {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"commitment": "single"}]} | ||||||
|  |  | ||||||
|  | {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"encoding":"jsonParsed"}]} | ||||||
|  |  | ||||||
|  | {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"filters":[{"dataSize":80}]}]} | ||||||
|  |  | ||||||
| // Result | // Result | ||||||
| {"jsonrpc": "2.0","result": 0,"id": 1} | {"jsonrpc": "2.0","result": 24040,"id": 1} | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### Notification Format: | #### Notification Format: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
|  | // Binary encoding | ||||||
| { | { | ||||||
|   "jsonrpc": "2.0", |   "jsonrpc": "2.0", | ||||||
|   "method": "programNotification", |   "method": "programNotification", | ||||||
| @@ -1359,10 +1405,44 @@ Subscribe to a program to receive notifications when the lamports or data for a | |||||||
|       "value": { |       "value": { | ||||||
|         "pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq" |         "pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq" | ||||||
|         "account": { |         "account": { | ||||||
|           "data": "9qRxMDwy1ntDhBBoiy4Na9uDLbRTSzUS989m", |           "data": "11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHPXHRDEHrBesJhZyqnnq9qJeUuF7WHxiuLuL5twc38w2TXNLxnDbjmuR", | ||||||
|           "executable": false, |           "executable": false, | ||||||
|           "lamports": 33594, |           "lamports": 33594, | ||||||
|           "owner": "7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq", |           "owner": "11111111111111111111111111111111", | ||||||
|  |           "rentEpoch": 636 | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "subscription": 24040 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Parsed-JSON encoding | ||||||
|  | { | ||||||
|  |   "jsonrpc": "2.0", | ||||||
|  |   "method": "programNotification", | ||||||
|  |   "params": { | ||||||
|  |     "result": { | ||||||
|  |       "context": { | ||||||
|  |         "slot": 5208469 | ||||||
|  |       }, | ||||||
|  |       "value": { | ||||||
|  |         "pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq" | ||||||
|  |         "account": { | ||||||
|  |           "data": { | ||||||
|  |              "nonce": { | ||||||
|  |                 "initialized": { | ||||||
|  |                    "authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX", | ||||||
|  |                    "blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k", | ||||||
|  |                    "feeCalculator": { | ||||||
|  |                       "lamportsPerSignature": 5000 | ||||||
|  |                    } | ||||||
|  |                 } | ||||||
|  |              } | ||||||
|  |           }, | ||||||
|  |           "executable": false, | ||||||
|  |           "lamports": 33594, | ||||||
|  |           "owner": "11111111111111111111111111111111", | ||||||
|           "rentEpoch": 636 |           "rentEpoch": 636 | ||||||
|         }, |         }, | ||||||
|       } |       } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user