Allow programs to realloc their accounts within limits (#19475)

This commit is contained in:
Jack May
2021-09-28 01:13:03 -07:00
committed by GitHub
parent 578efdd59f
commit 4e27543415
21 changed files with 1536 additions and 78 deletions

View File

@@ -14,6 +14,8 @@ use solana_bpf_loader_program::{
syscalls::register_syscalls,
BpfError, ThisInstructionMeter,
};
use solana_bpf_rust_realloc::instructions::*;
use solana_bpf_rust_realloc_invoke::instructions::*;
use solana_cli_output::display::println_transaction;
use solana_rbpf::{
static_analysis::Analysis,
@@ -43,7 +45,8 @@ use solana_sdk::{
process_instruction::{InvokeContext, MockInvokeContext},
pubkey::Pubkey,
signature::{keypair_from_seed, Keypair, Signer},
system_instruction, system_program, sysvar,
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
system_program, sysvar,
sysvar::{clock, rent},
transaction::{Transaction, TransactionError},
};
@@ -234,6 +237,7 @@ fn run_program(
executable.as_ref(),
parameter_bytes.as_slice_mut(),
&mut invoke_context,
&account_lengths,
)
.unwrap();
let result = if i == 0 {
@@ -283,6 +287,7 @@ fn run_program(
parameter_accounts,
parameter_bytes.as_slice(),
&account_lengths,
true,
)
.unwrap();
}
@@ -2536,3 +2541,716 @@ fn test_program_bpf_ro_account_modify() {
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
}
#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_realloc() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, id, entrypoint);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let account = AccountSharedData::new(42, 5, &program_id);
bank.store_account(&pubkey, &account);
// Realloc RO account
let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump);
instruction.accounts[0].is_writable = false;
assert_eq!(
bank_client
.send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
// Realloc account to overflow
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, usize::MAX, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE + 1,
&mut bump
)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
let mut bump = i as u64;
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_fill(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
1,
&mut bump,
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN],
vec![AccountMeta::new(pubkey, false)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via cpi
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via cpi
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// zero-init
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ZERO_INIT],
vec![AccountMeta::new(pubkey, true)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
}
#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_realloc_invoke() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, id, entrypoint);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let realloc_program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc",
);
let realloc_invoke_program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc_invoke",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey().clone();
let account = AccountSharedData::new(42, 5, &realloc_program_id);
bank.store_account(&pubkey, &account);
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
// Realloc RO account
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_ZERO_RO],
vec![
AccountMeta::new_readonly(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_PLUS_ONE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max twice
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_TWICE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via system program
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via system program
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&realloc_program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to 100 and check via CPI
let invoke_account = AccountSharedData::new(42, 5, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_INVOKE_CHECK],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(100, data.len());
for i in 0..5 {
assert_eq!(data[i], 0);
}
for i in 5..data.len() {
assert_eq!(data[i], 2);
}
// Realloc rescursively and fill data
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(200, data.len());
for i in 0..100 {
assert_eq!(data[i], 1);
}
for i in 100..200 {
assert_eq!(data[i], 2);
}
// Create account, realloc, check
let new_keypair = Keypair::new();
let new_pubkey = new_keypair.pubkey().clone();
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
&[&mint_keypair, &new_keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(new_pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap();
assert_eq!(200, data.len());
let account = bank.get_account(&new_pubkey).unwrap();
assert_eq!(&realloc_invoke_program_id, account.owner());
// Invoke, dealloc, and assign
let pre_len = 100;
let new_len = pre_len * 2;
let mut invoke_account = AccountSharedData::new(42, pre_len, &realloc_program_id);
invoke_account.set_data_from_slice(&vec![1; pre_len]);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]);
instruction_data.extend_from_slice(&pre_len.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(new_len, data.len());
for i in 0..new_len {
assert_eq!(data[i], 0);
}
// Realloc to max invoke max
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc invoke max twice
let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_INVOKE_MAX_TWICE],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
}