Refactor accounts.rs with Justin's comments to improve lock accounts (#21406)
with results code path. - fix a bug that could unlock accounts that weren't locked - add test to the refactored function - skip enumerating transaction accounts if qos results is an error - add #[must_use] annotation - avoid clone error in results - add qos error code to unlock_accounts match statement - remove unnecessary AbiExample
This commit is contained in:
parent
9fc3bf189d
commit
9edfc5936d
|
@ -940,7 +940,8 @@ impl BankingStage {
|
||||||
// Once accounts are locked, other threads cannot encode transactions that will modify the
|
// Once accounts are locked, other threads cannot encode transactions that will modify the
|
||||||
// same account state
|
// same account state
|
||||||
let mut lock_time = Measure::start("lock_time");
|
let mut lock_time = Measure::start("lock_time");
|
||||||
let batch = bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.iter());
|
let batch =
|
||||||
|
bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.into_iter());
|
||||||
lock_time.stop();
|
lock_time.stop();
|
||||||
|
|
||||||
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit and
|
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit and
|
||||||
|
|
|
@ -24,6 +24,10 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct QosService {
|
pub struct QosService {
|
||||||
|
// cost_model instance is owned by validator, shared between replay_stage and
|
||||||
|
// banking_stage. replay_stage writes the latest on-chain program timings to
|
||||||
|
// it; banking_stage's qos_service reads that information to calculate
|
||||||
|
// transaction cost, hence RwLock wrapped.
|
||||||
cost_model: Arc<RwLock<CostModel>>,
|
cost_model: Arc<RwLock<CostModel>>,
|
||||||
metrics: Arc<QosServiceMetrics>,
|
metrics: Arc<QosServiceMetrics>,
|
||||||
reporting_thread: Option<JoinHandle<()>>,
|
reporting_thread: Option<JoinHandle<()>>,
|
||||||
|
|
|
@ -969,22 +969,27 @@ impl Accounts {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
#[allow(clippy::needless_collect)]
|
#[allow(clippy::needless_collect)]
|
||||||
pub fn lock_accounts_with_results<'a>(
|
pub fn lock_accounts_with_results<'a>(
|
||||||
&self,
|
&self,
|
||||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||||
results: impl Iterator<Item = &'a Result<()>>,
|
results: impl Iterator<Item = Result<()>>,
|
||||||
demote_program_write_locks: bool,
|
demote_program_write_locks: bool,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
let keys: Vec<_> = txs
|
let key_results: Vec<_> = txs
|
||||||
.map(|tx| tx.get_account_locks(demote_program_write_locks))
|
.zip(results)
|
||||||
|
.map(|(tx, result)| match result {
|
||||||
|
Ok(()) => Ok(tx.get_account_locks(demote_program_write_locks)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let account_locks = &mut self.account_locks.lock().unwrap();
|
let account_locks = &mut self.account_locks.lock().unwrap();
|
||||||
keys.into_iter()
|
key_results
|
||||||
.zip(results)
|
.into_iter()
|
||||||
.map(|(keys, result)| match result {
|
.map(|key_result| match key_result {
|
||||||
Ok(()) => self.lock_account(account_locks, keys.writable, keys.readonly),
|
Ok(keys) => self.lock_account(account_locks, keys.writable, keys.readonly),
|
||||||
Err(e) => Err(e.clone()),
|
Err(e) => Err(e),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -1003,6 +1008,8 @@ impl Accounts {
|
||||||
Err(TransactionError::AccountInUse) => None,
|
Err(TransactionError::AccountInUse) => None,
|
||||||
Err(TransactionError::SanitizeFailure) => None,
|
Err(TransactionError::SanitizeFailure) => None,
|
||||||
Err(TransactionError::AccountLoadedTwice) => None,
|
Err(TransactionError::AccountLoadedTwice) => None,
|
||||||
|
Err(TransactionError::WouldExceedMaxBlockCostLimit) => None,
|
||||||
|
Err(TransactionError::WouldExceedMaxAccountCostLimit) => None,
|
||||||
_ => Some(tx.get_account_locks(demote_program_write_locks)),
|
_ => Some(tx.get_account_locks(demote_program_write_locks)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -2391,6 +2398,117 @@ mod tests {
|
||||||
.contains(&keypair1.pubkey()));
|
.contains(&keypair1.pubkey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_accounts_locks_with_results() {
|
||||||
|
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,
|
||||||
|
2,
|
||||||
|
vec![keypair1.pubkey(), keypair0.pubkey(), native_loader::id()],
|
||||||
|
Hash::default(),
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
let tx0 = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||||
|
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||||
|
let message = Message::new_with_compiled_instructions(
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
vec![keypair2.pubkey(), keypair0.pubkey(), native_loader::id()],
|
||||||
|
Hash::default(),
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
let tx1 = new_sanitized_tx(&[&keypair2], message, Hash::default());
|
||||||
|
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||||
|
let message = Message::new_with_compiled_instructions(
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
vec![keypair3.pubkey(), keypair0.pubkey(), native_loader::id()],
|
||||||
|
Hash::default(),
|
||||||
|
instructions,
|
||||||
|
);
|
||||||
|
let tx2 = new_sanitized_tx(&[&keypair3], message, Hash::default());
|
||||||
|
let txs = vec![tx0, tx1, tx2];
|
||||||
|
|
||||||
|
let qos_results = vec![
|
||||||
|
Ok(()),
|
||||||
|
Err(TransactionError::WouldExceedMaxBlockCostLimit),
|
||||||
|
Ok(()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let results = accounts.lock_accounts_with_results(
|
||||||
|
txs.iter(),
|
||||||
|
qos_results.into_iter(),
|
||||||
|
demote_program_write_locks,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times
|
||||||
|
assert!(results[1].is_err()); // is not locked due to !qos_results[1].is_ok()
|
||||||
|
assert!(results[2].is_ok()); // Read-only account (keypair0) can be referenced multiple times
|
||||||
|
|
||||||
|
// verify that keypair0 read-only lock twice (for tx0 and tx2)
|
||||||
|
assert_eq!(
|
||||||
|
*accounts
|
||||||
|
.account_locks
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.readonly_locks
|
||||||
|
.get(&keypair0.pubkey())
|
||||||
|
.unwrap(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
// verify that keypair2 (for tx1) is not write-locked
|
||||||
|
assert!(accounts
|
||||||
|
.account_locks
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.write_locks
|
||||||
|
.get(&keypair2.pubkey())
|
||||||
|
.is_none());
|
||||||
|
|
||||||
|
accounts.unlock_accounts(txs.iter(), &results, demote_program_write_locks);
|
||||||
|
|
||||||
|
// check all locks to be removed
|
||||||
|
assert!(accounts
|
||||||
|
.account_locks
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.readonly_locks
|
||||||
|
.is_empty());
|
||||||
|
assert!(accounts
|
||||||
|
.account_locks
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.write_locks
|
||||||
|
.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collect_accounts_to_store() {
|
fn test_collect_accounts_to_store() {
|
||||||
let keypair0 = Keypair::new();
|
let keypair0 = Keypair::new();
|
||||||
|
|
|
@ -3356,7 +3356,7 @@ impl Bank {
|
||||||
pub fn prepare_sanitized_batch_with_results<'a, 'b>(
|
pub fn prepare_sanitized_batch_with_results<'a, 'b>(
|
||||||
&'a self,
|
&'a self,
|
||||||
transactions: &'b [SanitizedTransaction],
|
transactions: &'b [SanitizedTransaction],
|
||||||
transaction_results: impl Iterator<Item = &'b Result<()>>,
|
transaction_results: impl Iterator<Item = Result<()>>,
|
||||||
) -> TransactionBatch<'a, 'b> {
|
) -> TransactionBatch<'a, 'b> {
|
||||||
// this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit
|
// this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit
|
||||||
let lock_results = self.rc.accounts.lock_accounts_with_results(
|
let lock_results = self.rc.accounts.lock_accounts_with_results(
|
||||||
|
|
|
@ -14,17 +14,15 @@ use std::collections::HashMap;
|
||||||
const MAX_WRITABLE_ACCOUNTS: usize = 256;
|
const MAX_WRITABLE_ACCOUNTS: usize = 256;
|
||||||
|
|
||||||
// costs are stored in number of 'compute unit's
|
// costs are stored in number of 'compute unit's
|
||||||
#[derive(AbiExample, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TransactionCost {
|
pub struct TransactionCost {
|
||||||
pub writable_accounts: Vec<Pubkey>,
|
pub writable_accounts: Vec<Pubkey>,
|
||||||
pub signature_cost: u64,
|
pub signature_cost: u64,
|
||||||
pub write_lock_cost: u64,
|
pub write_lock_cost: u64,
|
||||||
pub data_bytes_cost: u64,
|
pub data_bytes_cost: u64,
|
||||||
pub execution_cost: u64,
|
pub execution_cost: u64,
|
||||||
// `cost_weight` is a multiplier to be applied to tx cost, that
|
// `cost_weight` is a multiplier could be applied to transaction cost,
|
||||||
// allows to increase/decrease tx cost linearly based on algo.
|
// if set to zero allows the transaction to bypass cost limit check.
|
||||||
// for example, vote tx could have weight zero to bypass cost
|
|
||||||
// limit checking during block packing.
|
|
||||||
pub cost_weight: u32,
|
pub cost_weight: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue