Record and store invoked instructions in transaction meta (#12311)

* Record invoked instructions and store in transaction meta

* Enable cpi recording if transaction sender is some

* Rename invoked to innerInstructions
This commit is contained in:
Justin Starry
2020-09-24 22:36:22 +08:00
committed by GitHub
parent 860ecdd376
commit 6601ec8f26
19 changed files with 429 additions and 98 deletions

View File

@@ -227,6 +227,7 @@ impl InvokeContext for MockInvokeContext {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None
}
fn record_instruction(&self, _instruction: &Instruction) {}
}
#[derive(Debug, Default, Clone)]
pub struct MockLogger {

View File

@@ -251,9 +251,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
// Signer privilege escalation will always fail the whole transaction
instruction.accounts[0].is_signer = true;
sol_assert(SUCCESS !=
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
break;
}
case TEST_PRIVILEGE_ESCALATION_WRITABLE: {
@@ -267,9 +267,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
// Writable privilege escalation will always fail the whole transaction
instruction.accounts[0].is_writable = true;
sol_assert(SUCCESS !=
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
break;
}
default:

View File

@@ -239,6 +239,7 @@ fn process_instruction(
);
invoke(&invoked_instruction, accounts)?;
// Signer privilege escalation will always fail the whole transaction
invoked_instruction.accounts[0].is_signer = true;
assert_eq!(
invoke(&invoked_instruction, accounts),
@@ -254,6 +255,7 @@ fn process_instruction(
);
invoke(&invoked_instruction, accounts)?;
// Writable privilege escalation will always fail the whole transaction
invoked_instruction.accounts[0].is_writable = true;
assert_eq!(
invoke(&invoked_instruction, accounts),

View File

@@ -18,7 +18,7 @@ use solana_sdk::{
account::{Account, KeyedAccount},
bpf_loader, bpf_loader_deprecated,
client::SyncClient,
clock::DEFAULT_SLOTS_PER_EPOCH,
clock::{DEFAULT_SLOTS_PER_EPOCH, MAX_PROCESSING_AGE},
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
entrypoint_native::{
ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction,
@@ -28,7 +28,7 @@ use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history},
transaction::TransactionError,
transaction::{Transaction, TransactionError},
};
use std::{cell::RefCell, env, fs::File, io::Read, path::PathBuf, rc::Rc, sync::Arc};
@@ -98,6 +98,26 @@ fn run_program(
Ok(vm.get_total_instruction_count())
}
fn process_transaction_and_record_inner(
bank: &Bank,
tx: Transaction,
) -> (Result<(), TransactionError>, Vec<Vec<CompiledInstruction>>) {
let signature = tx.signatures.get(0).unwrap().clone();
let txs = vec![tx];
let tx_batch = bank.prepare_batch(&txs, None);
let (mut results, _, mut inner) =
bank.load_execute_and_commit_transactions(&tx_batch, MAX_PROCESSING_AGE, false, true);
let inner_instructions = inner.swap_remove(0);
let result = results
.fee_collection_results
.swap_remove(0)
.and_then(|_| bank.get_signature_status(&signature).unwrap());
(
result,
inner_instructions.expect("cpi recording should be enabled"),
)
}
#[test]
#[cfg(any(feature = "bpf_c", feature = "bpf_rust"))]
fn test_program_bpf_sanity() {
@@ -482,61 +502,91 @@ fn test_program_bpf_invoke() {
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
assert!(bank_client
.send_and_confirm_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.is_ok());
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
assert!(result.is_ok());
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(
invoked_programs,
vec![
solana_sdk::system_program::id(),
solana_sdk::system_program::id(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
]
);
// failure cases
let instruction = Instruction::new(
invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_SIGNER,
&[TEST_PRIVILEGE_ESCALATION_SIGNER, nonce1, nonce2, nonce3],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![invoked_program_id.clone()]);
assert_eq!(
bank_client
.send_and_confirm_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602))
);
let instruction = Instruction::new(
invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_WRITABLE,
&[TEST_PRIVILEGE_ESCALATION_WRITABLE, nonce1, nonce2, nonce3],
account_metas.clone(),
);
let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![invoked_program_id.clone()]);
assert_eq!(
bank_client
.send_and_confirm_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602))
);
@@ -639,6 +689,7 @@ impl InvokeContext for MockInvokeContext {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None
}
fn record_instruction(&self, _instruction: &Instruction) {}
}
#[derive(Debug, Default, Clone)]

View File

@@ -275,6 +275,7 @@ mod tests {
account::Account,
entrypoint_native::{ComputeBudget, Logger, ProcessInstruction},
instruction::CompiledInstruction,
instruction::Instruction,
message::Message,
rent::Rent,
};
@@ -360,6 +361,7 @@ mod tests {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None
}
fn record_instruction(&self, _instruction: &Instruction) {}
}
struct TestInstructionMeter {
@@ -587,6 +589,7 @@ mod tests {
max_invoke_depth: 2,
},
Rc::new(RefCell::new(Executors::default())),
None,
);
assert_eq!(
Err(InstructionError::Custom(194969602)),

View File

@@ -1020,6 +1020,7 @@ fn call<'a>(
ro_regions,
)?;
verify_instruction(syscall, &instruction, &signers)?;
invoke_context.record_instruction(&instruction);
let message = Message::new(&[instruction], None);
let callee_program_id_index = message.instructions[0].program_id_index as usize;
let callee_program_id = message.account_keys[callee_program_id_index];