Use BPF serialization ABI for native target in program-test; Fix account data resizing and editing (#26507)
* Use BPF serialization ABI for native target in program-test (PR22754) * Fixes for the program-test PR22754 rebase on v1.11.1 * Include bug test case in program-test unit tests * Fix segfault on duped accounts * Add CPI create_account test * Revert gitignore addition Co-authored-by: jozanza <hello@jsavary.com> Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
This commit is contained in:
parent
c4ec031daa
commit
18e9225a75
|
@ -9,6 +9,7 @@ use {
|
||||||
log::*,
|
log::*,
|
||||||
solana_banks_client::start_client,
|
solana_banks_client::start_client,
|
||||||
solana_banks_server::banks_server::start_local_server,
|
solana_banks_server::banks_server::start_local_server,
|
||||||
|
solana_bpf_loader_program::serialization::serialize_parameters,
|
||||||
solana_program_runtime::{
|
solana_program_runtime::{
|
||||||
compute_budget::ComputeBudget, ic_msg, invoke_context::ProcessInstructionWithContext,
|
compute_budget::ComputeBudget, ic_msg, invoke_context::ProcessInstructionWithContext,
|
||||||
stable_log, timings::ExecuteTimings,
|
stable_log, timings::ExecuteTimings,
|
||||||
|
@ -24,7 +25,7 @@ use {
|
||||||
account::{Account, AccountSharedData, ReadableAccount},
|
account::{Account, AccountSharedData, ReadableAccount},
|
||||||
account_info::AccountInfo,
|
account_info::AccountInfo,
|
||||||
clock::Slot,
|
clock::Slot,
|
||||||
entrypoint::{ProgramResult, SUCCESS},
|
entrypoint::{deserialize, ProgramResult, SUCCESS},
|
||||||
feature_set::FEATURE_NAMES,
|
feature_set::FEATURE_NAMES,
|
||||||
fee_calculator::{FeeCalculator, FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE},
|
fee_calculator::{FeeCalculator, FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE},
|
||||||
genesis_config::{ClusterType, GenesisConfig},
|
genesis_config::{ClusterType, GenesisConfig},
|
||||||
|
@ -41,13 +42,12 @@ use {
|
||||||
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
|
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
|
||||||
std::{
|
std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::HashSet,
|
collections::{HashMap, HashSet},
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, Read},
|
io::{self, Read},
|
||||||
mem::transmute,
|
mem::transmute,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc, RwLock,
|
Arc, RwLock,
|
||||||
|
@ -112,58 +112,19 @@ pub fn builtin_process_instruction(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy indices_in_instruction into a HashSet to ensure there are no duplicates
|
// Copy indices_in_instruction into a HashSet to ensure there are no duplicates
|
||||||
let deduplicated_indices: HashSet<usize> = instruction_account_indices.clone().collect();
|
let deduplicated_indices: HashSet<usize> = instruction_account_indices.collect();
|
||||||
|
|
||||||
// Create copies of the accounts
|
// Serialize entrypoint parameters with BPF ABI
|
||||||
let mut account_copies = deduplicated_indices
|
let (mut parameter_bytes, _account_lengths) = serialize_parameters(
|
||||||
.iter()
|
invoke_context.transaction_context,
|
||||||
.map(|instruction_account_index| {
|
invoke_context
|
||||||
let borrowed_account = instruction_context
|
.transaction_context
|
||||||
.try_borrow_instruction_account(transaction_context, *instruction_account_index)?;
|
.get_current_instruction_context()?,
|
||||||
Ok((
|
)?;
|
||||||
*borrowed_account.get_key(),
|
|
||||||
*borrowed_account.get_owner(),
|
|
||||||
borrowed_account.get_lamports(),
|
|
||||||
borrowed_account.get_data().to_vec(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, InstructionError>>()?;
|
|
||||||
|
|
||||||
// Create shared references to account_copies
|
// Deserialize data back into instruction params
|
||||||
let account_refs: Vec<_> = account_copies
|
let (program_id, account_infos, _input) =
|
||||||
.iter_mut()
|
unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) };
|
||||||
.map(|(key, owner, lamports, data)| {
|
|
||||||
(
|
|
||||||
key,
|
|
||||||
owner,
|
|
||||||
Rc::new(RefCell::new(lamports)),
|
|
||||||
Rc::new(RefCell::new(data.as_mut())),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Create AccountInfos
|
|
||||||
let account_infos = instruction_account_indices
|
|
||||||
.map(|instruction_account_index| {
|
|
||||||
let account_copy_index = deduplicated_indices
|
|
||||||
.iter()
|
|
||||||
.position(|index| *index == instruction_account_index)
|
|
||||||
.unwrap();
|
|
||||||
let (key, owner, lamports, data) = &account_refs[account_copy_index];
|
|
||||||
let borrowed_account = instruction_context
|
|
||||||
.try_borrow_instruction_account(transaction_context, instruction_account_index)?;
|
|
||||||
Ok(AccountInfo {
|
|
||||||
key,
|
|
||||||
is_signer: borrowed_account.is_signer(),
|
|
||||||
is_writable: borrowed_account.is_writable(),
|
|
||||||
lamports: lamports.clone(),
|
|
||||||
data: data.clone(),
|
|
||||||
owner,
|
|
||||||
executable: borrowed_account.is_executable(),
|
|
||||||
rent_epoch: borrowed_account.get_rent_epoch(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<AccountInfo>, InstructionError>>()?;
|
|
||||||
|
|
||||||
// Execute the program
|
// Execute the program
|
||||||
process_instruction(program_id, &account_infos, instruction_data).map_err(|err| {
|
process_instruction(program_id, &account_infos, instruction_data).map_err(|err| {
|
||||||
|
@ -173,27 +134,35 @@ pub fn builtin_process_instruction(
|
||||||
})?;
|
})?;
|
||||||
stable_log::program_success(&log_collector, program_id);
|
stable_log::program_success(&log_collector, program_id);
|
||||||
|
|
||||||
|
// Lookup table for AccountInfo
|
||||||
|
let account_info_map: HashMap<_, _> = account_infos.into_iter().map(|a| (a.key, a)).collect();
|
||||||
|
|
||||||
|
// Re-fetch the instruction context. The previous reference may have been
|
||||||
|
// invalidated due to the `set_invoke_context` in a CPI.
|
||||||
|
let transaction_context = &invoke_context.transaction_context;
|
||||||
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||||
|
|
||||||
// Commit AccountInfo changes back into KeyedAccounts
|
// Commit AccountInfo changes back into KeyedAccounts
|
||||||
for (instruction_account_index, (_key, owner, lamports, data)) in deduplicated_indices
|
for i in deduplicated_indices.into_iter() {
|
||||||
.into_iter()
|
let mut borrowed_account =
|
||||||
.zip(account_copies.into_iter())
|
instruction_context.try_borrow_instruction_account(transaction_context, i)?;
|
||||||
{
|
if borrowed_account.is_writable() {
|
||||||
let mut borrowed_account = instruction_context
|
if let Some(account_info) = account_info_map.get(borrowed_account.get_key()) {
|
||||||
.try_borrow_instruction_account(transaction_context, instruction_account_index)?;
|
if borrowed_account.get_lamports() != account_info.lamports() {
|
||||||
if borrowed_account.get_lamports() != lamports {
|
borrowed_account.set_lamports(account_info.lamports())?;
|
||||||
borrowed_account.set_lamports(lamports)?;
|
}
|
||||||
}
|
|
||||||
// The redundant check helps to avoid the expensive data comparison if we can
|
if borrowed_account
|
||||||
match borrowed_account
|
.can_data_be_resized(account_info.data_len())
|
||||||
.can_data_be_resized(data.len())
|
.is_ok()
|
||||||
.and_then(|_| borrowed_account.can_data_be_changed())
|
&& borrowed_account.can_data_be_changed().is_ok()
|
||||||
{
|
{
|
||||||
Ok(()) => borrowed_account.set_data(&data)?,
|
borrowed_account.set_data(&account_info.data.borrow())?;
|
||||||
Err(err) if borrowed_account.get_data() != data => return Err(err),
|
}
|
||||||
_ => {}
|
if borrowed_account.get_owner() != account_info.owner {
|
||||||
}
|
borrowed_account.set_owner(account_info.owner.as_ref())?;
|
||||||
if borrowed_account.get_owner() != &owner {
|
}
|
||||||
borrowed_account.set_owner(owner.as_ref())?;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +245,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|seeds| Pubkey::create_program_address(seeds, caller).unwrap())
|
.map(|seeds| Pubkey::create_program_address(seeds, caller).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (instruction_accounts, program_indices) = invoke_context
|
let (instruction_accounts, program_indices) = invoke_context
|
||||||
.prepare_instruction(instruction, &signers)
|
.prepare_instruction(instruction, &signers)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -363,8 +333,6 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let account_info = &account_infos[account_info_index];
|
let account_info = &account_infos[account_info_index];
|
||||||
**account_info.try_borrow_mut_lamports().unwrap() = borrowed_account.get_lamports();
|
**account_info.try_borrow_mut_lamports().unwrap() = borrowed_account.get_lamports();
|
||||||
let mut data = account_info.try_borrow_mut_data()?;
|
|
||||||
let new_data = borrowed_account.get_data();
|
|
||||||
if account_info.owner != borrowed_account.get_owner() {
|
if account_info.owner != borrowed_account.get_owner() {
|
||||||
// TODO Figure out a better way to allow the System Program to set the account owner
|
// TODO Figure out a better way to allow the System Program to set the account owner
|
||||||
#[allow(clippy::transmute_ptr_to_ptr)]
|
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||||
|
@ -373,14 +341,17 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
||||||
unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
|
unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
|
||||||
*account_info_mut = *borrowed_account.get_owner();
|
*account_info_mut = *borrowed_account.get_owner();
|
||||||
}
|
}
|
||||||
// TODO: Figure out how to allow the System Program to resize the account data
|
|
||||||
assert!(
|
let new_data = borrowed_account.get_data();
|
||||||
data.len() == new_data.len(),
|
let new_len = new_data.len();
|
||||||
"Account data resizing not supported yet: {} -> {}. \
|
|
||||||
Consider making this test conditional on `#[cfg(feature = \"test-bpf\")]`",
|
// Resize account_info data (grow-only)
|
||||||
data.len(),
|
if account_info.data_len() < new_len {
|
||||||
new_data.len()
|
account_info.realloc(new_len, false)?;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// Clone the data
|
||||||
|
let mut data = account_info.try_borrow_mut_data()?;
|
||||||
data.clone_from_slice(new_data);
|
data.clone_from_slice(new_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,22 @@ use {
|
||||||
solana_program_test::{processor, ProgramTest},
|
solana_program_test::{processor, ProgramTest},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account_info::{next_account_info, AccountInfo},
|
account_info::{next_account_info, AccountInfo},
|
||||||
entrypoint::ProgramResult,
|
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
msg,
|
msg,
|
||||||
program::invoke,
|
program::invoke,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
signature::Signer,
|
signature::Signer,
|
||||||
|
signer::keypair::Keypair,
|
||||||
|
system_instruction, system_program,
|
||||||
|
sysvar::Sysvar,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process instruction to invoke into another program
|
// Process instruction to invoke into another program
|
||||||
|
// We pass this specific number of accounts in order to test for a reported error.
|
||||||
fn invoker_process_instruction(
|
fn invoker_process_instruction(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
|
@ -23,13 +28,50 @@ fn invoker_process_instruction(
|
||||||
let account_info_iter = &mut accounts.iter();
|
let account_info_iter = &mut accounts.iter();
|
||||||
let invoked_program_info = next_account_info(account_info_iter)?;
|
let invoked_program_info = next_account_info(account_info_iter)?;
|
||||||
invoke(
|
invoke(
|
||||||
&Instruction::new_with_bincode(*invoked_program_info.key, &[0], vec![]),
|
&Instruction::new_with_bincode(
|
||||||
|
*invoked_program_info.key,
|
||||||
|
&[0],
|
||||||
|
vec![AccountMeta::new_readonly(*invoked_program_info.key, false)],
|
||||||
|
),
|
||||||
&[invoked_program_info.clone()],
|
&[invoked_program_info.clone()],
|
||||||
)?;
|
)?;
|
||||||
msg!("Processing invoker instruction after CPI");
|
msg!("Processing invoker instruction after CPI");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process instruction to invoke into another program with duplicates
|
||||||
|
fn invoker_dupes_process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
_input: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
// if we can call `msg!` successfully, then InvokeContext exists as required
|
||||||
|
msg!("Processing invoker instruction before CPI");
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let invoked_program_info = next_account_info(account_info_iter)?;
|
||||||
|
invoke(
|
||||||
|
&Instruction::new_with_bincode(
|
||||||
|
*invoked_program_info.key,
|
||||||
|
&[0],
|
||||||
|
vec![
|
||||||
|
AccountMeta::new_readonly(*invoked_program_info.key, false),
|
||||||
|
AccountMeta::new_readonly(*invoked_program_info.key, false),
|
||||||
|
AccountMeta::new_readonly(*invoked_program_info.key, false),
|
||||||
|
AccountMeta::new_readonly(*invoked_program_info.key, false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
invoked_program_info.clone(),
|
||||||
|
invoked_program_info.clone(),
|
||||||
|
invoked_program_info.clone(),
|
||||||
|
invoked_program_info.clone(),
|
||||||
|
invoked_program_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
msg!("Processing invoker instruction after CPI");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Process instruction to be invoked by another program
|
// Process instruction to be invoked by another program
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn invoked_process_instruction(
|
fn invoked_process_instruction(
|
||||||
|
@ -42,6 +84,37 @@ fn invoked_process_instruction(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process instruction to invoke into system program to create an account
|
||||||
|
fn invoke_create_account(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
_input: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
msg!("Processing instruction before system program CPI instruction");
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let payer_info = next_account_info(account_info_iter)?;
|
||||||
|
let create_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let system_program_info = next_account_info(account_info_iter)?;
|
||||||
|
let rent = Rent::get()?;
|
||||||
|
let minimum_balance = rent.minimum_balance(MAX_PERMITTED_DATA_INCREASE);
|
||||||
|
invoke(
|
||||||
|
&system_instruction::create_account(
|
||||||
|
payer_info.key,
|
||||||
|
create_account_info.key,
|
||||||
|
minimum_balance,
|
||||||
|
MAX_PERMITTED_DATA_INCREASE as u64,
|
||||||
|
program_id,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
payer_info.clone(),
|
||||||
|
create_account_info.clone(),
|
||||||
|
system_program_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
msg!("Processing instruction after system program CPI");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn cpi() {
|
async fn cpi() {
|
||||||
let invoker_program_id = Pubkey::new_unique();
|
let invoker_program_id = Pubkey::new_unique();
|
||||||
|
@ -77,3 +150,79 @@ async fn cpi() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cpi_dupes() {
|
||||||
|
let invoker_program_id = Pubkey::new_unique();
|
||||||
|
let mut program_test = ProgramTest::new(
|
||||||
|
"invoker",
|
||||||
|
invoker_program_id,
|
||||||
|
processor!(invoker_dupes_process_instruction),
|
||||||
|
);
|
||||||
|
let invoked_program_id = Pubkey::new_unique();
|
||||||
|
program_test.add_program(
|
||||||
|
"invoked",
|
||||||
|
invoked_program_id,
|
||||||
|
processor!(invoked_process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut context = program_test.start_with_context().await;
|
||||||
|
let instructions = vec![Instruction::new_with_bincode(
|
||||||
|
invoker_program_id,
|
||||||
|
&[0],
|
||||||
|
vec![
|
||||||
|
AccountMeta::new_readonly(invoked_program_id, false),
|
||||||
|
AccountMeta::new_readonly(invoked_program_id, false),
|
||||||
|
AccountMeta::new_readonly(invoked_program_id, false),
|
||||||
|
AccountMeta::new_readonly(invoked_program_id, false),
|
||||||
|
],
|
||||||
|
)];
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&instructions,
|
||||||
|
Some(&context.payer.pubkey()),
|
||||||
|
&[&context.payer],
|
||||||
|
context.last_blockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cpi_create_account() {
|
||||||
|
let create_account_program_id = Pubkey::new_unique();
|
||||||
|
let program_test = ProgramTest::new(
|
||||||
|
"create_account",
|
||||||
|
create_account_program_id,
|
||||||
|
processor!(invoke_create_account),
|
||||||
|
);
|
||||||
|
|
||||||
|
let create_account_keypair = Keypair::new();
|
||||||
|
let mut context = program_test.start_with_context().await;
|
||||||
|
let instructions = vec![Instruction::new_with_bincode(
|
||||||
|
create_account_program_id,
|
||||||
|
&[0],
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(context.payer.pubkey(), true),
|
||||||
|
AccountMeta::new(create_account_keypair.pubkey(), true),
|
||||||
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
|
],
|
||||||
|
)];
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&instructions,
|
||||||
|
Some(&context.payer.pubkey()),
|
||||||
|
&[&context.payer, &create_account_keypair],
|
||||||
|
context.last_blockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue