Add runtime support for address table lookups (backport #22223) (#22354)

This commit is contained in:
Justin Starry
2022-01-08 07:57:04 +08:00
committed by GitHub
parent 662c6be51e
commit 1f00926874
21 changed files with 663 additions and 159 deletions

View File

@@ -22,6 +22,7 @@ use {
},
log::*,
rand::{thread_rng, Rng},
solana_address_lookup_table_program::state::AddressLookupTable,
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
@@ -30,13 +31,19 @@ use {
feature_set::{self, FeatureSet},
genesis_config::ClusterType,
hash::Hash,
message::SanitizedMessage,
message::{
v0::{LoadedAddresses, MessageAddressTableLookup},
SanitizedMessage,
},
native_loader,
nonce::{state::Versions as NonceVersions, State as NonceState},
pubkey::Pubkey,
system_program,
sysvar::{self, instructions::construct_instructions_data},
transaction::{Result, SanitizedTransaction, TransactionAccountLocks, TransactionError},
sysvar::{self, instructions::construct_instructions_data, slot_hashes::SlotHashes},
transaction::{
AddressLookupError, Result, SanitizedTransaction, TransactionAccountLocks,
TransactionError,
},
},
std::{
cmp::Reverse,
@@ -223,6 +230,40 @@ impl Accounts {
})
}
pub fn load_lookup_table_addresses(
&self,
ancestors: &Ancestors,
address_table_lookup: &MessageAddressTableLookup,
slot_hashes: &SlotHashes,
) -> std::result::Result<LoadedAddresses, AddressLookupError> {
let table_account = self
.accounts_db
.load_with_fixed_root(ancestors, &address_table_lookup.account_key)
.map(|(account, _rent)| account)
.ok_or(AddressLookupError::LookupTableAccountNotFound)?;
if table_account.owner() == &solana_address_lookup_table_program::id() {
let current_slot = ancestors.max_slot();
let lookup_table = AddressLookupTable::deserialize(table_account.data())
.map_err(|_ix_err| AddressLookupError::InvalidAccountData)?;
Ok(LoadedAddresses {
writable: lookup_table.lookup(
current_slot,
&address_table_lookup.writable_indexes,
slot_hashes,
)?,
readonly: lookup_table.lookup(
current_slot,
&address_table_lookup.readonly_indexes,
slot_hashes,
)?,
})
} else {
Err(AddressLookupError::InvalidAccountOwner)
}
}
fn load_transaction(
&self,
ancestors: &Ancestors,
@@ -1242,6 +1283,7 @@ mod tests {
bank::{DurableNonceFee, TransactionExecutionDetails},
rent_collector::RentCollector,
},
solana_address_lookup_table_program::state::LookupTableMeta,
solana_sdk::{
account::{AccountSharedData, WritableAccount},
epoch_schedule::EpochSchedule,
@@ -1256,6 +1298,7 @@ mod tests {
transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS},
},
std::{
borrow::Cow,
convert::TryFrom,
sync::atomic::{AtomicBool, AtomicU64, Ordering},
thread, time,
@@ -1836,6 +1879,149 @@ mod tests {
}
}
#[test]
fn test_load_lookup_table_addresses_account_not_found() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::LookupTableAccountNotFound),
);
}
#[test]
fn test_load_lookup_table_addresses_invalid_account_owner() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let invalid_table_account = AccountSharedData::default();
accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::InvalidAccountOwner),
);
}
#[test]
fn test_load_lookup_table_addresses_invalid_account_data() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let invalid_table_account =
AccountSharedData::new(1, 0, &solana_address_lookup_table_program::id());
accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::InvalidAccountData),
);
}
#[test]
fn test_load_lookup_table_addresses() {
let ancestors = vec![(1, 1), (0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let table_key = Pubkey::new_unique();
let table_addresses = vec![Pubkey::new_unique(), Pubkey::new_unique()];
let table_account = {
let table_state = AddressLookupTable {
meta: LookupTableMeta::default(),
addresses: Cow::Owned(table_addresses.clone()),
};
let table_data = {
let mut data = vec![];
table_state.serialize_for_tests(&mut data).unwrap();
data
};
AccountSharedData::create(
1,
table_data,
solana_address_lookup_table_program::id(),
false,
0,
)
};
accounts.store_slow_uncached(0, &table_key, &table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: table_key,
writable_indexes: vec![0],
readonly_indexes: vec![1],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Ok(LoadedAddresses {
writable: vec![table_addresses[0]],
readonly: vec![table_addresses[1]],
}),
);
}
#[test]
fn test_load_by_program_slot() {
let accounts = Accounts::new_with_config_for_tests(

View File

@@ -111,7 +111,10 @@ use {
inflation::Inflation,
instruction::CompiledInstruction,
lamports::LamportsError,
message::SanitizedMessage,
message::{
v0::{LoadedAddresses, MessageAddressTableLookup},
SanitizedMessage,
},
native_loader,
native_token::sol_to_lamports,
nonce, nonce_account,
@@ -127,7 +130,7 @@ use {
sysvar::{self, Sysvar, SysvarId},
timing::years_as_slots,
transaction::{
Result, SanitizedTransaction, Transaction, TransactionError,
AddressLookupError, Result, SanitizedTransaction, Transaction, TransactionError,
TransactionVerificationMode, VersionedTransaction,
},
},
@@ -213,7 +216,7 @@ impl RentDebits {
}
type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "6XG6H1FChrDdY39K62KFWj5XfDao4dd24WZgcJkdMu1E")]
#[frozen_abi(digest = "2r36f5cfgP7ABq7D3kRkRfQZWdggGFUnnhwTrVEWhoTC")]
pub type BankSlotDelta = SlotDelta<Result<()>>;
// Eager rent collection repeats in cyclic manner.
@@ -3522,6 +3525,44 @@ impl Bank {
Arc::make_mut(&mut cache).remove(pubkey);
}
/// Get the value of a cached sysvar by its id
pub fn get_cached_sysvar<T: Sysvar>(&self, id: &Pubkey) -> Option<T> {
self.sysvar_cache
.read()
.unwrap()
.iter()
.find_map(|(key, data)| {
if id == key {
bincode::deserialize(data).ok()
} else {
None
}
})
}
pub fn load_lookup_table_addresses(
&self,
address_table_lookups: &[MessageAddressTableLookup],
) -> Result<LoadedAddresses> {
if !self.versioned_tx_message_enabled() {
return Err(TransactionError::UnsupportedVersion);
}
let slot_hashes: SlotHashes = self
.get_cached_sysvar(&sysvar::slot_hashes::id())
.ok_or(TransactionError::AccountNotFound)?;
Ok(address_table_lookups
.iter()
.map(|address_table_lookup| {
self.rc.accounts.load_lookup_table_addresses(
&self.ancestors,
address_table_lookup,
&slot_hashes,
)
})
.collect::<std::result::Result<_, AddressLookupError>>()?)
}
/// Execute a transaction using the provided loaded accounts and update
/// the executors cache if the transaction was successful.
fn execute_loaded_transaction(
@@ -3535,16 +3576,6 @@ impl Bank {
timings: &mut ExecuteTimings,
error_counters: &mut ErrorCounters,
) -> TransactionExecutionResult {
let legacy_message = match tx.message().legacy_message() {
Some(message) => message,
None => {
// TODO: support versioned messages
return TransactionExecutionResult::NotExecuted(
TransactionError::UnsupportedVersion,
);
}
};
let mut get_executors_time = Measure::start("get_executors_time");
let executors = self.get_executors(
tx.message(),
@@ -3579,7 +3610,7 @@ impl Bank {
let mut process_message_time = Measure::start("process_message_time");
let process_result = MessageProcessor::process_message(
&self.builtin_programs.vec,
legacy_message,
tx.message(),
&loaded_transaction.program_indices,
&account_refcells,
self.rent_collector.rent,
@@ -5336,8 +5367,8 @@ impl Bank {
tx.message.hash()
};
SanitizedTransaction::try_create(tx, message_hash, None, |_| {
Err(TransactionError::UnsupportedVersion)
SanitizedTransaction::try_create(tx, message_hash, None, |lookups| {
self.load_lookup_table_addresses(lookups)
})
}?;

View File

@@ -15,7 +15,7 @@ use {
compute_budget::ComputeBudget,
feature_set::{prevent_calling_precompiles_as_programs, FeatureSet},
hash::Hash,
message::Message,
message::SanitizedMessage,
precompiles::is_precompile,
pubkey::Pubkey,
rent::Rent,
@@ -54,7 +54,7 @@ impl MessageProcessor {
#[allow(clippy::too_many_arguments)]
pub fn process_message(
builtin_programs: &[BuiltinProgram],
message: &Message,
message: &SanitizedMessage,
program_indices: &[Vec<usize>],
accounts: &[TransactionAccountRefCell],
rent: Rent,
@@ -83,14 +83,12 @@ impl MessageProcessor {
current_accounts_data_len,
);
debug_assert_eq!(program_indices.len(), message.instructions.len());
for (instruction_index, (instruction, program_indices)) in message
.instructions
.iter()
debug_assert_eq!(program_indices.len(), message.instructions().len());
for (instruction_index, ((program_id, instruction), program_indices)) in message
.program_instructions_iter()
.zip(program_indices.iter())
.enumerate()
{
let program_id = instruction.program_id(&message.account_keys);
if invoke_context
.feature_set
.is_active(&prevent_calling_precompiles_as_programs::id())
@@ -102,7 +100,7 @@ impl MessageProcessor {
// Fixup the special instructions key if present
// before the account pre-values are taken care of
for (pubkey, account) in accounts.iter().take(message.account_keys.len()) {
for (pubkey, account) in accounts.iter().take(message.account_keys_len()) {
if instructions::check_id(pubkey) {
let mut mut_account_ref = account.borrow_mut();
instructions::store_current_index(
@@ -131,7 +129,7 @@ impl MessageProcessor {
);
time.stop();
timings.details.accumulate_program(
instruction.program_id(&message.account_keys),
program_id,
time.as_us(),
compute_units_consumed,
result.is_err(),
@@ -247,14 +245,14 @@ mod tests {
AccountMeta::new(accounts[0].0, true),
AccountMeta::new_readonly(accounts[1].0, false),
];
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::Correct,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
@@ -277,14 +275,14 @@ mod tests {
assert_eq!(accounts[0].1.borrow().lamports(), 100);
assert_eq!(accounts[1].1.borrow().lamports(), 0);
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::AttemptCredit { lamports: 50 },
account_metas.clone(),
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
@@ -311,14 +309,14 @@ mod tests {
))
);
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::AttemptDataChange { data: 50 },
account_metas,
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
@@ -457,14 +455,14 @@ mod tests {
];
// Try to borrow mut the same account
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::BorrowFail,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
&message,
@@ -491,14 +489,14 @@ mod tests {
);
// Try to borrow mut the same account in a safe way
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::MultiBorrowMut,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
&message,
@@ -519,7 +517,7 @@ mod tests {
assert!(result.is_ok());
// Do work on the same account but at different location in keyed_accounts[]
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::DoWork {
@@ -529,7 +527,7 @@ mod tests {
account_metas,
)],
Some(&accounts[0].0),
);
));
let result = MessageProcessor::process_message(
builtin_programs,
&message,
@@ -577,7 +575,7 @@ mod tests {
(mock_program_id, mock_program_account),
];
let message = Message::new(
let message = SanitizedMessage::Legacy(Message::new(
&[
new_secp256k1_instruction(
&libsecp256k1::SecretKey::random(&mut rand::thread_rng()),
@@ -586,7 +584,7 @@ mod tests {
Instruction::new_with_bytes(mock_program_id, &[], vec![]),
],
None,
);
));
let result = MessageProcessor::process_message(
builtin_programs,