Demote write locks on transaction program ids (#19593)

* Add feature

* Demote write lock on program ids

* Fixup bpf tests

* Update MappedMessage::is_writable

* Comma nit

* Review comments
This commit is contained in:
Tyera Eulberg
2021-09-03 21:05:30 -06:00
committed by GitHub
parent 7578db7ee3
commit decec3cd8b
20 changed files with 297 additions and 177 deletions

View File

@@ -201,8 +201,9 @@ impl Accounts {
fn construct_instructions_account(
message: &SanitizedMessage,
is_owned_by_sysvar: bool,
demote_program_write_locks: bool,
) -> AccountSharedData {
let mut data = message.serialize_instructions();
let mut data = message.serialize_instructions(demote_program_write_locks);
// add room for current instruction index.
data.resize(data.len() + 2, 0);
let owner = if is_owned_by_sysvar {
@@ -240,6 +241,8 @@ impl Accounts {
let mut account_deps = Vec::with_capacity(message.account_keys_len());
let mut rent_debits = RentDebits::default();
let rent_for_sysvars = feature_set.is_active(&feature_set::rent_for_sysvars::id());
let demote_program_write_locks =
feature_set.is_active(&feature_set::demote_program_write_locks::id());
for (i, key) in message.account_keys_iter().enumerate() {
let account = if message.is_non_loader_key(i) {
@@ -248,20 +251,21 @@ impl Accounts {
}
if solana_sdk::sysvar::instructions::check_id(key) {
if message.is_writable(i) {
if message.is_writable(i, demote_program_write_locks) {
return Err(TransactionError::InvalidAccountIndex);
}
Self::construct_instructions_account(
message,
feature_set
.is_active(&feature_set::instructions_sysvar_owned_by_sysvar::id()),
demote_program_write_locks,
)
} else {
let (account, rent) = self
.accounts_db
.load_with_fixed_root(ancestors, key)
.map(|(mut account, _)| {
if message.is_writable(i) {
if message.is_writable(i, demote_program_write_locks) {
let rent_due = rent_collector.collect_from_existing_account(
key,
&mut account,
@@ -886,8 +890,11 @@ impl Accounts {
pub fn lock_accounts<'a>(
&self,
txs: impl Iterator<Item = &'a SanitizedTransaction>,
demote_program_write_locks: bool,
) -> Vec<Result<()>> {
let keys: Vec<_> = txs.map(|tx| tx.get_account_locks()).collect();
let keys: Vec<_> = txs
.map(|tx| tx.get_account_locks(demote_program_write_locks))
.collect();
let mut account_locks = &mut self.account_locks.lock().unwrap();
keys.into_iter()
.map(|keys| self.lock_account(&mut account_locks, keys.writable, keys.readonly))
@@ -900,6 +907,7 @@ impl Accounts {
&self,
txs: impl Iterator<Item = &'a SanitizedTransaction>,
results: &[Result<()>],
demote_program_write_locks: bool,
) {
let keys: Vec<_> = txs
.zip(results)
@@ -907,7 +915,7 @@ impl Accounts {
Err(TransactionError::AccountInUse) => None,
Err(TransactionError::SanitizeFailure) => None,
Err(TransactionError::AccountLoadedTwice) => None,
_ => Some(tx.get_account_locks()),
_ => Some(tx.get_account_locks(demote_program_write_locks)),
})
.collect();
let mut account_locks = self.account_locks.lock().unwrap();
@@ -930,6 +938,7 @@ impl Accounts {
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool,
) {
let accounts_to_store = self.collect_accounts_to_store(
txs,
@@ -939,6 +948,7 @@ impl Accounts {
last_blockhash_with_fee_calculator,
rent_for_sysvars,
merge_nonce_error_into_system_error,
demote_program_write_locks,
);
self.accounts_db.store_cached(slot, &accounts_to_store);
}
@@ -964,6 +974,7 @@ impl Accounts {
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
let mut accounts = Vec::with_capacity(loaded.len());
for (i, ((raccs, _nonce_rollback), tx)) in loaded.iter_mut().zip(txs).enumerate() {
@@ -1012,7 +1023,7 @@ impl Accounts {
fee_payer_index = Some(i);
}
let is_fee_payer = Some(i) == fee_payer_index;
if message.is_writable(i)
if message.is_writable(i, demote_program_write_locks)
&& (res.is_ok()
|| (maybe_nonce_rollback.is_some() && (is_nonce_account || is_fee_payer)))
{
@@ -1731,6 +1742,8 @@ mod tests {
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
accounts.store_slow_uncached(0, &keypair3.pubkey(), &account3);
let demote_program_write_locks = true;
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
@@ -1741,7 +1754,7 @@ mod tests {
instructions,
);
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
let results0 = accounts.lock_accounts([tx.clone()].iter());
let results0 = accounts.lock_accounts([tx.clone()].iter(), demote_program_write_locks);
assert!(results0[0].is_ok());
assert_eq!(
@@ -1776,7 +1789,7 @@ mod tests {
);
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
let txs = vec![tx0, tx1];
let results1 = accounts.lock_accounts(txs.iter());
let results1 = accounts.lock_accounts(txs.iter(), demote_program_write_locks);
assert!(results1[0].is_ok()); // Read-only account (keypair1) can be referenced multiple times
assert!(results1[1].is_err()); // Read-only account (keypair1) cannot also be locked as writable
@@ -1791,8 +1804,8 @@ mod tests {
2
);
accounts.unlock_accounts([tx].iter(), &results0);
accounts.unlock_accounts(txs.iter(), &results1);
accounts.unlock_accounts([tx].iter(), &results0, demote_program_write_locks);
accounts.unlock_accounts(txs.iter(), &results1, demote_program_write_locks);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
@@ -1803,7 +1816,7 @@ mod tests {
instructions,
);
let tx = new_sanitized_tx(&[&keypair1], message, Hash::default());
let results2 = accounts.lock_accounts([tx].iter());
let results2 = accounts.lock_accounts([tx].iter(), demote_program_write_locks);
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
// Check that read-only lock with zero references is deleted
@@ -1840,6 +1853,8 @@ mod tests {
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
let demote_program_write_locks = true;
let accounts_arc = Arc::new(accounts);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
@@ -1872,13 +1887,15 @@ mod tests {
let exit_clone = exit_clone.clone();
loop {
let txs = vec![writable_tx.clone()];
let results = accounts_clone.clone().lock_accounts(txs.iter());
let results = accounts_clone
.clone()
.lock_accounts(txs.iter(), demote_program_write_locks);
for result in results.iter() {
if result.is_ok() {
counter_clone.clone().fetch_add(1, Ordering::SeqCst);
}
}
accounts_clone.unlock_accounts(txs.iter(), &results);
accounts_clone.unlock_accounts(txs.iter(), &results, demote_program_write_locks);
if exit_clone.clone().load(Ordering::Relaxed) {
break;
}
@@ -1887,18 +1904,85 @@ mod tests {
let counter_clone = counter;
for _ in 0..5 {
let txs = vec![readonly_tx.clone()];
let results = accounts_arc.clone().lock_accounts(txs.iter());
let results = accounts_arc
.clone()
.lock_accounts(txs.iter(), demote_program_write_locks);
if results[0].is_ok() {
let counter_value = counter_clone.clone().load(Ordering::SeqCst);
thread::sleep(time::Duration::from_millis(50));
assert_eq!(counter_value, counter_clone.clone().load(Ordering::SeqCst));
}
accounts_arc.unlock_accounts(txs.iter(), &results);
accounts_arc.unlock_accounts(txs.iter(), &results, demote_program_write_locks);
thread::sleep(time::Duration::from_millis(50));
}
exit.store(true, Ordering::Relaxed);
}
#[test]
fn test_demote_program_write_locks() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let account3 = AccountSharedData::new(4, 0, &Pubkey::default());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0);
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
accounts.store_slow_uncached(0, &keypair3.pubkey(), &account3);
let demote_program_write_locks = true;
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
0, // All accounts marked as writable
vec![keypair0.pubkey(), keypair1.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
let results0 = accounts.lock_accounts([tx].iter(), demote_program_write_locks);
assert!(results0[0].is_ok());
// Instruction program-id account demoted to readonly
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&native_loader::id())
.unwrap(),
1
);
// Non-program accounts remain writable
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.contains(&keypair0.pubkey()));
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.contains(&keypair1.pubkey()));
}
#[test]
fn test_collect_accounts_to_store() {
let keypair0 = Keypair::new();
@@ -1991,6 +2075,7 @@ mod tests {
&(Hash::default(), FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
);
assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts
@@ -2370,6 +2455,7 @@ mod tests {
&(next_blockhash, FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
);
assert_eq!(collected_accounts.len(), 2);
assert_eq!(
@@ -2487,6 +2573,7 @@ mod tests {
&(next_blockhash, FeeCalculator::default()),
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
);
assert_eq!(collected_accounts.len(), 1);
let collected_nonce_account = collected_accounts