Cache re-usable work performed by the loader (#12135)

This commit is contained in:
Jack May
2020-09-14 17:42:37 -07:00
committed by GitHub
parent af2262cbba
commit 3278d78f08
11 changed files with 543 additions and 249 deletions

View File

@ -13,7 +13,7 @@ use crate::{
builtins::get_builtins,
epoch_stakes::{EpochStakes, NodeVoteAccounts},
log_collector::LogCollector,
message_processor::MessageProcessor,
message_processor::{Executors, MessageProcessor},
nonce_utils,
rent_collector::RentCollector,
stakes::Stakes,
@ -35,7 +35,9 @@ use solana_sdk::{
Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND,
MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, SECONDS_PER_DAY,
},
entrypoint_native::{ComputeBudget, ProcessInstruction, ProcessInstructionWithContext},
entrypoint_native::{
ComputeBudget, Executor, ProcessInstruction, ProcessInstructionWithContext,
},
epoch_info::EpochInfo,
epoch_schedule::EpochSchedule,
fee_calculator::{FeeCalculator, FeeRateGovernor},
@ -44,6 +46,7 @@ use solana_sdk::{
hash::{extend_and_hash, hashv, Hash},
incinerator,
inflation::Inflation,
message::Message,
native_loader,
native_token::sol_to_lamports,
nonce,
@ -143,6 +146,58 @@ impl Builtin {
}
}
const MAX_CACHED_EXECUTORS: usize = 100; // 10 MB assuming programs are around 100k
/// LFU Cache of executors
#[derive(AbiExample)]
struct CachedExecutors {
max: usize,
executors: HashMap<Pubkey, (AtomicU64, Arc<dyn Executor>)>,
}
impl Default for CachedExecutors {
fn default() -> Self {
Self {
max: MAX_CACHED_EXECUTORS,
executors: HashMap::new(),
}
}
}
impl CachedExecutors {
pub fn new(max: usize) -> Self {
Self {
max,
executors: HashMap::new(),
}
}
pub fn get(&self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors.get(pubkey).map(|(count, executor)| {
count.fetch_add(1, Ordering::Relaxed);
executor.clone()
})
}
pub fn put(&mut self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
if !self.executors.contains_key(pubkey) {
if self.executors.len() >= self.max {
let mut least = u64::MAX;
let default_key = Pubkey::default();
let mut least_key = &default_key;
for (key, (count, _)) in self.executors.iter() {
let count = count.load(Ordering::Relaxed);
if count < least {
least = count;
least_key = key;
}
}
let least_key = *least_key;
let _ = self.executors.remove(&least_key);
}
let _ = self
.executors
.insert(*pubkey, (AtomicU64::new(0), executor));
}
}
}
#[derive(Default)]
pub struct BankRc {
/// where all the Accounts are stored
@ -333,9 +388,9 @@ pub(crate) struct BankFieldsToSerialize<'a> {
}
/// Manager for the state of all accounts and programs after processing its entries.
/// AbiExample is needed even without Serialize/Deserialize; actual (de-)serializeaion
/// AbiExample is needed even without Serialize/Deserialize; actual (de-)serialization
/// are implemented elsewhere for versioning
#[derive(Default, AbiExample)]
#[derive(AbiExample, Default)]
pub struct Bank {
/// References to accounts, parent and signature status
pub rc: BankRc,
@ -458,6 +513,9 @@ pub struct Bank {
// this is temporary field only to remove rewards_pool entirely
pub rewards_pool_pubkeys: Arc<HashSet<Pubkey>>,
/// Cached executors
cached_executors: Arc<RwLock<CachedExecutors>>,
}
impl Default for BlockhashQueue {
@ -536,7 +594,7 @@ impl Bank {
epoch,
blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()),
// TODO: clean this up, soo much special-case copying...
// TODO: clean this up, so much special-case copying...
hashes_per_tick: parent.hashes_per_tick,
ticks_per_slot: parent.ticks_per_slot,
ns_per_slot: parent.ns_per_slot,
@ -575,6 +633,7 @@ impl Bank {
cluster_type: parent.cluster_type,
lazy_rent_collection: AtomicBool::new(parent.lazy_rent_collection.load(Relaxed)),
rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(),
cached_executors: parent.cached_executors.clone(),
};
datapoint_info!(
@ -676,6 +735,7 @@ impl Bank {
cluster_type: Some(genesis_config.cluster_type),
lazy_rent_collection: new(),
rewards_pool_pubkeys: new(),
cached_executors: Arc::new(RwLock::new(CachedExecutors::new(MAX_CACHED_EXECUTORS))),
};
bank.finish_init(genesis_config);
@ -1804,6 +1864,49 @@ impl Bank {
});
}
/// Get any cached executors needed by the transaction
fn get_executors(
&self,
message: &Message,
loaders: &[Vec<(Pubkey, Account)>],
) -> Rc<RefCell<Executors>> {
let mut num_executors = message.account_keys.len();
for instruction_loaders in loaders.iter() {
num_executors += instruction_loaders.len();
}
let mut executors = HashMap::with_capacity(num_executors);
let cache = self.cached_executors.read().unwrap();
for key in message.account_keys.iter() {
if let Some(executor) = cache.get(key) {
executors.insert(*key, executor);
}
}
for instruction_loaders in loaders.iter() {
for (key, _) in instruction_loaders.iter() {
if let Some(executor) = cache.get(key) {
executors.insert(*key, executor);
}
}
}
Rc::new(RefCell::new(Executors {
executors,
is_dirty: false,
}))
}
/// Add executors back to the bank's cache if modified
fn update_executors(&self, executors: Rc<RefCell<Executors>>) {
let executors = executors.borrow();
if executors.is_dirty {
let mut cache = self.cached_executors.write().unwrap();
for (key, executor) in executors.executors.iter() {
cache.put(key, (*executor).clone());
}
}
}
#[allow(clippy::type_complexity)]
pub fn load_and_execute_transactions(
&self,
@ -1861,6 +1964,8 @@ impl Bank {
(Ok((accounts, loaders, _rents)), hash_age_kind) => {
signature_count += u64::from(tx.message().header.num_required_signatures);
let executors = self.get_executors(&tx.message, &loaders);
let (account_refcells, loader_refcells) =
Self::accounts_to_refcells(accounts, loaders);
@ -1870,6 +1975,7 @@ impl Bank {
&account_refcells,
&self.rent_collector,
log_collector.clone(),
executors.clone(),
);
Self::refcells_to_accounts(
@ -1879,6 +1985,8 @@ impl Bank {
loader_refcells,
);
self.update_executors(executors);
if let Err(TransactionError::InstructionError(_, _)) = &process_result {
error_counters.instruction_error += 1;
}
@ -3466,6 +3574,7 @@ mod tests {
account::KeyedAccount,
account_utils::StateMut,
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT},
entrypoint_native::InvokeContext,
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
genesis_config::create_genesis_config,
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
@ -8629,4 +8738,118 @@ mod tests {
info!("NOT-asserting overflowing capitalization for bank0");
}
}
struct TestExecutor {}
impl Executor for TestExecutor {
fn execute(
&self,
_program_id: &Pubkey,
_keyed_accounts: &[KeyedAccount],
_instruction_data: &[u8],
_invoke_context: &mut dyn InvokeContext,
) -> std::result::Result<(), InstructionError> {
Ok(())
}
}
#[test]
fn test_cached_executors() {
let key1 = Pubkey::new_rand();
let key2 = Pubkey::new_rand();
let key3 = Pubkey::new_rand();
let key4 = Pubkey::new_rand();
let executor: Arc<dyn Executor> = Arc::new(TestExecutor {});
let mut cache = CachedExecutors::new(3);
cache.put(&key1, executor.clone());
cache.put(&key2, executor.clone());
cache.put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
assert!(cache.get(&key3).is_some());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
cache.put(&key4, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_some());
assert!(cache.get(&key3).is_none());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
assert!(cache.get(&key4).is_some());
cache.put(&key3, executor.clone());
assert!(cache.get(&key1).is_some());
assert!(cache.get(&key2).is_none());
assert!(cache.get(&key3).is_some());
assert!(cache.get(&key4).is_some());
}
#[test]
fn test_bank_executor_cache() {
solana_logger::setup();
let (genesis_config, _) = create_genesis_config(1);
let bank = Bank::new(&genesis_config);
let key1 = Pubkey::new_rand();
let key2 = Pubkey::new_rand();
let key3 = Pubkey::new_rand();
let key4 = Pubkey::new_rand();
let executor: Arc<dyn Executor> = Arc::new(TestExecutor {});
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 1,
},
account_keys: vec![key1, key2],
recent_blockhash: Hash::default(),
instructions: vec![],
};
let loaders = &[
vec![(key3, Account::default()), (key4, Account::default())],
vec![(key1, Account::default())],
];
// don't do any work if not dirty
let mut executors = Executors::default();
executors.insert(key1, executor.clone());
executors.insert(key2, executor.clone());
executors.insert(key3, executor.clone());
executors.insert(key4, executor.clone());
let executors = Rc::new(RefCell::new(executors));
executors.borrow_mut().is_dirty = false;
bank.update_executors(executors);
let executors = bank.get_executors(&message, loaders);
assert_eq!(executors.borrow().executors.len(), 0);
// do work
let mut executors = Executors::default();
executors.insert(key1, executor.clone());
executors.insert(key2, executor.clone());
executors.insert(key3, executor.clone());
executors.insert(key4, executor.clone());
let executors = Rc::new(RefCell::new(executors));
bank.update_executors(executors);
let executors = bank.get_executors(&message, loaders);
assert_eq!(executors.borrow().executors.len(), 4);
assert!(executors.borrow().executors.contains_key(&key1));
assert!(executors.borrow().executors.contains_key(&key2));
assert!(executors.borrow().executors.contains_key(&key3));
assert!(executors.borrow().executors.contains_key(&key4));
// Check inheritance
let bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::new_rand(), 1);
let executors = bank.get_executors(&message, loaders);
assert_eq!(executors.borrow().executors.len(), 4);
assert!(executors.borrow().executors.contains_key(&key1));
assert!(executors.borrow().executors.contains_key(&key2));
assert!(executors.borrow().executors.contains_key(&key3));
assert!(executors.borrow().executors.contains_key(&key4));
}
}

View File

@ -8,7 +8,7 @@ use solana_sdk::{
clock::Epoch,
entrypoint_native::{
ComputeBudget, ComputeMeter, ErasedProcessInstruction, ErasedProcessInstructionWithContext,
InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
},
instruction::{CompiledInstruction, InstructionError},
message::Message,
@ -18,7 +18,29 @@ use solana_sdk::{
system_program,
transaction::TransactionError,
};
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
pub struct Executors {
pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
pub is_dirty: bool,
}
impl Default for Executors {
fn default() -> Self {
Self {
executors: HashMap::default(),
is_dirty: false,
}
}
}
impl Executors {
pub fn insert(&mut self, key: Pubkey, executor: Arc<dyn Executor>) {
let _ = self.executors.insert(key, executor);
self.is_dirty = true;
}
pub fn get(&self, key: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors.get(key).cloned()
}
}
// The relevant state of an account before an Instruction executes, used
// to verify account integrity after the Instruction completes
@ -182,6 +204,7 @@ pub struct ThisInvokeContext {
is_cross_program_supported: bool,
compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
executors: Rc<RefCell<Executors>>,
}
impl ThisInvokeContext {
pub fn new(
@ -192,6 +215,7 @@ impl ThisInvokeContext {
log_collector: Option<Rc<LogCollector>>,
is_cross_program_supported: bool,
compute_budget: ComputeBudget,
executors: Rc<RefCell<Executors>>,
) -> Self {
let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth);
program_ids.push(*program_id);
@ -206,6 +230,7 @@ impl ThisInvokeContext {
compute_meter: Rc::new(RefCell::new(ThisComputeMeter {
remaining: compute_budget.max_units,
})),
executors,
}
}
}
@ -262,6 +287,12 @@ impl InvokeContext for ThisInvokeContext {
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
self.compute_meter.clone()
}
fn add_executor(&mut self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
self.executors.borrow_mut().insert(*pubkey, executor);
}
fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors.borrow().get(&pubkey)
}
}
pub struct ThisLogger {
log_collector: Option<Rc<LogCollector>>,
@ -633,6 +664,7 @@ impl MessageProcessor {
accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>,
) -> Result<(), InstructionError> {
let pre_accounts = Self::create_pre_accounts(message, instruction, accounts);
let mut invoke_context = ThisInvokeContext::new(
@ -643,6 +675,7 @@ impl MessageProcessor {
log_collector,
self.is_cross_program_supported,
self.compute_budget,
executors,
);
let keyed_accounts =
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
@ -668,6 +701,7 @@ impl MessageProcessor {
accounts: &[Rc<RefCell<Account>>],
rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>,
) -> Result<(), TransactionError> {
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
self.execute_instruction(
@ -677,6 +711,7 @@ impl MessageProcessor {
accounts,
rent_collector,
log_collector.clone(),
executors.clone(),
)
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
}
@ -733,6 +768,7 @@ mod tests {
None,
true,
ComputeBudget::default(),
Rc::new(RefCell::new(Executors::default())),
);
// Check call depth increases and has a limit
@ -1244,6 +1280,8 @@ mod tests {
let account = RefCell::new(create_loadable_account("mock_system_program"));
loaders.push(vec![(mock_system_program_id, account)]);
let executors = Rc::new(RefCell::new(Executors::default()));
let from_pubkey = Pubkey::new_rand();
let to_pubkey = Pubkey::new_rand();
let account_metas = vec![
@ -1259,8 +1297,14 @@ mod tests {
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
);
assert_eq!(result, Ok(()));
assert_eq!(accounts[0].borrow().lamports, 100);
assert_eq!(accounts[1].borrow().lamports, 0);
@ -1274,8 +1318,14 @@ mod tests {
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
@ -1293,8 +1343,14 @@ mod tests {
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors,
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
@ -1375,6 +1431,8 @@ mod tests {
let account = RefCell::new(create_loadable_account("mock_system_program"));
loaders.push(vec![(mock_program_id, account)]);
let executors = Rc::new(RefCell::new(Executors::default()));
let from_pubkey = Pubkey::new_rand();
let to_pubkey = Pubkey::new_rand();
let dup_pubkey = from_pubkey;
@ -1393,8 +1451,14 @@ mod tests {
)],
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
@ -1412,8 +1476,14 @@ mod tests {
)],
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
);
assert_eq!(result, Ok(()));
// Do work on the same account but at different location in keyed_accounts[]
@ -1428,8 +1498,14 @@ mod tests {
)],
Some(&from_pubkey),
);
let result =
message_processor.process_message(&message, &loaders, &accounts, &rent_collector, None);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors,
);
assert_eq!(result, Ok(()));
assert_eq!(accounts[0].borrow().lamports, 80);
assert_eq!(accounts[1].borrow().lamports, 20);
@ -1504,6 +1580,7 @@ mod tests {
None,
true,
ComputeBudget::default(),
Rc::new(RefCell::new(Executors::default())),
);
let metas = vec![
AccountMeta::new(owned_key, false),