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) Configuration object containing the following optional fields:
|
||||||
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
- `<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