Integrate program loader-v4 with bank (#32832)

* Integrate program loader-v4 with bank

* fix tests

* new struct for ProgramRuntimeEnvironments

* remove environment from program_runtime_environment_v

* move find_program_in_cache() to invoke_context

* cleanup
This commit is contained in:
Pankaj Garg 2023-08-16 10:50:23 -07:00 committed by GitHub
parent d5d4732f17
commit c17b938204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 93 deletions

View File

@ -536,8 +536,14 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) {
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
// Adding `DELAY_VISIBILITY_SLOT_OFFSET` to slots to accommodate for delay visibility of the program // Adding `DELAY_VISIBILITY_SLOT_OFFSET` to slots to accommodate for delay visibility of the program
let mut loaded_programs = let mut loaded_programs = LoadedProgramsForTxBatch::new(
LoadedProgramsForTxBatch::new(bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET); bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET,
bank.loaded_programs_cache
.read()
.unwrap()
.environments
.clone(),
);
for key in cached_account_keys { for key in cached_account_keys {
loaded_programs.replenish(key, bank.load_program(&key)); loaded_programs.replenish(key, bank.load_program(&key));
debug!("Loaded program {}", key); debug!("Loaded program {}", key);

View File

@ -214,6 +214,14 @@ impl<'a> InvokeContext<'a> {
} }
} }
pub fn find_program_in_cache(&self, pubkey: &Pubkey) -> Option<Arc<LoadedProgram>> {
// First lookup the cache of the programs modified by the current transaction. If not found, lookup
// the cache of the cache of the programs that are loaded for the transaction batch.
self.programs_modified_by_tx
.find(pubkey)
.or_else(|| self.programs_loaded_for_tx_batch.find(pubkey))
}
/// Push a stack frame onto the invocation stack /// Push a stack frame onto the invocation stack
pub fn push(&mut self) -> Result<(), InstructionError> { pub fn push(&mut self) -> Result<(), InstructionError> {
let instruction_context = self let instruction_context = self

View File

@ -347,16 +347,21 @@ impl LoadedProgram {
} }
} }
#[derive(Clone, Debug, Default)]
pub struct ProgramRuntimeEnvironments {
/// Globally shared RBPF config and syscall registry
pub program_runtime_v1: Arc<BuiltinProgram<InvokeContext<'static>>>,
/// Globally shared RBPF config and syscall registry for runtime V2
pub program_runtime_v2: Arc<BuiltinProgram<InvokeContext<'static>>>,
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct LoadedPrograms { pub struct LoadedPrograms {
/// A two level index: /// A two level index:
/// ///
/// Pubkey is the address of a program, multiple versions can coexists simultaneously under the same address (in different slots). /// Pubkey is the address of a program, multiple versions can coexists simultaneously under the same address (in different slots).
entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>, entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
/// Globally shared RBPF config and syscall registry pub environments: ProgramRuntimeEnvironments,
pub program_runtime_environment_v1: Arc<BuiltinProgram<InvokeContext<'static>>>,
/// Globally shared RBPF config and syscall registry for runtime V2
pub program_runtime_environment_v2: Arc<BuiltinProgram<InvokeContext<'static>>>,
latest_root: Slot, latest_root: Slot,
pub stats: Stats, pub stats: Stats,
} }
@ -367,13 +372,15 @@ pub struct LoadedProgramsForTxBatch {
/// LoadedProgram is the corresponding program entry valid for the slot in which a transaction is being executed. /// LoadedProgram is the corresponding program entry valid for the slot in which a transaction is being executed.
entries: HashMap<Pubkey, Arc<LoadedProgram>>, entries: HashMap<Pubkey, Arc<LoadedProgram>>,
slot: Slot, slot: Slot,
pub environments: ProgramRuntimeEnvironments,
} }
impl LoadedProgramsForTxBatch { impl LoadedProgramsForTxBatch {
pub fn new(slot: Slot) -> Self { pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self {
Self { Self {
entries: HashMap::new(), entries: HashMap::new(),
slot, slot,
environments,
} }
} }
@ -496,22 +503,22 @@ impl LoadedPrograms {
LoadedProgramType::LegacyV0(program) | LoadedProgramType::LegacyV1(program) LoadedProgramType::LegacyV0(program) | LoadedProgramType::LegacyV1(program)
if Arc::ptr_eq( if Arc::ptr_eq(
program.get_loader(), program.get_loader(),
&self.program_runtime_environment_v1, &self.environments.program_runtime_v1,
) => ) =>
{ {
true true
} }
LoadedProgramType::Unloaded(environment) LoadedProgramType::Unloaded(environment)
| LoadedProgramType::FailedVerification(environment) | LoadedProgramType::FailedVerification(environment)
if Arc::ptr_eq(environment, &self.program_runtime_environment_v1) if Arc::ptr_eq(environment, &self.environments.program_runtime_v1)
|| Arc::ptr_eq(environment, &self.program_runtime_environment_v2) => || Arc::ptr_eq(environment, &self.environments.program_runtime_v2) =>
{ {
true true
} }
LoadedProgramType::Typed(program) LoadedProgramType::Typed(program)
if Arc::ptr_eq( if Arc::ptr_eq(
program.get_loader(), program.get_loader(),
&self.program_runtime_environment_v2, &self.environments.program_runtime_v2,
) => ) =>
{ {
true true
@ -654,6 +661,7 @@ impl LoadedPrograms {
LoadedProgramsForTxBatch { LoadedProgramsForTxBatch {
entries: found, entries: found,
slot: working_slot.current_slot(), slot: working_slot.current_slot(),
environments: self.environments.clone(),
}, },
missing, missing,
) )

View File

@ -99,18 +99,6 @@ pub fn load_program_from_bytes(
Ok(loaded_program) Ok(loaded_program)
} }
fn find_program_in_cache(
invoke_context: &InvokeContext,
pubkey: &Pubkey,
) -> Option<Arc<LoadedProgram>> {
// First lookup the cache of the programs modified by the current transaction. If not found, lookup
// the cache of the cache of the programs that are loaded for the transaction batch.
invoke_context
.programs_modified_by_tx
.find(pubkey)
.or_else(|| invoke_context.programs_loaded_for_tx_batch.find(pubkey))
}
macro_rules! deploy_program { macro_rules! deploy_program {
($invoke_context:expr, $program_id:expr, $loader_key:expr, ($invoke_context:expr, $program_id:expr, $loader_key:expr,
$account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{ $account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{
@ -137,7 +125,7 @@ macro_rules! deploy_program {
$slot, $slot,
Arc::new(program_runtime_environment), Arc::new(program_runtime_environment),
)?; )?;
if let Some(old_entry) = find_program_in_cache($invoke_context, &$program_id) { if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) {
executor.tx_usage_counter.store( executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed), old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed Ordering::Relaxed
@ -507,7 +495,8 @@ fn process_instruction_inner(
} }
let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
let executor = find_program_in_cache(invoke_context, program_account.get_key()) let executor = invoke_context
.find_program_in_cache(program_account.get_key())
.ok_or(InstructionError::InvalidAccountData)?; .ok_or(InstructionError::InvalidAccountData)?;
if executor.is_tombstone() { if executor.is_tombstone() {

View File

@ -5,7 +5,9 @@ use {
compute_budget::ComputeBudget, compute_budget::ComputeBudget,
ic_logger_msg, ic_logger_msg,
invoke_context::InvokeContext, invoke_context::InvokeContext,
loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramType}, loaded_programs::{
LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET,
},
log_collector::LogCollector, log_collector::LogCollector,
stable_log, stable_log,
}, },
@ -22,7 +24,7 @@ use {
}, },
solana_sdk::{ solana_sdk::{
entrypoint::{HEAP_LENGTH, SUCCESS}, entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{self, FeatureSet}, feature_set,
instruction::InstructionError, instruction::InstructionError,
loader_v4::{self, LoaderV4State, DEPLOYMENT_COOLDOWN_IN_SLOTS}, loader_v4::{self, LoaderV4State, DEPLOYMENT_COOLDOWN_IN_SLOTS},
loader_v4_instruction::LoaderV4Instruction, loader_v4_instruction::LoaderV4Instruction,
@ -100,42 +102,6 @@ pub fn create_program_runtime_environment_v2<'a>(
BuiltinProgram::new_loader(config) BuiltinProgram::new_loader(config)
} }
pub fn load_program_from_account(
_feature_set: &FeatureSet,
compute_budget: &ComputeBudget,
log_collector: Option<Rc<RefCell<LogCollector>>>,
program: &BorrowedAccount,
debugging_features: bool,
) -> Result<(Arc<LoadedProgram>, LoadProgramMetrics), InstructionError> {
let mut load_program_metrics = LoadProgramMetrics {
program_id: program.get_key().to_string(),
..LoadProgramMetrics::default()
};
let state = get_state(program.get_data())?;
let programdata = program
.get_data()
.get(LoaderV4State::program_data_offset()..)
.ok_or(InstructionError::AccountDataTooSmall)?;
let loaded_program = LoadedProgram::new(
&loader_v4::id(),
Arc::new(create_program_runtime_environment_v2(
compute_budget,
debugging_features,
)),
state.slot,
state.slot.saturating_add(1),
None,
programdata,
program.get_data().len(),
&mut load_program_metrics,
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
Ok((Arc::new(loaded_program), load_program_metrics))
}
fn calculate_heap_cost(heap_size: u64, heap_cost: u64) -> u64 { fn calculate_heap_cost(heap_size: u64, heap_cost: u64) -> u64 {
const KIBIBYTE: u64 = 1024; const KIBIBYTE: u64 = 1024;
const PAGE_SIZE_KB: u64 = 32; const PAGE_SIZE_KB: u64 = 32;
@ -458,13 +424,37 @@ pub fn process_instruction_deploy(
} else { } else {
&program &program
}; };
let (_executor, load_program_metrics) = load_program_from_account(
&invoke_context.feature_set, let programdata = buffer
invoke_context.get_compute_budget(), .get_data()
invoke_context.get_log_collector(), .get(LoaderV4State::program_data_offset()..)
buffer, .ok_or(InstructionError::AccountDataTooSmall)?;
false, /* debugging_features */
)?; let deployment_slot = state.slot;
let effective_slot = deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET);
let mut load_program_metrics = LoadProgramMetrics {
program_id: buffer.get_key().to_string(),
..LoadProgramMetrics::default()
};
let executor = LoadedProgram::new(
&loader_v4::id(),
invoke_context
.programs_modified_by_tx
.environments
.program_runtime_v2
.clone(),
deployment_slot,
effective_slot,
None,
programdata,
buffer.get_data().len(),
&mut load_program_metrics,
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings); load_program_metrics.submit_datapoint(&mut invoke_context.timings);
if let Some(mut source_program) = source_program { if let Some(mut source_program) = source_program {
let rent = invoke_context.get_sysvar_cache().get_rent()?; let rent = invoke_context.get_sysvar_cache().get_rent()?;
@ -478,6 +468,20 @@ pub fn process_instruction_deploy(
let state = get_state_mut(program.get_data_mut()?)?; let state = get_state_mut(program.get_data_mut()?)?;
state.slot = current_slot; state.slot = current_slot;
state.is_deployed = true; state.is_deployed = true;
if let Some(old_entry) = invoke_context.find_program_in_cache(program.get_key()) {
executor.tx_usage_counter.store(
old_entry.tx_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
executor.ix_usage_counter.store(
old_entry.ix_usage_counter.load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
invoke_context
.programs_modified_by_tx
.replenish(*program.get_key(), Arc::new(executor));
Ok(()) Ok(())
} }
@ -602,14 +606,13 @@ pub fn process_instruction_inner(
return Err(Box::new(InstructionError::InvalidArgument)); return Err(Box::new(InstructionError::InvalidArgument));
} }
let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
let (loaded_program, load_program_metrics) = load_program_from_account( let loaded_program = invoke_context
&invoke_context.feature_set, .find_program_in_cache(program.get_key())
invoke_context.get_compute_budget(), .ok_or(InstructionError::InvalidAccountData)?;
invoke_context.get_log_collector(),
&program, if loaded_program.is_tombstone() {
false, /* debugging_features */ return Err(Box::new(InstructionError::InvalidAccountData));
)?; }
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
get_or_create_executor_time.stop(); get_or_create_executor_time.stop();
saturating_add_assign!( saturating_add_assign!(
invoke_context.timings.get_or_create_executor_us, invoke_context.timings.get_or_create_executor_us,
@ -651,6 +654,50 @@ mod tests {
std::{fs::File, io::Read, path::Path}, std::{fs::File, io::Read, path::Path},
}; };
pub fn load_all_invoked_programs(invoke_context: &mut InvokeContext) {
let mut load_program_metrics = LoadProgramMetrics::default();
let num_accounts = invoke_context.transaction_context.get_number_of_accounts();
for index in 0..num_accounts {
let account = invoke_context
.transaction_context
.get_account_at_index(index)
.expect("Failed to get the account")
.borrow();
let owner = account.owner();
if loader_v4::check_id(owner) {
let pubkey = invoke_context
.transaction_context
.get_key_of_account_at_index(index)
.expect("Failed to get account key");
if let Some(programdata) =
account.data().get(LoaderV4State::program_data_offset()..)
{
if let Ok(loaded_program) = LoadedProgram::new(
&loader_v4::id(),
invoke_context
.programs_modified_by_tx
.environments
.program_runtime_v2
.clone(),
0,
0,
None,
programdata,
account.data().len(),
&mut load_program_metrics,
) {
invoke_context.programs_modified_by_tx.set_slot_for_tests(0);
invoke_context
.programs_modified_by_tx
.replenish(*pubkey, Arc::new(loaded_program));
}
}
}
}
}
fn process_instruction( fn process_instruction(
program_indices: Vec<IndexOfAccount>, program_indices: Vec<IndexOfAccount>,
instruction_data: &[u8], instruction_data: &[u8],
@ -676,7 +723,16 @@ mod tests {
instruction_accounts, instruction_accounts,
expected_result, expected_result,
super::process_instruction, super::process_instruction,
|_invoke_context| {}, |invoke_context| {
invoke_context
.programs_modified_by_tx
.environments
.program_runtime_v2 = Arc::new(create_program_runtime_environment_v2(
&ComputeBudget::default(),
false,
));
load_all_invoked_programs(invoke_context);
},
|_invoke_context| {}, |_invoke_context| {},
) )
} }
@ -1447,7 +1503,7 @@ mod tests {
load_program_account_from_elf(false, None, "rodata"), load_program_account_from_elf(false, None, "rodata"),
), ),
( (
program_address, Pubkey::new_unique(),
load_program_account_from_elf(true, None, "invalid"), load_program_account_from_elf(true, None, "invalid"),
), ),
]; ];

View File

@ -109,7 +109,7 @@ use {
invoke_context::ProcessInstructionWithContext, invoke_context::ProcessInstructionWithContext,
loaded_programs::{ loaded_programs::{
LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET,
}, },
log_collector::LogCollector, log_collector::LogCollector,
message_processor::MessageProcessor, message_processor::MessageProcessor,
@ -150,7 +150,7 @@ use {
inflation::Inflation, inflation::Inflation,
instruction::InstructionError, instruction::InstructionError,
lamports::LamportsError, lamports::LamportsError,
loader_v4, loader_v4::{self, LoaderV4State},
message::{AccountKeys, SanitizedMessage}, message::{AccountKeys, SanitizedMessage},
native_loader, native_loader,
native_token::LAMPORTS_PER_SOL, native_token::LAMPORTS_PER_SOL,
@ -305,8 +305,10 @@ impl BankRc {
enum ProgramAccountLoadResult { enum ProgramAccountLoadResult {
AccountNotFound, AccountNotFound,
InvalidAccountData, InvalidAccountData,
InvalidV4Program,
ProgramOfLoaderV1orV2(AccountSharedData), ProgramOfLoaderV1orV2(AccountSharedData),
ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot), ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
ProgramOfLoaderV4(AccountSharedData, Slot),
} }
pub struct LoadAndExecuteTransactionsOutput { pub struct LoadAndExecuteTransactionsOutput {
@ -4602,6 +4604,14 @@ impl Bank {
program_account.owner() program_account.owner()
)); ));
if loader_v4::check_id(program_account.owner()) {
return solana_loader_v4_program::get_state(program_account.data())
.ok()
.and_then(|state| state.is_deployed.then_some(state.slot))
.map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
.unwrap_or(ProgramAccountLoadResult::InvalidV4Program);
}
if !bpf_loader_upgradeable::check_id(program_account.owner()) { if !bpf_loader_upgradeable::check_id(program_account.owner()) {
return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account); return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account);
} }
@ -4631,11 +4641,11 @@ impl Bank {
} }
pub fn load_program(&self, pubkey: &Pubkey) -> Arc<LoadedProgram> { pub fn load_program(&self, pubkey: &Pubkey) -> Arc<LoadedProgram> {
let program_runtime_environment_v1 = self let environments = self
.loaded_programs_cache .loaded_programs_cache
.read() .read()
.unwrap() .unwrap()
.program_runtime_environment_v1 .environments
.clone(); .clone();
let mut load_program_metrics = LoadProgramMetrics { let mut load_program_metrics = LoadProgramMetrics {
@ -4662,7 +4672,7 @@ impl Bank {
program_account.owner(), program_account.owner(),
program_account.data().len(), program_account.data().len(),
0, 0,
program_runtime_environment_v1.clone(), environments.program_runtime_v1.clone(),
) )
} }
@ -4686,14 +4696,43 @@ impl Bank {
.len() .len()
.saturating_add(programdata_account.data().len()), .saturating_add(programdata_account.data().len()),
slot, slot,
program_runtime_environment_v1.clone(), environments.program_runtime_v1.clone(),
) )
}), }),
ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => {
let loaded_program = program_account
.data()
.get(LoaderV4State::program_data_offset()..)
.and_then(|elf_bytes| {
LoadedProgram::new(
&loader_v4::id(),
environments.program_runtime_v2.clone(),
slot,
slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
None,
elf_bytes,
program_account.data().len(),
&mut load_program_metrics,
)
.ok()
})
.unwrap_or(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::FailedVerification(environments.program_runtime_v2),
));
Ok(loaded_program)
}
ProgramAccountLoadResult::InvalidV4Program => Ok(LoadedProgram::new_tombstone(
self.slot,
LoadedProgramType::FailedVerification(environments.program_runtime_v2),
)),
} }
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
LoadedProgram::new_tombstone( LoadedProgram::new_tombstone(
self.slot, self.slot,
LoadedProgramType::FailedVerification(program_runtime_environment_v1), LoadedProgramType::FailedVerification(environments.program_runtime_v1),
) )
}); });
@ -4774,8 +4813,14 @@ impl Bank {
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature(); let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
let mut executed_units = 0u64; let mut executed_units = 0u64;
let mut programs_modified_by_tx = LoadedProgramsForTxBatch::new(self.slot); let mut programs_modified_by_tx = LoadedProgramsForTxBatch::new(
let mut programs_updated_only_for_global_cache = LoadedProgramsForTxBatch::new(self.slot); self.slot,
programs_loaded_for_tx_batch.environments.clone(),
);
let mut programs_updated_only_for_global_cache = LoadedProgramsForTxBatch::new(
self.slot,
programs_loaded_for_tx_batch.environments.clone(),
);
let mut process_message_time = Measure::start("process_message_time"); let mut process_message_time = Measure::start("process_message_time");
let process_result = MessageProcessor::process_message( let process_result = MessageProcessor::process_message(
tx.message(), tx.message(),
@ -8012,10 +8057,10 @@ impl Bank {
) )
.unwrap(); .unwrap();
let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap(); let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap();
if *loaded_programs_cache.program_runtime_environment_v1 if *loaded_programs_cache.environments.program_runtime_v1
!= program_runtime_environment_v1 != program_runtime_environment_v1
{ {
loaded_programs_cache.program_runtime_environment_v1 = loaded_programs_cache.environments.program_runtime_v1 =
Arc::new(program_runtime_environment_v1); Arc::new(program_runtime_environment_v1);
} }
let program_runtime_environment_v2 = let program_runtime_environment_v2 =
@ -8023,10 +8068,10 @@ impl Bank {
&self.runtime_config.compute_budget.unwrap_or_default(), &self.runtime_config.compute_budget.unwrap_or_default(),
false, /* debugging_features */ false, /* debugging_features */
); );
if *loaded_programs_cache.program_runtime_environment_v2 if *loaded_programs_cache.environments.program_runtime_v2
!= program_runtime_environment_v2 != program_runtime_environment_v2
{ {
loaded_programs_cache.program_runtime_environment_v2 = loaded_programs_cache.environments.program_runtime_v2 =
Arc::new(program_runtime_environment_v2); Arc::new(program_runtime_environment_v2);
} }
loaded_programs_cache.prune_feature_set_transition(); loaded_programs_cache.prune_feature_set_transition();