Store versioned transactions in the ledger, disabled by default (#19139)
* Add support for versioned transactions, but disable by default * merge conflicts * trent's feedback * bump Cargo.lock * Fix transaction error encoding * Rename legacy_transaction method * cargo clippy * Clean up casts, int arithmetic, and unused methods * Check for duplicates in sanitized message conversion * fix clippy * fix new test * Fix bpf conditional compilation for message module
This commit is contained in:
@@ -31,12 +31,11 @@ use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
genesis_config::ClusterType,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
message::SanitizedMessage,
|
||||
native_loader, nonce,
|
||||
nonce::NONCED_TX_MARKER_IX_INDEX,
|
||||
pubkey::Pubkey,
|
||||
transaction::Result,
|
||||
transaction::{Transaction, TransactionError},
|
||||
transaction::{Result, SanitizedTransaction, TransactionError},
|
||||
};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
@@ -198,7 +197,7 @@ impl Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_instructions_account(message: &Message) -> AccountSharedData {
|
||||
fn construct_instructions_account(message: &SanitizedMessage) -> AccountSharedData {
|
||||
let mut data = message.serialize_instructions();
|
||||
// add room for current instruction index.
|
||||
data.resize(data.len() + 2, 0);
|
||||
@@ -211,7 +210,7 @@ impl Accounts {
|
||||
fn load_transaction(
|
||||
&self,
|
||||
ancestors: &Ancestors,
|
||||
tx: &Transaction,
|
||||
tx: &SanitizedTransaction,
|
||||
fee: u64,
|
||||
error_counters: &mut ErrorCounters,
|
||||
rent_collector: &RentCollector,
|
||||
@@ -219,19 +218,20 @@ impl Accounts {
|
||||
) -> Result<LoadedTransaction> {
|
||||
// Copy all the accounts
|
||||
let message = tx.message();
|
||||
if tx.signatures.is_empty() && fee != 0 {
|
||||
// NOTE: this check will never fail because `tx` is sanitized
|
||||
if tx.signatures().is_empty() && fee != 0 {
|
||||
Err(TransactionError::MissingSignatureForFee)
|
||||
} else {
|
||||
// There is no way to predict what program will execute without an error
|
||||
// If a fee can pay for execution then the program will be scheduled
|
||||
let mut payer_index = None;
|
||||
let mut tx_rent: TransactionRent = 0;
|
||||
let mut accounts = Vec::with_capacity(message.account_keys.len());
|
||||
let mut account_deps = Vec::with_capacity(message.account_keys.len());
|
||||
let mut accounts = Vec::with_capacity(message.account_keys_len());
|
||||
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());
|
||||
|
||||
for (i, key) in message.account_keys.iter().enumerate() {
|
||||
for (i, key) in message.account_keys_iter().enumerate() {
|
||||
let account = if message.is_non_loader_key(i) {
|
||||
if payer_index.is_none() {
|
||||
payer_index = Some(i);
|
||||
@@ -296,7 +296,7 @@ impl Accounts {
|
||||
};
|
||||
accounts.push((*key, account));
|
||||
}
|
||||
debug_assert_eq!(accounts.len(), message.account_keys.len());
|
||||
debug_assert_eq!(accounts.len(), message.account_keys_len());
|
||||
// Appends the account_deps at the end of the accounts,
|
||||
// this way they can be accessed in a uniform way.
|
||||
// At places where only the accounts are needed,
|
||||
@@ -336,19 +336,9 @@ impl Accounts {
|
||||
|
||||
let message = tx.message();
|
||||
let loaders = message
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ix| {
|
||||
if message.account_keys.len() <= ix.program_id_index as usize {
|
||||
error_counters.account_not_found += 1;
|
||||
return Err(TransactionError::AccountNotFound);
|
||||
}
|
||||
let program_id = message.account_keys[ix.program_id_index as usize];
|
||||
self.load_executable_accounts(
|
||||
ancestors,
|
||||
&program_id,
|
||||
error_counters,
|
||||
)
|
||||
.program_instructions_iter()
|
||||
.map(|(program_id, _ix)| {
|
||||
self.load_executable_accounts(ancestors, program_id, error_counters)
|
||||
})
|
||||
.collect::<Result<TransactionLoaders>>()?;
|
||||
Ok(LoadedTransaction {
|
||||
@@ -434,17 +424,18 @@ impl Accounts {
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
pub fn load_accounts<'a>(
|
||||
pub fn load_accounts(
|
||||
&self,
|
||||
ancestors: &Ancestors,
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
txs: &[SanitizedTransaction],
|
||||
lock_results: Vec<TransactionCheckResult>,
|
||||
hash_queue: &BlockhashQueue,
|
||||
error_counters: &mut ErrorCounters,
|
||||
rent_collector: &RentCollector,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Vec<TransactionLoadResult> {
|
||||
txs.zip(lock_results)
|
||||
txs.iter()
|
||||
.zip(lock_results)
|
||||
.map(|etx| match etx {
|
||||
(tx, (Ok(()), nonce_rollback)) => {
|
||||
let fee_calculator = nonce_rollback
|
||||
@@ -453,12 +444,11 @@ impl Accounts {
|
||||
.unwrap_or_else(|| {
|
||||
#[allow(deprecated)]
|
||||
hash_queue
|
||||
.get_fee_calculator(&tx.message().recent_blockhash)
|
||||
.get_fee_calculator(tx.message().recent_blockhash())
|
||||
.cloned()
|
||||
});
|
||||
let fee = if let Some(fee_calculator) = fee_calculator {
|
||||
#[allow(deprecated)]
|
||||
fee_calculator.calculate_fee(tx.message())
|
||||
tx.message().calculate_fee(&fee_calculator)
|
||||
} else {
|
||||
return (Err(TransactionError::BlockhashNotFound), None);
|
||||
};
|
||||
@@ -879,15 +869,14 @@ impl Accounts {
|
||||
/// same time
|
||||
#[must_use]
|
||||
#[allow(clippy::needless_collect)]
|
||||
pub fn lock_accounts<'a>(&self, txs: impl Iterator<Item = &'a Transaction>) -> Vec<Result<()>> {
|
||||
let keys: Vec<_> = txs
|
||||
.map(|tx| tx.message().get_account_keys_by_lock_type())
|
||||
.collect();
|
||||
pub fn lock_accounts<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
) -> Vec<Result<()>> {
|
||||
let keys: Vec<_> = txs.map(|tx| tx.get_account_locks()).collect();
|
||||
let mut account_locks = &mut self.account_locks.lock().unwrap();
|
||||
keys.into_iter()
|
||||
.map(|(writable_keys, readonly_keys)| {
|
||||
self.lock_account(&mut account_locks, writable_keys, readonly_keys)
|
||||
})
|
||||
.map(|keys| self.lock_account(&mut account_locks, keys.writable, keys.readonly))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -895,7 +884,7 @@ impl Accounts {
|
||||
#[allow(clippy::needless_collect)]
|
||||
pub fn unlock_accounts<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
results: &[Result<()>],
|
||||
) {
|
||||
let keys: Vec<_> = txs
|
||||
@@ -904,13 +893,13 @@ impl Accounts {
|
||||
Err(TransactionError::AccountInUse) => None,
|
||||
Err(TransactionError::SanitizeFailure) => None,
|
||||
Err(TransactionError::AccountLoadedTwice) => None,
|
||||
_ => Some(tx.message.get_account_keys_by_lock_type()),
|
||||
_ => Some(tx.get_account_locks()),
|
||||
})
|
||||
.collect();
|
||||
let mut account_locks = self.account_locks.lock().unwrap();
|
||||
debug!("bank unlock accounts");
|
||||
keys.into_iter().for_each(|(writable_keys, readonly_keys)| {
|
||||
self.unlock_account(&mut account_locks, writable_keys, readonly_keys);
|
||||
keys.into_iter().for_each(|keys| {
|
||||
self.unlock_account(&mut account_locks, keys.writable, keys.readonly);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -920,7 +909,7 @@ impl Accounts {
|
||||
pub fn store_cached<'a>(
|
||||
&self,
|
||||
slot: Slot,
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
txs: &'a [SanitizedTransaction],
|
||||
res: &'a [TransactionExecutionResult],
|
||||
loaded: &'a mut [TransactionLoadResult],
|
||||
rent_collector: &RentCollector,
|
||||
@@ -954,7 +943,7 @@ impl Accounts {
|
||||
|
||||
fn collect_accounts_to_store<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
txs: &'a [SanitizedTransaction],
|
||||
res: &'a [TransactionExecutionResult],
|
||||
loaded: &'a mut [TransactionLoadResult],
|
||||
rent_collector: &RentCollector,
|
||||
@@ -991,10 +980,10 @@ impl Accounts {
|
||||
(Err(_), _nonce_rollback) => continue,
|
||||
};
|
||||
|
||||
let message = &tx.message();
|
||||
let message = tx.message();
|
||||
let loaded_transaction = raccs.as_mut().unwrap();
|
||||
let mut fee_payer_index = None;
|
||||
for (i, (key, account)) in (0..message.account_keys.len())
|
||||
for (i, (key, account)) in (0..message.account_keys_len())
|
||||
.zip(loaded_transaction.accounts.iter_mut())
|
||||
.filter(|(i, _account)| message.is_non_loader_key(*i))
|
||||
{
|
||||
@@ -1131,14 +1120,25 @@ mod tests {
|
||||
message::Message,
|
||||
nonce, nonce_account,
|
||||
rent::Rent,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
signature::{keypair_from_seed, signers::Signers, Keypair, Signer},
|
||||
system_instruction, system_program,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
{thread, time},
|
||||
};
|
||||
|
||||
fn new_sanitized_tx<T: Signers>(
|
||||
from_keypairs: &T,
|
||||
message: Message,
|
||||
recent_blockhash: Hash,
|
||||
) -> SanitizedTransaction {
|
||||
SanitizedTransaction::try_from(Transaction::new(from_keypairs, message, recent_blockhash))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn load_accounts_with_fee_and_rent(
|
||||
tx: Transaction,
|
||||
ka: &[(Pubkey, AccountSharedData)],
|
||||
@@ -1160,9 +1160,10 @@ mod tests {
|
||||
}
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
let sanitized_tx = SanitizedTransaction::try_from(tx).unwrap();
|
||||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
[tx].iter(),
|
||||
&[sanitized_tx],
|
||||
vec![(Ok(()), None)],
|
||||
&hash_queue,
|
||||
error_counters,
|
||||
@@ -1190,30 +1191,6 @@ mod tests {
|
||||
load_accounts_with_fee(tx, ka, &fee_calculator, error_counters)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_no_key() {
|
||||
let accounts: Vec<(Pubkey, AccountSharedData)> = Vec::new();
|
||||
let mut error_counters = ErrorCounters::default();
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(0, &(), vec![0])];
|
||||
let tx = Transaction::new_with_compiled_instructions::<[&Keypair; 0]>(
|
||||
&[],
|
||||
&[],
|
||||
Hash::default(),
|
||||
vec![native_loader::id()],
|
||||
instructions,
|
||||
);
|
||||
|
||||
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
|
||||
|
||||
assert_eq!(error_counters.account_not_found, 1);
|
||||
assert_eq!(loaded_accounts.len(), 1);
|
||||
assert_eq!(
|
||||
loaded_accounts[0],
|
||||
(Err(TransactionError::AccountNotFound), None,)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_no_account_0_exists() {
|
||||
let accounts: Vec<(Pubkey, AccountSharedData)> = Vec::new();
|
||||
@@ -1522,41 +1499,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_bad_program_id() {
|
||||
let mut accounts: Vec<(Pubkey, AccountSharedData)> = Vec::new();
|
||||
let mut error_counters = ErrorCounters::default();
|
||||
|
||||
let keypair = Keypair::new();
|
||||
let key0 = keypair.pubkey();
|
||||
let key1 = Pubkey::new(&[5u8; 32]);
|
||||
|
||||
let account = AccountSharedData::new(1, 0, &Pubkey::default());
|
||||
accounts.push((key0, account));
|
||||
|
||||
let mut account = AccountSharedData::new(40, 1, &native_loader::id());
|
||||
account.set_executable(true);
|
||||
accounts.push((key1, account));
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(0, &(), vec![0])];
|
||||
let tx = Transaction::new_with_compiled_instructions(
|
||||
&[&keypair],
|
||||
&[],
|
||||
Hash::default(),
|
||||
vec![key1],
|
||||
instructions,
|
||||
);
|
||||
|
||||
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
|
||||
|
||||
assert_eq!(error_counters.invalid_program_for_execution, 1);
|
||||
assert_eq!(loaded_accounts.len(), 1);
|
||||
assert_eq!(
|
||||
loaded_accounts[0],
|
||||
(Err(TransactionError::InvalidProgramForExecution), None,)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_accounts_bad_owner() {
|
||||
let mut accounts: Vec<(Pubkey, AccountSharedData)> = Vec::new();
|
||||
@@ -1784,7 +1726,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let tx = Transaction::new(&[&keypair0], message, Hash::default());
|
||||
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
||||
let results0 = accounts.lock_accounts([tx.clone()].iter());
|
||||
|
||||
assert!(results0[0].is_ok());
|
||||
@@ -1808,7 +1750,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let tx0 = Transaction::new(&[&keypair2], message, Hash::default());
|
||||
let tx0 = new_sanitized_tx(&[&keypair2], message, Hash::default());
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let message = Message::new_with_compiled_instructions(
|
||||
1,
|
||||
@@ -1818,7 +1760,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let tx1 = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||
let txs = vec![tx0, tx1];
|
||||
let results1 = accounts.lock_accounts(txs.iter());
|
||||
|
||||
@@ -1846,7 +1788,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let tx = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let tx = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||
let results2 = accounts.lock_accounts([tx].iter());
|
||||
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
|
||||
|
||||
@@ -1895,7 +1837,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let readonly_tx = Transaction::new(&[&keypair0], readonly_message, Hash::default());
|
||||
let readonly_tx = new_sanitized_tx(&[&keypair0], readonly_message, Hash::default());
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let writable_message = Message::new_with_compiled_instructions(
|
||||
@@ -1906,7 +1848,7 @@ mod tests {
|
||||
Hash::default(),
|
||||
instructions,
|
||||
);
|
||||
let writable_tx = Transaction::new(&[&keypair1], writable_message, Hash::default());
|
||||
let writable_tx = new_sanitized_tx(&[&keypair1], writable_message, Hash::default());
|
||||
|
||||
let counter_clone = counter.clone();
|
||||
let accounts_clone = accounts_arc.clone();
|
||||
@@ -1967,7 +1909,7 @@ mod tests {
|
||||
(message.account_keys[0], account0),
|
||||
(message.account_keys[1], account2.clone()),
|
||||
];
|
||||
let tx0 = Transaction::new(&[&keypair0], message, Hash::default());
|
||||
let tx0 = new_sanitized_tx(&[&keypair0], message, Hash::default());
|
||||
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let message = Message::new_with_compiled_instructions(
|
||||
@@ -1982,7 +1924,7 @@ mod tests {
|
||||
(message.account_keys[0], account1),
|
||||
(message.account_keys[1], account2),
|
||||
];
|
||||
let tx1 = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
|
||||
|
||||
let loaders = vec![(Ok(()), None), (Ok(()), None)];
|
||||
|
||||
@@ -2026,9 +1968,9 @@ mod tests {
|
||||
.unwrap()
|
||||
.insert_new_readonly(&pubkey);
|
||||
}
|
||||
let txs = &[tx0, tx1];
|
||||
let txs = vec![tx0, tx1];
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
txs.iter(),
|
||||
&txs,
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
@@ -2087,16 +2029,17 @@ mod tests {
|
||||
}
|
||||
|
||||
fn load_accounts_no_store(accounts: &Accounts, tx: Transaction) -> Vec<TransactionLoadResult> {
|
||||
let tx = SanitizedTransaction::try_from(tx).unwrap();
|
||||
let rent_collector = RentCollector::default();
|
||||
let fee_calculator = FeeCalculator::new(10);
|
||||
let mut hash_queue = BlockhashQueue::new(100);
|
||||
hash_queue.register_hash(&tx.message().recent_blockhash, &fee_calculator);
|
||||
hash_queue.register_hash(tx.message().recent_blockhash(), &fee_calculator);
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
let mut error_counters = ErrorCounters::default();
|
||||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
[tx].iter(),
|
||||
&[tx],
|
||||
vec![(Ok(()), None)],
|
||||
&hash_queue,
|
||||
&mut error_counters,
|
||||
@@ -2357,7 +2300,7 @@ mod tests {
|
||||
(message.account_keys[3], to_account),
|
||||
(message.account_keys[4], recent_blockhashes_sysvar_account),
|
||||
];
|
||||
let tx = Transaction::new(&[&nonce_authority, &from], message, blockhash);
|
||||
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
|
||||
|
||||
let nonce_state =
|
||||
nonce::state::Versions::new_current(nonce::State::Initialized(nonce::state::Data {
|
||||
@@ -2404,9 +2347,9 @@ mod tests {
|
||||
false,
|
||||
AccountShrinkThreshold::default(),
|
||||
);
|
||||
let txs = &[tx];
|
||||
let txs = vec![tx];
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
txs.iter(),
|
||||
&txs,
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
@@ -2475,7 +2418,7 @@ mod tests {
|
||||
(message.account_keys[3], to_account),
|
||||
(message.account_keys[4], recent_blockhashes_sysvar_account),
|
||||
];
|
||||
let tx = Transaction::new(&[&nonce_authority, &from], message, blockhash);
|
||||
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
|
||||
|
||||
let nonce_state =
|
||||
nonce::state::Versions::new_current(nonce::State::Initialized(nonce::state::Data {
|
||||
@@ -2521,9 +2464,9 @@ mod tests {
|
||||
false,
|
||||
AccountShrinkThreshold::default(),
|
||||
);
|
||||
let txs = &[tx];
|
||||
let txs = vec![tx];
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
txs.iter(),
|
||||
&txs,
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
|
Reference in New Issue
Block a user