Add solana logs
command
This commit is contained in:
@ -6,8 +6,13 @@ use jsonrpc_derive::rpc;
|
||||
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
|
||||
use solana_account_decoder::UiAccount;
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSignatureSubscribeConfig},
|
||||
rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult, SlotInfo},
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSignatureSubscribeConfig,
|
||||
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
||||
},
|
||||
rpc_response::{
|
||||
Response as RpcResponse, RpcKeyedAccount, RpcLogsResponse, RpcSignatureResult, SlotInfo,
|
||||
},
|
||||
};
|
||||
#[cfg(test)]
|
||||
use solana_runtime::bank_forks::BankForks;
|
||||
@ -75,6 +80,24 @@ pub trait RpcSolPubSub {
|
||||
fn program_unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId)
|
||||
-> Result<bool>;
|
||||
|
||||
// Get logs for all transactions that reference the specified address
|
||||
#[pubsub(subscription = "logsNotification", subscribe, name = "logsSubscribe")]
|
||||
fn logs_subscribe(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<RpcLogsResponse>>,
|
||||
filter: RpcTransactionLogsFilter,
|
||||
config: RpcTransactionLogsConfig,
|
||||
);
|
||||
|
||||
// Unsubscribe from logs notification subscription.
|
||||
#[pubsub(
|
||||
subscription = "logsNotification",
|
||||
unsubscribe,
|
||||
name = "logsUnsubscribe"
|
||||
)]
|
||||
fn logs_unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool>;
|
||||
|
||||
// Get notification when signature is verified
|
||||
// Accepts signature parameter as base-58 encoded string
|
||||
#[pubsub(
|
||||
@ -241,6 +264,67 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn logs_subscribe(
|
||||
&self,
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<RpcLogsResponse>>,
|
||||
filter: RpcTransactionLogsFilter,
|
||||
config: RpcTransactionLogsConfig,
|
||||
) {
|
||||
info!("logs_subscribe");
|
||||
|
||||
let (address, include_votes) = match filter {
|
||||
RpcTransactionLogsFilter::All => (None, false),
|
||||
RpcTransactionLogsFilter::AllWithVotes => (None, true),
|
||||
RpcTransactionLogsFilter::Mentions(addresses) => {
|
||||
match addresses.len() {
|
||||
1 => match param::<Pubkey>(&addresses[0], "mentions") {
|
||||
Ok(address) => (Some(address), false),
|
||||
Err(e) => {
|
||||
subscriber.reject(e).unwrap();
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Room is reserved in the API to support multiple addresses, but for now
|
||||
// the implementation only supports one
|
||||
subscriber
|
||||
.reject(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "Invalid Request: Only 1 address supported".into(),
|
||||
data: None,
|
||||
})
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let id = self.uid.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
self.subscriptions.add_logs_subscription(
|
||||
address,
|
||||
include_votes,
|
||||
config.commitment,
|
||||
sub_id,
|
||||
subscriber,
|
||||
)
|
||||
}
|
||||
|
||||
fn logs_unsubscribe(&self, _meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool> {
|
||||
info!("logs_unsubscribe: id={:?}", id);
|
||||
if self.subscriptions.remove_logs_subscription(&id) {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "Invalid Request: Subscription id does not exist".into(),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn signature_subscribe(
|
||||
&self,
|
||||
_meta: Self::Metadata,
|
||||
|
@ -17,12 +17,14 @@ use solana_client::{
|
||||
rpc_filter::RpcFilterType,
|
||||
rpc_response::{
|
||||
ProcessedSignatureResult, ReceivedSignatureResult, Response, RpcKeyedAccount,
|
||||
RpcResponseContext, RpcSignatureResult, SlotInfo,
|
||||
RpcLogsResponse, RpcResponseContext, RpcSignatureResult, SlotInfo,
|
||||
},
|
||||
};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
bank::Bank,
|
||||
bank::{
|
||||
Bank, TransactionLogCollectorConfig, TransactionLogCollectorFilter, TransactionLogInfo,
|
||||
},
|
||||
bank_forks::BankForks,
|
||||
commitment::{BlockCommitmentCache, CommitmentSlots},
|
||||
};
|
||||
@ -35,16 +37,16 @@ use solana_sdk::{
|
||||
transaction,
|
||||
};
|
||||
use solana_vote_program::vote_state::Vote;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{Receiver, RecvTimeoutError, SendError, Sender},
|
||||
};
|
||||
use std::thread::{Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{Receiver, RecvTimeoutError, SendError, Sender},
|
||||
},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
thread::{Builder, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
// Stuck on tokio 0.1 until the jsonrpc-pubsub crate upgrades to tokio 0.2
|
||||
@ -52,6 +54,28 @@ use tokio_01::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
|
||||
|
||||
const RECEIVE_DELAY_MILLIS: u64 = 100;
|
||||
|
||||
trait BankGetTransactionLogsAdapter {
|
||||
fn get_transaction_logs_adapter(
|
||||
&self,
|
||||
stuff: &(Option<Pubkey>, bool),
|
||||
) -> Option<Vec<TransactionLogInfo>>;
|
||||
}
|
||||
|
||||
impl BankGetTransactionLogsAdapter for Bank {
|
||||
fn get_transaction_logs_adapter(
|
||||
&self,
|
||||
config: &(Option<Pubkey>, bool),
|
||||
) -> Option<Vec<TransactionLogInfo>> {
|
||||
let mut logs = self.get_transaction_logs(config.0.as_ref());
|
||||
|
||||
if config.0.is_none() && !config.1 {
|
||||
// Filter out votes if the subscriber doesn't want them
|
||||
logs = logs.map(|logs| logs.into_iter().filter(|log| !log.is_vote).collect());
|
||||
}
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
// A more human-friendly version of Vote, with the bank state signature base58 encoded.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RpcVote {
|
||||
@ -103,6 +127,12 @@ type RpcAccountSubscriptions = RwLock<
|
||||
HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>, UiAccountEncoding>>,
|
||||
>,
|
||||
>;
|
||||
type RpcLogsSubscriptions = RwLock<
|
||||
HashMap<
|
||||
(Option<Pubkey>, bool),
|
||||
HashMap<SubscriptionId, SubscriptionData<Response<RpcLogsResponse>, ()>>,
|
||||
>,
|
||||
>;
|
||||
type RpcProgramSubscriptions = RwLock<
|
||||
HashMap<
|
||||
Pubkey,
|
||||
@ -182,7 +212,7 @@ where
|
||||
S: Clone + Serialize,
|
||||
B: Fn(&Bank, &K) -> X,
|
||||
F: Fn(X, &K, Slot, Option<T>, Arc<Bank>) -> (Box<dyn Iterator<Item = S>>, Slot),
|
||||
X: Clone + Serialize + Default,
|
||||
X: Clone + Default,
|
||||
T: Clone,
|
||||
{
|
||||
let mut notified_set: HashSet<SubscriptionId> = HashSet::new();
|
||||
@ -321,12 +351,34 @@ fn filter_program_results(
|
||||
(accounts, last_notified_slot)
|
||||
}
|
||||
|
||||
fn filter_logs_results(
|
||||
logs: Option<Vec<TransactionLogInfo>>,
|
||||
_address: &(Option<Pubkey>, bool),
|
||||
last_notified_slot: Slot,
|
||||
_config: Option<()>,
|
||||
_bank: Arc<Bank>,
|
||||
) -> (Box<dyn Iterator<Item = RpcLogsResponse>>, Slot) {
|
||||
match logs {
|
||||
None => (Box::new(iter::empty()), last_notified_slot),
|
||||
Some(logs) => (
|
||||
Box::new(logs.into_iter().map(|log| RpcLogsResponse {
|
||||
signature: log.signature.to_string(),
|
||||
err: log.result.err(),
|
||||
logs: log.log_messages,
|
||||
})),
|
||||
last_notified_slot,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Subscriptions {
|
||||
account_subscriptions: Arc<RpcAccountSubscriptions>,
|
||||
program_subscriptions: Arc<RpcProgramSubscriptions>,
|
||||
logs_subscriptions: Arc<RpcLogsSubscriptions>,
|
||||
signature_subscriptions: Arc<RpcSignatureSubscriptions>,
|
||||
gossip_account_subscriptions: Arc<RpcAccountSubscriptions>,
|
||||
gossip_logs_subscriptions: Arc<RpcLogsSubscriptions>,
|
||||
gossip_program_subscriptions: Arc<RpcProgramSubscriptions>,
|
||||
gossip_signature_subscriptions: Arc<RpcSignatureSubscriptions>,
|
||||
slot_subscriptions: Arc<RpcSlotSubscriptions>,
|
||||
@ -383,9 +435,11 @@ impl RpcSubscriptions {
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
let account_subscriptions = Arc::new(RpcAccountSubscriptions::default());
|
||||
let logs_subscriptions = Arc::new(RpcLogsSubscriptions::default());
|
||||
let program_subscriptions = Arc::new(RpcProgramSubscriptions::default());
|
||||
let signature_subscriptions = Arc::new(RpcSignatureSubscriptions::default());
|
||||
let gossip_account_subscriptions = Arc::new(RpcAccountSubscriptions::default());
|
||||
let gossip_logs_subscriptions = Arc::new(RpcLogsSubscriptions::default());
|
||||
let gossip_program_subscriptions = Arc::new(RpcProgramSubscriptions::default());
|
||||
let gossip_signature_subscriptions = Arc::new(RpcSignatureSubscriptions::default());
|
||||
let slot_subscriptions = Arc::new(RpcSlotSubscriptions::default());
|
||||
@ -398,9 +452,11 @@ impl RpcSubscriptions {
|
||||
let exit_clone = exit.clone();
|
||||
let subscriptions = Subscriptions {
|
||||
account_subscriptions,
|
||||
logs_subscriptions,
|
||||
program_subscriptions,
|
||||
signature_subscriptions,
|
||||
gossip_account_subscriptions,
|
||||
gossip_logs_subscriptions,
|
||||
gossip_program_subscriptions,
|
||||
gossip_signature_subscriptions,
|
||||
slot_subscriptions,
|
||||
@ -474,6 +530,25 @@ impl RpcSubscriptions {
|
||||
)
|
||||
}
|
||||
|
||||
fn check_logs(
|
||||
address_with_enable_votes_flag: &(Option<Pubkey>, bool),
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
logs_subscriptions: Arc<RpcLogsSubscriptions>,
|
||||
notifier: &RpcNotifier,
|
||||
commitment_slots: &CommitmentSlots,
|
||||
) -> HashSet<SubscriptionId> {
|
||||
let subscriptions = logs_subscriptions.read().unwrap();
|
||||
check_commitment_and_notify(
|
||||
&subscriptions,
|
||||
address_with_enable_votes_flag,
|
||||
bank_forks,
|
||||
commitment_slots,
|
||||
Bank::get_transaction_logs_adapter,
|
||||
filter_logs_results,
|
||||
notifier,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_program(
|
||||
program_id: &Pubkey,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
@ -647,6 +722,114 @@ impl RpcSubscriptions {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_logs_subscription(
|
||||
&self,
|
||||
address: Option<Pubkey>,
|
||||
include_votes: bool,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<RpcLogsResponse>>,
|
||||
) {
|
||||
let commitment = commitment.unwrap_or_else(CommitmentConfig::single_gossip);
|
||||
|
||||
{
|
||||
let mut subscriptions = if commitment.commitment == CommitmentLevel::SingleGossip {
|
||||
self.subscriptions
|
||||
.gossip_logs_subscriptions
|
||||
.write()
|
||||
.unwrap()
|
||||
} else {
|
||||
self.subscriptions.logs_subscriptions.write().unwrap()
|
||||
};
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
(address, include_votes),
|
||||
commitment,
|
||||
sub_id,
|
||||
subscriber,
|
||||
0, // last_notified_slot is not utilized for logs subscriptions
|
||||
None,
|
||||
);
|
||||
}
|
||||
self.update_bank_transaction_log_keys();
|
||||
}
|
||||
|
||||
pub fn remove_logs_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
let mut removed = {
|
||||
let mut subscriptions = self.subscriptions.logs_subscriptions.write().unwrap();
|
||||
remove_subscription(&mut subscriptions, id)
|
||||
};
|
||||
|
||||
if !removed {
|
||||
removed = {
|
||||
let mut subscriptions = self
|
||||
.subscriptions
|
||||
.gossip_logs_subscriptions
|
||||
.write()
|
||||
.unwrap();
|
||||
remove_subscription(&mut subscriptions, id)
|
||||
};
|
||||
}
|
||||
|
||||
if removed {
|
||||
self.update_bank_transaction_log_keys();
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
fn update_bank_transaction_log_keys(&self) {
|
||||
// Grab a write lock for both `logs_subscriptions` and `gossip_logs_subscriptions`, to
|
||||
// ensure `Bank::transaction_log_collector_config` is updated atomically.
|
||||
let logs_subscriptions = self.subscriptions.logs_subscriptions.write().unwrap();
|
||||
let gossip_logs_subscriptions = self
|
||||
.subscriptions
|
||||
.gossip_logs_subscriptions
|
||||
.write()
|
||||
.unwrap();
|
||||
|
||||
let mut config = TransactionLogCollectorConfig::default();
|
||||
|
||||
let mut all = false;
|
||||
let mut all_with_votes = false;
|
||||
let mut mentioned_address = false;
|
||||
for (address, with_votes) in logs_subscriptions
|
||||
.keys()
|
||||
.chain(gossip_logs_subscriptions.keys())
|
||||
{
|
||||
match address {
|
||||
None => {
|
||||
if *with_votes {
|
||||
all_with_votes = true;
|
||||
} else {
|
||||
all = true;
|
||||
}
|
||||
}
|
||||
Some(address) => {
|
||||
config.mentioned_addresses.insert(*address);
|
||||
mentioned_address = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
config.filter = if all_with_votes {
|
||||
TransactionLogCollectorFilter::AllWithVotes
|
||||
} else if all {
|
||||
TransactionLogCollectorFilter::All
|
||||
} else if mentioned_address {
|
||||
TransactionLogCollectorFilter::OnlyMentionedAddresses
|
||||
} else {
|
||||
TransactionLogCollectorFilter::None
|
||||
};
|
||||
|
||||
*self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.root_bank()
|
||||
.transaction_log_collector_config
|
||||
.write()
|
||||
.unwrap() = config;
|
||||
}
|
||||
|
||||
pub fn add_signature_subscription(
|
||||
&self,
|
||||
signature: Signature,
|
||||
@ -847,8 +1030,9 @@ impl RpcSubscriptions {
|
||||
}
|
||||
}
|
||||
NotificationEntry::Bank(commitment_slots) => {
|
||||
RpcSubscriptions::notify_accounts_programs_signatures(
|
||||
RpcSubscriptions::notify_accounts_logs_programs_signatures(
|
||||
&subscriptions.account_subscriptions,
|
||||
&subscriptions.logs_subscriptions,
|
||||
&subscriptions.program_subscriptions,
|
||||
&subscriptions.signature_subscriptions,
|
||||
&bank_forks,
|
||||
@ -894,8 +1078,9 @@ impl RpcSubscriptions {
|
||||
highest_confirmed_slot: slot,
|
||||
..CommitmentSlots::default()
|
||||
};
|
||||
RpcSubscriptions::notify_accounts_programs_signatures(
|
||||
RpcSubscriptions::notify_accounts_logs_programs_signatures(
|
||||
&subscriptions.gossip_account_subscriptions,
|
||||
&subscriptions.gossip_logs_subscriptions,
|
||||
&subscriptions.gossip_program_subscriptions,
|
||||
&subscriptions.gossip_signature_subscriptions,
|
||||
bank_forks,
|
||||
@ -905,8 +1090,9 @@ impl RpcSubscriptions {
|
||||
);
|
||||
}
|
||||
|
||||
fn notify_accounts_programs_signatures(
|
||||
fn notify_accounts_logs_programs_signatures(
|
||||
account_subscriptions: &Arc<RpcAccountSubscriptions>,
|
||||
logs_subscriptions: &Arc<RpcLogsSubscriptions>,
|
||||
program_subscriptions: &Arc<RpcProgramSubscriptions>,
|
||||
signature_subscriptions: &Arc<RpcSignatureSubscriptions>,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
@ -932,6 +1118,24 @@ impl RpcSubscriptions {
|
||||
}
|
||||
accounts_time.stop();
|
||||
|
||||
let mut logs_time = Measure::start("logs");
|
||||
let logs: Vec<_> = {
|
||||
let subs = logs_subscriptions.read().unwrap();
|
||||
subs.keys().cloned().collect()
|
||||
};
|
||||
let mut num_logs_notified = 0;
|
||||
for address in &logs {
|
||||
num_logs_notified += Self::check_logs(
|
||||
address,
|
||||
bank_forks,
|
||||
logs_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&commitment_slots,
|
||||
)
|
||||
.len();
|
||||
}
|
||||
logs_time.stop();
|
||||
|
||||
let mut programs_time = Measure::start("programs");
|
||||
let programs: Vec<_> = {
|
||||
let subs = program_subscriptions.read().unwrap();
|
||||
@ -990,6 +1194,9 @@ impl RpcSubscriptions {
|
||||
("num_account_subscriptions", pubkeys.len(), i64),
|
||||
("num_account_pubkeys_notified", num_pubkeys_notified, i64),
|
||||
("accounts_time", accounts_time.as_us() as i64, i64),
|
||||
("num_logs_subscriptions", logs.len(), i64),
|
||||
("num_logs_notified", num_logs_notified, i64),
|
||||
("logs_time", logs_time.as_us() as i64, i64),
|
||||
("num_program_subscriptions", programs.len(), i64),
|
||||
("num_programs_notified", num_programs_notified, i64),
|
||||
("programs_time", programs_time.as_us() as i64, i64),
|
||||
|
Reference in New Issue
Block a user