Add solana logs command

This commit is contained in:
Michael Vines
2020-11-20 13:52:58 -08:00
parent f96c4ec84e
commit 4ef2da0ff0
10 changed files with 785 additions and 105 deletions

View File

@ -404,6 +404,45 @@ pub type InnerInstructionsList = Vec<InnerInstructions>;
/// A list of log messages emitted during a transaction
pub type TransactionLogMessages = Vec<String>;
#[derive(Serialize, Deserialize, AbiExample, AbiEnumVisitor, Debug, PartialEq)]
pub enum TransactionLogCollectorFilter {
All,
AllWithVotes,
None,
OnlyMentionedAddresses,
}
impl Default for TransactionLogCollectorFilter {
fn default() -> Self {
Self::None
}
}
#[derive(AbiExample, Debug, Default)]
pub struct TransactionLogCollectorConfig {
pub mentioned_addresses: HashSet<Pubkey>,
pub filter: TransactionLogCollectorFilter,
}
#[derive(AbiExample, Clone, Debug)]
pub struct TransactionLogInfo {
pub signature: Signature,
pub result: Result<()>,
pub is_vote: bool,
pub log_messages: TransactionLogMessages,
}
#[derive(AbiExample, Default, Debug)]
pub struct TransactionLogCollector {
// All the logs collected for from this Bank. Exact contents depend on the
// active `TransactionLogCollectorFilter`
pub logs: Vec<TransactionLogInfo>,
// For each `mentioned_addresses`, maintain a list of indicies into `logs` to easily
// locate the logs from transactions that included the mentioned addresses.
pub mentioned_address_map: HashMap<Pubkey, Vec<usize>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HashAgeKind {
Extant,
@ -724,6 +763,13 @@ pub struct Bank {
transaction_debug_keys: Option<Arc<HashSet<Pubkey>>>,
// Global configuration for how transaction logs should be collected across all banks
pub transaction_log_collector_config: Arc<RwLock<TransactionLogCollectorConfig>>,
// Logs from transactions that this Bank executed collected according to the criteria in
// `transaction_log_collector_config`
pub transaction_log_collector: Arc<RwLock<TransactionLogCollector>>,
pub feature_set: Arc<FeatureSet>,
}
@ -868,6 +914,8 @@ impl Bank {
rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(),
cached_executors: RwLock::new((*parent.cached_executors.read().unwrap()).clone()),
transaction_debug_keys: parent.transaction_debug_keys.clone(),
transaction_log_collector_config: parent.transaction_log_collector_config.clone(),
transaction_log_collector: Arc::new(RwLock::new(TransactionLogCollector::default())),
feature_set: parent.feature_set.clone(),
};
@ -984,6 +1032,8 @@ impl Bank {
CachedExecutors::new(MAX_CACHED_EXECUTORS),
)))),
transaction_debug_keys: debug_keys,
transaction_log_collector_config: new(),
transaction_log_collector: new(),
feature_set: new(),
};
bank.finish_init(genesis_config, additional_builtins);
@ -2117,7 +2167,10 @@ impl Bank {
}
/// Run transactions against a frozen bank without committing the results
pub fn simulate_transaction(&self, transaction: Transaction) -> (Result<()>, Vec<String>) {
pub fn simulate_transaction(
&self,
transaction: Transaction,
) -> (Result<()>, TransactionLogMessages) {
assert!(self.is_frozen(), "simulation bank must be frozen");
let txs = &[transaction];
@ -2127,7 +2180,7 @@ impl Bank {
_loaded_accounts,
executed,
_inner_instructions,
transaction_logs,
log_messages,
_retryable_transactions,
_transaction_count,
_signature_count,
@ -2142,7 +2195,7 @@ impl Bank {
);
let transaction_result = executed[0].0.clone().map(|_| ());
let log_messages = transaction_logs
let log_messages = log_messages
.get(0)
.map_or(vec![], |messages| messages.to_vec());
@ -2182,6 +2235,7 @@ impl Bank {
&self.feature_set,
)
}
fn check_age(
&self,
txs: &[Transaction],
@ -2213,6 +2267,7 @@ impl Bank {
})
.collect()
}
fn check_signatures(
&self,
txs: &[Transaction],
@ -2249,6 +2304,7 @@ impl Bank {
})
.collect()
}
fn filter_by_vote_transactions(
&self,
txs: &[Transaction],
@ -2260,23 +2316,8 @@ impl Bank {
.zip(lock_results.into_iter())
.map(|((_, tx), lock_res)| {
if lock_res.0.is_ok() {
if tx.message.instructions.len() == 1 {
let instruction = &tx.message.instructions[0];
let program_pubkey =
tx.message.account_keys[instruction.program_id_index as usize];
if program_pubkey == solana_vote_program::id() {
if let Ok(vote_instruction) =
limited_deserialize::<VoteInstruction>(&instruction.data)
{
match vote_instruction {
VoteInstruction::Vote(_)
| VoteInstruction::VoteSwitch(_, _) => {
return lock_res;
}
_ => {}
}
}
}
if is_simple_vote_transaction(tx) {
return lock_res;
}
error_counters.not_allowed_during_cluster_maintenance += 1;
@ -2598,7 +2639,7 @@ impl Bank {
let mut signature_count: u64 = 0;
let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
Vec::with_capacity(txs.len());
let mut transaction_logs: Vec<TransactionLogMessages> = Vec::with_capacity(txs.len());
let mut transaction_log_messages = Vec::with_capacity(txs.len());
let bpf_compute_budget = self
.bpf_compute_budget
.unwrap_or_else(|| BpfComputeBudget::new(&self.feature_set));
@ -2649,7 +2690,7 @@ impl Bank {
.unwrap_or_default()
.into();
transaction_logs.push(log_messages);
transaction_log_messages.push(log_messages);
}
Self::compile_recorded_instructions(
@ -2686,7 +2727,10 @@ impl Bank {
let mut tx_count: u64 = 0;
let err_count = &mut error_counters.total;
for ((r, _hash_age_kind), tx) in executed.iter().zip(txs.iter()) {
let transaction_log_collector_config =
self.transaction_log_collector_config.read().unwrap();
for (i, ((r, _hash_age_kind), tx)) in executed.iter().zip(txs.iter()).enumerate() {
if let Some(debug_keys) = &self.transaction_debug_keys {
for key in &tx.message.account_keys {
if debug_keys.contains(key) {
@ -2695,6 +2739,50 @@ impl Bank {
}
}
}
if transaction_log_collector_config.filter != TransactionLogCollectorFilter::None {
let mut transaction_log_collector = self.transaction_log_collector.write().unwrap();
let transaction_log_index = transaction_log_collector.logs.len();
let mut mentioned_address = false;
if !transaction_log_collector_config
.mentioned_addresses
.is_empty()
{
for key in &tx.message.account_keys {
if transaction_log_collector_config
.mentioned_addresses
.contains(key)
{
transaction_log_collector
.mentioned_address_map
.entry(*key)
.or_default()
.push(transaction_log_index);
mentioned_address = true;
}
}
}
let is_vote = is_simple_vote_transaction(tx);
let store = match transaction_log_collector_config.filter {
TransactionLogCollectorFilter::All => !is_vote || mentioned_address,
TransactionLogCollectorFilter::AllWithVotes => true,
TransactionLogCollectorFilter::None => false,
TransactionLogCollectorFilter::OnlyMentionedAddresses => mentioned_address,
};
if store {
transaction_log_collector.logs.push(TransactionLogInfo {
signature: tx.signatures[0],
result: r.clone(),
is_vote,
log_messages: transaction_log_messages.get(i).cloned().unwrap_or_default(),
});
}
}
if r.is_ok() {
tx_count += 1;
} else {
@ -2716,7 +2804,7 @@ impl Bank {
loaded_accounts,
executed,
inner_instructions,
transaction_logs,
transaction_log_messages,
retryable_txs,
tx_count,
signature_count,
@ -3673,6 +3761,26 @@ impl Bank {
.load_by_program_slot(self.slot(), Some(program_id))
}
pub fn get_transaction_logs(
&self,
address: Option<&Pubkey>,
) -> Option<Vec<TransactionLogInfo>> {
let transaction_log_collector = self.transaction_log_collector.read().unwrap();
match address {
None => Some(transaction_log_collector.logs.clone()),
Some(address) => transaction_log_collector
.mentioned_address_map
.get(address)
.map(|log_indices| {
log_indices
.iter()
.map(|i| transaction_log_collector.logs[*i].clone())
.collect()
}),
}
}
pub fn get_all_accounts_modified_since_parent(&self) -> Vec<(Pubkey, Account)> {
self.rc.accounts.load_by_program_slot(self.slot(), None)
}
@ -4377,6 +4485,21 @@ pub fn goto_end_of_slot(bank: &mut Bank) {
}
}
fn is_simple_vote_transaction(transaction: &Transaction) -> bool {
if transaction.message.instructions.len() == 1 {
let instruction = &transaction.message.instructions[0];
let program_pubkey =
transaction.message.account_keys[instruction.program_id_index as usize];
if program_pubkey == solana_vote_program::id() {
if let Ok(vote_instruction) = limited_deserialize::<VoteInstruction>(&instruction.data)
{
return matches!(vote_instruction, VoteInstruction::Vote(_) | VoteInstruction::VoteSwitch(_, _));
}
}
}
false
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;