Cache re-usable work performed by the loader (#12135)
This commit is contained in:
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
Reference in New Issue
Block a user