Fix duplicate account translation in CPI (#23701)
* Use visit_each_account_once() in CPI syscall to prevent duplicate accounts from being translated twice. * remove unwrap() from runtime Co-authored-by: Jack May <jack@solana.com>
This commit is contained in:
committed by
GitHub
parent
be0aeea01a
commit
dda3a463a2
@ -352,7 +352,11 @@ impl<'a> InvokeContext<'a> {
|
|||||||
}
|
}
|
||||||
Err(InstructionError::MissingAccount)
|
Err(InstructionError::MissingAccount)
|
||||||
};
|
};
|
||||||
visit_each_account_once(instruction_accounts, &mut work)?;
|
visit_each_account_once(
|
||||||
|
instruction_accounts,
|
||||||
|
&mut work,
|
||||||
|
InstructionError::NotEnoughAccountKeys,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let contains = (0..self
|
let contains = (0..self
|
||||||
.transaction_context
|
.transaction_context
|
||||||
@ -517,7 +521,11 @@ impl<'a> InvokeContext<'a> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
visit_each_account_once(instruction_accounts, &mut work)?;
|
visit_each_account_once(
|
||||||
|
instruction_accounts,
|
||||||
|
&mut work,
|
||||||
|
InstructionError::NotEnoughAccountKeys,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Verify that the total sum of all the lamports did not change
|
// Verify that the total sum of all the lamports did not change
|
||||||
if pre_sum != post_sum {
|
if pre_sum != post_sum {
|
||||||
@ -615,7 +623,11 @@ impl<'a> InvokeContext<'a> {
|
|||||||
}
|
}
|
||||||
Err(InstructionError::MissingAccount)
|
Err(InstructionError::MissingAccount)
|
||||||
};
|
};
|
||||||
visit_each_account_once(instruction_accounts, &mut work)?;
|
visit_each_account_once(
|
||||||
|
instruction_accounts,
|
||||||
|
&mut work,
|
||||||
|
InstructionError::NotEnoughAccountKeys,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Verify that the total sum of all the lamports did not change
|
// Verify that the total sum of all the lamports did not change
|
||||||
if pre_sum != post_sum {
|
if pre_sum != post_sum {
|
||||||
@ -1154,23 +1166,25 @@ pub fn mock_process_instruction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Visit each unique instruction account index once
|
/// Visit each unique instruction account index once
|
||||||
fn visit_each_account_once(
|
pub fn visit_each_account_once<E>(
|
||||||
instruction_accounts: &[InstructionAccount],
|
instruction_accounts: &[InstructionAccount],
|
||||||
work: &mut dyn FnMut(usize, &InstructionAccount) -> Result<(), InstructionError>,
|
work: &mut dyn FnMut(usize, &InstructionAccount) -> Result<(), E>,
|
||||||
) -> Result<(), InstructionError> {
|
inner_error: E,
|
||||||
|
) -> Result<(), E> {
|
||||||
|
// Note: This is an O(n^2) algorithm,
|
||||||
|
// but performed on a very small slice and requires no heap allocations
|
||||||
'root: for (index, instruction_account) in instruction_accounts.iter().enumerate() {
|
'root: for (index, instruction_account) in instruction_accounts.iter().enumerate() {
|
||||||
// Note: This is an O(n^2) algorithm,
|
match instruction_accounts.get(..index) {
|
||||||
// but performed on a very small slice and requires no heap allocations
|
Some(range) => {
|
||||||
for before in instruction_accounts
|
for before in range.iter() {
|
||||||
.get(..index)
|
if before.index_in_transaction == instruction_account.index_in_transaction {
|
||||||
.ok_or(InstructionError::NotEnoughAccountKeys)?
|
continue 'root; // skip dups
|
||||||
.iter()
|
}
|
||||||
{
|
}
|
||||||
if before.index_in_transaction == instruction_account.index_in_transaction {
|
work(index, instruction_account)?;
|
||||||
continue 'root; // skip dups
|
|
||||||
}
|
}
|
||||||
|
None => return Err(inner_error),
|
||||||
}
|
}
|
||||||
work(index, instruction_account)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1211,7 +1225,8 @@ mod tests {
|
|||||||
index_sum_b += entry.index_in_transaction;
|
index_sum_b += entry.index_in_transaction;
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
visit_each_account_once(accounts, &mut work).unwrap();
|
visit_each_account_once(accounts, &mut work, InstructionError::NotEnoughAccountKeys)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
(unique_entries, index_sum_a, index_sum_b)
|
(unique_entries, index_sum_a, index_sum_b)
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ use {
|
|||||||
alloc::Alloc,
|
alloc::Alloc,
|
||||||
solana_program_runtime::{
|
solana_program_runtime::{
|
||||||
ic_logger_msg, ic_msg,
|
ic_logger_msg, ic_msg,
|
||||||
invoke_context::{ComputeMeter, InvokeContext},
|
invoke_context::{visit_each_account_once, ComputeMeter, InvokeContext},
|
||||||
stable_log,
|
stable_log,
|
||||||
timings::ExecuteTimings,
|
timings::ExecuteTimings,
|
||||||
},
|
},
|
||||||
@ -2574,68 +2574,76 @@ where
|
|||||||
))?;
|
))?;
|
||||||
accounts.push((*program_account_index, None));
|
accounts.push((*program_account_index, None));
|
||||||
|
|
||||||
for instruction_account in instruction_accounts.iter() {
|
visit_each_account_once::<EbpfError<BpfError>>(
|
||||||
let account = invoke_context
|
instruction_accounts,
|
||||||
.transaction_context
|
&mut |_index: usize, instruction_account: &InstructionAccount| {
|
||||||
.get_account_at_index(instruction_account.index_in_transaction)
|
let account = invoke_context
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.transaction_context
|
||||||
let account_key = invoke_context
|
.get_account_at_index(instruction_account.index_in_transaction)
|
||||||
.transaction_context
|
.map_err(SyscallError::InstructionError)?;
|
||||||
.get_key_of_account_at_index(instruction_account.index_in_transaction)
|
let account_key = invoke_context
|
||||||
.map_err(SyscallError::InstructionError)?;
|
.transaction_context
|
||||||
if account.borrow().executable() {
|
.get_key_of_account_at_index(instruction_account.index_in_transaction)
|
||||||
// Use the known account
|
.map_err(SyscallError::InstructionError)?;
|
||||||
accounts.push((instruction_account.index_in_transaction, None));
|
if account.borrow().executable() {
|
||||||
} else if let Some(caller_account_index) =
|
// Use the known account
|
||||||
account_info_keys.iter().position(|key| *key == account_key)
|
accounts.push((instruction_account.index_in_transaction, None));
|
||||||
{
|
} else if let Some(caller_account_index) =
|
||||||
let mut caller_account = do_translate(
|
account_info_keys.iter().position(|key| *key == account_key)
|
||||||
account_infos
|
|
||||||
.get(caller_account_index)
|
|
||||||
.ok_or(SyscallError::InvalidLength)?,
|
|
||||||
invoke_context,
|
|
||||||
)?;
|
|
||||||
{
|
{
|
||||||
let mut account = account.borrow_mut();
|
let mut caller_account = do_translate(
|
||||||
account.copy_into_owner_from_slice(caller_account.owner.as_ref());
|
account_infos
|
||||||
account.set_data_from_slice(caller_account.data);
|
.get(caller_account_index)
|
||||||
account.set_lamports(*caller_account.lamports);
|
.ok_or(SyscallError::InvalidLength)?,
|
||||||
account.set_executable(caller_account.executable);
|
invoke_context,
|
||||||
account.set_rent_epoch(caller_account.rent_epoch);
|
)?;
|
||||||
}
|
{
|
||||||
let caller_account = if instruction_account.is_writable {
|
let mut account = account.borrow_mut();
|
||||||
let orig_data_len_index = instruction_account
|
account.copy_into_owner_from_slice(caller_account.owner.as_ref());
|
||||||
.index_in_caller
|
account.set_data_from_slice(caller_account.data);
|
||||||
.saturating_sub(instruction_context.get_number_of_program_accounts());
|
account.set_lamports(*caller_account.lamports);
|
||||||
if orig_data_len_index < orig_data_lens.len() {
|
account.set_executable(caller_account.executable);
|
||||||
caller_account.original_data_len = *orig_data_lens
|
account.set_rent_epoch(caller_account.rent_epoch);
|
||||||
.get(orig_data_len_index)
|
|
||||||
.ok_or(SyscallError::InvalidLength)?;
|
|
||||||
} else {
|
|
||||||
ic_msg!(
|
|
||||||
invoke_context,
|
|
||||||
"Internal error: index mismatch for account {}",
|
|
||||||
account_key
|
|
||||||
);
|
|
||||||
return Err(
|
|
||||||
SyscallError::InstructionError(InstructionError::MissingAccount).into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
let caller_account = if instruction_account.is_writable {
|
||||||
|
let orig_data_len_index = instruction_account
|
||||||
|
.index_in_caller
|
||||||
|
.saturating_sub(instruction_context.get_number_of_program_accounts());
|
||||||
|
if orig_data_len_index < orig_data_lens.len() {
|
||||||
|
caller_account.original_data_len = *orig_data_lens
|
||||||
|
.get(orig_data_len_index)
|
||||||
|
.ok_or(SyscallError::InvalidLength)?;
|
||||||
|
} else {
|
||||||
|
ic_msg!(
|
||||||
|
invoke_context,
|
||||||
|
"Internal error: index mismatch for account {}",
|
||||||
|
account_key
|
||||||
|
);
|
||||||
|
return Err(SyscallError::InstructionError(
|
||||||
|
InstructionError::MissingAccount,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
Some(caller_account)
|
Some(caller_account)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
accounts.push((instruction_account.index_in_transaction, caller_account));
|
||||||
} else {
|
} else {
|
||||||
None
|
ic_msg!(
|
||||||
};
|
invoke_context,
|
||||||
accounts.push((instruction_account.index_in_transaction, caller_account));
|
"Instruction references an unknown account {}",
|
||||||
} else {
|
account_key
|
||||||
ic_msg!(
|
);
|
||||||
invoke_context,
|
return Err(
|
||||||
"Instruction references an unknown account {}",
|
SyscallError::InstructionError(InstructionError::MissingAccount).into(),
|
||||||
account_key
|
);
|
||||||
);
|
}
|
||||||
return Err(SyscallError::InstructionError(InstructionError::MissingAccount).into());
|
Ok(())
|
||||||
}
|
},
|
||||||
}
|
SyscallError::InstructionError(InstructionError::NotEnoughAccountKeys).into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user