Add program and runtime support for Durable Transaction Nonces (#6845)

* Rework transaction processing result forwarding

Durable nonce prereq

* Add Durable Nonce program API

* Add runtime changes for Durable Nonce program

* Register Durable Nonce program

* Concise comments and bad math

* Fix c/p error

* Add rent sysvar to withdraw ix

* Remove rent exempt required balance from Meta struct

* Use the helper
This commit is contained in:
Trent Nelson
2019-12-07 12:54:10 -07:00
committed by GitHub
parent 6469606baf
commit 1ffd6b4b4d
18 changed files with 1832 additions and 143 deletions

View File

@ -1,6 +1,7 @@
use crate::accounts_db::{AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters};
use crate::accounts_index::AccountsIndex;
use crate::append_vec::StoredAccount;
use crate::bank::{HashAgeKind, TransactionProcessResult};
use crate::blockhash_queue::BlockhashQueue;
use crate::message_processor::has_duplicates;
use crate::rent_collector::RentCollector;
@ -224,11 +225,11 @@ impl Accounts {
ancestors: &HashMap<Slot, usize>,
txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
lock_results: Vec<Result<()>>,
lock_results: Vec<TransactionProcessResult>,
hash_queue: &BlockhashQueue,
error_counters: &mut ErrorCounters,
rent_collector: &RentCollector,
) -> Vec<Result<TransactionLoadResult>> {
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
//PERF: hold the lock to scan for the references, but not to clone the accounts
//TODO: two locks usually leads to deadlocks, should this be one structure?
let accounts_index = self.accounts_db.accounts_index.read().unwrap();
@ -236,13 +237,20 @@ impl Accounts {
OrderedIterator::new(txs, txs_iteration_order)
.zip(lock_results.into_iter())
.map(|etx| match etx {
(tx, Ok(())) => {
let fee_calculator = hash_queue
.get_fee_calculator(&tx.message().recent_blockhash)
.ok_or(TransactionError::BlockhashNotFound)?;
(tx, (Ok(()), hash_age_kind)) => {
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
hash_queue.last_hash()
} else {
tx.message().recent_blockhash
};
let fee = if let Some(fee_calculator) = hash_queue.get_fee_calculator(&fee_hash)
{
fee_calculator.calculate_fee(tx.message())
} else {
return (Err(TransactionError::BlockhashNotFound), hash_age_kind);
};
let fee = fee_calculator.calculate_fee(tx.message());
let (accounts, rents) = self.load_tx_accounts(
let load_res = self.load_tx_accounts(
&storage,
ancestors,
&accounts_index,
@ -250,17 +258,27 @@ impl Accounts {
fee,
error_counters,
rent_collector,
)?;
let loaders = Self::load_loaders(
);
let (accounts, rents) = match load_res {
Ok((a, r)) => (a, r),
Err(e) => return (Err(e), hash_age_kind),
};
let load_res = Self::load_loaders(
&storage,
ancestors,
&accounts_index,
tx,
error_counters,
)?;
Ok((accounts, loaders, rents))
);
let loaders = match load_res {
Ok(loaders) => loaders,
Err(e) => return (Err(e), hash_age_kind),
};
(Ok((accounts, loaders, rents)), hash_age_kind)
}
(_, Err(e)) => Err(e),
(_, (Err(e), hash_age_kind)) => (Err(e), hash_age_kind),
})
.collect()
}
@ -520,8 +538,8 @@ impl Accounts {
slot: Slot,
txs: &[Transaction],
txs_iteration_order: Option<&[usize]>,
res: &[Result<()>],
loaded: &mut [Result<TransactionLoadResult>],
res: &[TransactionProcessResult],
loaded: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
rent_collector: &RentCollector,
) {
let accounts_to_store =
@ -543,17 +561,18 @@ impl Accounts {
&self,
txs: &'a [Transaction],
txs_iteration_order: Option<&'a [usize]>,
res: &'a [Result<()>],
loaded: &'a mut [Result<TransactionLoadResult>],
res: &'a [TransactionProcessResult],
loaded: &'a mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
rent_collector: &RentCollector,
) -> Vec<(&'a Pubkey, &'a Account)> {
let mut accounts = Vec::with_capacity(loaded.len());
for (i, (raccs, tx)) in loaded
for (i, ((raccs, _hash_age_kind), tx)) in loaded
.iter_mut()
.zip(OrderedIterator::new(txs, txs_iteration_order))
.enumerate()
{
if res[i].is_err() || raccs.is_err() {
let (res, _hash_age_kind) = &res[i];
if res.is_err() || raccs.is_err() {
continue;
}
@ -599,6 +618,7 @@ mod tests {
use super::*;
use crate::accounts_db::tests::copy_append_vecs;
use crate::accounts_db::{get_temp_accounts_paths, AccountsDBSerialize};
use crate::bank::HashAgeKind;
use bincode::serialize_into;
use rand::{thread_rng, Rng};
use solana_sdk::account::Account;
@ -618,7 +638,7 @@ mod tests {
ka: &Vec<(Pubkey, Account)>,
fee_calculator: &FeeCalculator,
error_counters: &mut ErrorCounters,
) -> Vec<Result<TransactionLoadResult>> {
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
let mut hash_queue = BlockhashQueue::new(100);
hash_queue.register_hash(&tx.message().recent_blockhash, &fee_calculator);
let accounts = Accounts::new(Vec::new());
@ -632,7 +652,7 @@ mod tests {
&ancestors,
&[tx],
None,
vec![Ok(())],
vec![(Ok(()), Some(HashAgeKind::Extant))],
&hash_queue,
error_counters,
&rent_collector,
@ -644,7 +664,7 @@ mod tests {
tx: Transaction,
ka: &Vec<(Pubkey, Account)>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<TransactionLoadResult>> {
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
let fee_calculator = FeeCalculator::default();
load_accounts_with_fee(tx, ka, &fee_calculator, error_counters)
}
@ -667,7 +687,13 @@ mod tests {
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(TransactionError::AccountNotFound));
assert_eq!(
loaded_accounts[0],
(
Err(TransactionError::AccountNotFound),
Some(HashAgeKind::Extant)
)
);
}
#[test]
@ -690,7 +716,13 @@ mod tests {
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(TransactionError::AccountNotFound));
assert_eq!(
loaded_accounts[0],
(
Err(TransactionError::AccountNotFound),
Some(HashAgeKind::Extant)
),
);
}
#[test]
@ -723,7 +755,10 @@ mod tests {
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
Err(TransactionError::ProgramAccountNotFound)
(
Err(TransactionError::ProgramAccountNotFound),
Some(HashAgeKind::Extant)
)
);
}
@ -757,7 +792,10 @@ mod tests {
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0].clone(),
Err(TransactionError::InsufficientFundsForFee)
(
Err(TransactionError::InsufficientFundsForFee),
Some(HashAgeKind::Extant)
),
);
}
@ -787,7 +825,10 @@ mod tests {
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
Err(TransactionError::InvalidAccountForFee)
(
Err(TransactionError::InvalidAccountForFee),
Some(HashAgeKind::Extant)
),
);
}
@ -822,13 +863,16 @@ mod tests {
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
(
Ok((transaction_accounts, transaction_loaders, _transaction_rents)),
_hash_age_kind,
) => {
assert_eq!(transaction_accounts.len(), 2);
assert_eq!(transaction_accounts[0], accounts[0].1);
assert_eq!(transaction_loaders.len(), 1);
assert_eq!(transaction_loaders[0].len(), 0);
}
Err(e) => Err(e).unwrap(),
(Err(e), _hash_age_kind) => Err(e).unwrap(),
}
}
@ -892,7 +936,13 @@ mod tests {
assert_eq!(error_counters.call_chain_too_deep, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(TransactionError::CallChainTooDeep));
assert_eq!(
loaded_accounts[0],
(
Err(TransactionError::CallChainTooDeep),
Some(HashAgeKind::Extant)
)
);
}
#[test]
@ -925,7 +975,13 @@ mod tests {
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(TransactionError::AccountNotFound));
assert_eq!(
loaded_accounts[0],
(
Err(TransactionError::AccountNotFound),
Some(HashAgeKind::Extant)
)
);
}
#[test]
@ -957,7 +1013,13 @@ mod tests {
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(TransactionError::AccountNotFound));
assert_eq!(
loaded_accounts[0],
(
Err(TransactionError::AccountNotFound),
Some(HashAgeKind::Extant)
)
);
}
#[test]
@ -1003,7 +1065,10 @@ mod tests {
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
Ok((transaction_accounts, transaction_loaders, _transaction_rents)) => {
(
Ok((transaction_accounts, transaction_loaders, _transaction_rents)),
_hash_age_kind,
) => {
assert_eq!(transaction_accounts.len(), 1);
assert_eq!(transaction_accounts[0], accounts[0].1);
assert_eq!(transaction_loaders.len(), 2);
@ -1016,7 +1081,7 @@ mod tests {
}
}
}
Err(e) => Err(e).unwrap(),
(Err(e), _hash_age_kind) => Err(e).unwrap(),
}
}
@ -1046,7 +1111,10 @@ mod tests {
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
Err(TransactionError::AccountLoadedTwice)
(
Err(TransactionError::AccountLoadedTwice),
Some(HashAgeKind::Extant)
)
);
}
@ -1378,7 +1446,10 @@ mod tests {
let tx1 = Transaction::new(&[&keypair1], message, Hash::default());
let txs = vec![tx0, tx1];
let loaders = vec![Ok(()), Ok(())];
let loaders = vec![
(Ok(()), Some(HashAgeKind::Extant)),
(Ok(()), Some(HashAgeKind::Extant)),
];
let account0 = Account::new(1, 0, &Pubkey::default());
let account1 = Account::new(2, 0, &Pubkey::default());
@ -1387,20 +1458,26 @@ mod tests {
let transaction_accounts0 = vec![account0, account2.clone()];
let transaction_loaders0 = vec![];
let transaction_rent0 = 0;
let loaded0 = Ok((
transaction_accounts0,
transaction_loaders0,
transaction_rent0,
));
let loaded0 = (
Ok((
transaction_accounts0,
transaction_loaders0,
transaction_rent0,
)),
Some(HashAgeKind::Extant),
);
let transaction_accounts1 = vec![account1, account2.clone()];
let transaction_loaders1 = vec![];
let transaction_rent1 = 0;
let loaded1 = Ok((
transaction_accounts1,
transaction_loaders1,
transaction_rent1,
));
let loaded1 = (
Ok((
transaction_accounts1,
transaction_loaders1,
transaction_rent1,
)),
Some(HashAgeKind::Extant),
);
let mut loaded = vec![loaded0, loaded1];

View File

@ -7,6 +7,7 @@ use crate::{
accounts_db::{AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters},
blockhash_queue::BlockhashQueue,
message_processor::{MessageProcessor, ProcessInstruction},
nonce_utils,
rent_collector::RentCollector,
serde_utils::{
deserialize_atomicbool, deserialize_atomicu64, serialize_atomicbool, serialize_atomicu64,
@ -154,9 +155,16 @@ impl StatusCacheRc {
pub type EnteredEpochCallback = Box<dyn Fn(&mut Bank) -> () + Sync + Send>;
pub type TransactionProcessResult = (Result<()>, Option<HashAgeKind>);
pub struct TransactionResults {
pub fee_collection_results: Vec<Result<()>>,
pub processing_results: Vec<Result<()>>,
pub processing_results: Vec<TransactionProcessResult>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HashAgeKind {
Extant,
DurableNonce,
}
/// Manager for the state of all accounts and programs after processing its entries.
@ -791,16 +799,17 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
res: &[Result<()>],
res: &[TransactionProcessResult],
) {
let mut status_cache = self.src.status_cache.write().unwrap();
for (i, tx) in OrderedIterator::new(txs, iteration_order).enumerate() {
if Self::can_commit(&res[i]) && !tx.signatures.is_empty() {
let (res, _hash_age_kind) = &res[i];
if Self::can_commit(res) && !tx.signatures.is_empty() {
status_cache.insert(
&tx.message().recent_blockhash,
&tx.signatures[0],
self.slot(),
res[i].clone(),
res.clone(),
);
}
}
@ -868,9 +877,9 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
results: Vec<Result<()>>,
results: Vec<TransactionProcessResult>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<TransactionLoadResult>> {
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
self.rc.accounts.load_accounts(
&self.ancestors,
txs,
@ -907,19 +916,23 @@ impl Bank {
lock_results: Vec<Result<()>>,
max_age: usize,
error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> {
) -> Vec<TransactionProcessResult> {
let hash_queue = self.blockhash_queue.read().unwrap();
OrderedIterator::new(txs, iteration_order)
.zip(lock_results.into_iter())
.map(|(tx, lock_res)| {
if lock_res.is_ok()
&& !hash_queue.check_hash_age(&tx.message().recent_blockhash, max_age)
{
error_counters.reserve_blockhash += 1;
Err(TransactionError::BlockhashNotFound)
} else {
lock_res
.map(|(tx, lock_res)| match lock_res {
Ok(()) => {
let message = tx.message();
if hash_queue.check_hash_age(&message.recent_blockhash, max_age) {
(Ok(()), Some(HashAgeKind::Extant))
} else if self.check_tx_durable_nonce(&tx) {
(Ok(()), Some(HashAgeKind::DurableNonce))
} else {
error_counters.reserve_blockhash += 1;
(Err(TransactionError::BlockhashNotFound), None)
}
}
Err(e) => (Err(e), None),
})
.collect()
}
@ -927,9 +940,9 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
lock_results: Vec<Result<()>>,
lock_results: Vec<TransactionProcessResult>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> {
) -> Vec<TransactionProcessResult> {
let rcache = self.src.status_cache.read().unwrap();
OrderedIterator::new(txs, iteration_order)
.zip(lock_results.into_iter())
@ -937,20 +950,25 @@ impl Bank {
if tx.signatures.is_empty() {
return lock_res;
}
if lock_res.is_ok()
&& rcache
.get_signature_status(
&tx.signatures[0],
&tx.message().recent_blockhash,
&self.ancestors,
)
.is_some()
{
error_counters.duplicate_signature += 1;
Err(TransactionError::DuplicateSignature)
} else {
lock_res
let (lock_res, hash_age_kind) = &lock_res;
if lock_res.is_ok()
&& rcache
.get_signature_status(
&tx.signatures[0],
&tx.message().recent_blockhash,
&self.ancestors,
)
.is_some()
{
error_counters.duplicate_signature += 1;
return (
Err(TransactionError::DuplicateSignature),
hash_age_kind.clone(),
);
}
}
lock_res
})
.collect()
}
@ -962,6 +980,18 @@ impl Bank {
.check_hash_age(hash, max_age)
}
pub fn check_tx_durable_nonce(&self, tx: &Transaction) -> bool {
nonce_utils::transaction_uses_durable_nonce(&tx)
.and_then(|nonce_ix| nonce_utils::get_nonce_pubkey_from_instruction(&nonce_ix, &tx))
.and_then(|nonce_pubkey| self.get_account(&nonce_pubkey))
.map_or_else(
|| false,
|nonce_account| {
nonce_utils::verify_nonce(&nonce_account, &tx.message().recent_blockhash)
},
)
}
pub fn check_transactions(
&self,
txs: &[Transaction],
@ -969,7 +999,7 @@ impl Bank {
lock_results: &[Result<()>],
max_age: usize,
mut error_counters: &mut ErrorCounters,
) -> Vec<Result<()>> {
) -> Vec<TransactionProcessResult> {
let refs_results = self.check_refs(txs, iteration_order, lock_results, &mut error_counters);
let age_results = self.check_age(
txs,
@ -1032,8 +1062,8 @@ impl Bank {
batch: &TransactionBatch,
max_age: usize,
) -> (
Vec<Result<TransactionLoadResult>>,
Vec<Result<()>>,
Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>,
Vec<TransactionProcessResult>,
Vec<usize>,
u64,
u64,
@ -1071,15 +1101,18 @@ impl Bank {
let mut execution_time = Measure::start("execution_time");
let mut signature_count: u64 = 0;
let executed: Vec<Result<()>> = loaded_accounts
let executed: Vec<TransactionProcessResult> = loaded_accounts
.iter_mut()
.zip(OrderedIterator::new(txs, batch.iteration_order()))
.map(|(accs, tx)| match accs {
Err(e) => Err(e.clone()),
Ok((accounts, loaders, _rents)) => {
(Err(e), hash_age_kind) => (Err(e.clone()), hash_age_kind.clone()),
(Ok((accounts, loaders, _rents)), hash_age_kind) => {
signature_count += u64::from(tx.message().header.num_required_signatures);
self.message_processor
.process_message(tx.message(), loaders, accounts)
(
self.message_processor
.process_message(tx.message(), loaders, accounts),
hash_age_kind.clone(),
)
}
})
.collect();
@ -1094,7 +1127,7 @@ impl Bank {
);
let mut tx_count: u64 = 0;
let mut err_count = 0;
for (r, tx) in executed.iter().zip(txs.iter()) {
for ((r, _hash_age_kind), tx) in executed.iter().zip(txs.iter()) {
if r.is_ok() {
tx_count += 1;
} else {
@ -1127,17 +1160,22 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
executed: &[Result<()>],
executed: &[TransactionProcessResult],
) -> Vec<Result<()>> {
let hash_queue = self.blockhash_queue.read().unwrap();
let mut fees = 0;
let results = OrderedIterator::new(txs, iteration_order)
.zip(executed.iter())
.map(|(tx, res)| {
let fee_calculator = hash_queue
.get_fee_calculator(&tx.message().recent_blockhash)
.ok_or(TransactionError::BlockhashNotFound)?;
let fee = fee_calculator.calculate_fee(tx.message());
.map(|(tx, (res, hash_age_kind))| {
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
self.last_blockhash()
} else {
tx.message().recent_blockhash
};
let fee = hash_queue
.get_fee_calculator(&fee_hash)
.ok_or(TransactionError::BlockhashNotFound)?
.calculate_fee(tx.message());
let message = tx.message();
match *res {
@ -1166,8 +1204,8 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
loaded_accounts: &mut [Result<TransactionLoadResult>],
executed: &[Result<()>],
loaded_accounts: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
executed: &[TransactionProcessResult],
tx_count: u64,
signature_count: u64,
) -> TransactionResults {
@ -1182,7 +1220,10 @@ impl Bank {
inc_new_counter_info!("bank-process_transactions-txs", tx_count as usize);
inc_new_counter_info!("bank-process_transactions-sigs", signature_count as usize);
if executed.iter().any(|res| Self::can_commit(res)) {
if executed
.iter()
.any(|(res, _hash_age_kind)| Self::can_commit(res))
{
self.is_delta.store(true, Ordering::Relaxed);
}
@ -1256,10 +1297,15 @@ impl Bank {
self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed);
}
fn collect_rent(&self, res: &[Result<()>], loaded_accounts: &[Result<TransactionLoadResult>]) {
fn collect_rent(
&self,
res: &[TransactionProcessResult],
loaded_accounts: &[(Result<TransactionLoadResult>, Option<HashAgeKind>)],
) {
let mut collected_rent: u64 = 0;
for (i, raccs) in loaded_accounts.iter().enumerate() {
if res[i].is_err() || raccs.is_err() {
for (i, (raccs, _hash_age_kind)) in loaded_accounts.iter().enumerate() {
let (res, _hash_age_kind) = &res[i];
if res.is_err() || raccs.is_err() {
continue;
}
@ -1543,15 +1589,16 @@ impl Bank {
&self,
txs: &[Transaction],
iteration_order: Option<&[usize]>,
res: &[Result<()>],
loaded: &[Result<TransactionLoadResult>],
res: &[TransactionProcessResult],
loaded: &[(Result<TransactionLoadResult>, Option<HashAgeKind>)],
) {
for (i, (raccs, tx)) in loaded
for (i, ((raccs, _load_hash_age_kind), tx)) in loaded
.iter()
.zip(OrderedIterator::new(txs, iteration_order))
.enumerate()
{
if res[i].is_err() || raccs.is_err() {
let (res, _res_hash_age_kind) = &res[i];
if res.is_err() || raccs.is_err() {
continue;
}
@ -1706,11 +1753,13 @@ mod tests {
use solana_sdk::system_program::solana_system_program;
use solana_sdk::{
account::KeyedAccount,
account_utils::State,
clock::DEFAULT_TICKS_PER_SLOT,
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
genesis_config::create_genesis_config,
instruction::{Instruction, InstructionError},
message::{Message, MessageHeader},
nonce_instruction, nonce_state,
poh_config::PohConfig,
rent::Rent,
signature::{Keypair, KeypairUtil},
@ -2943,11 +2992,14 @@ mod tests {
system_transaction::transfer(&mint_keypair, &key.pubkey(), 5, genesis_config.hash());
let results = vec![
Ok(()),
Err(TransactionError::InstructionError(
1,
InstructionError::new_result_with_negative_lamports(),
)),
(Ok(()), Some(HashAgeKind::Extant)),
(
Err(TransactionError::InstructionError(
1,
InstructionError::new_result_with_negative_lamports(),
)),
Some(HashAgeKind::Extant),
),
];
let initial_balance = bank.get_balance(&leader);
@ -4233,4 +4285,267 @@ mod tests {
}
}
}
fn get_nonce(bank: &Bank, nonce_pubkey: &Pubkey) -> Option<Hash> {
bank.get_account(&nonce_pubkey)
.and_then(|acc| match acc.state() {
Ok(nonce_state::NonceState::Initialized(_meta, hash)) => Some(hash),
_ => None,
})
}
fn nonce_setup(
bank: &mut Arc<Bank>,
mint_keypair: &Keypair,
custodian_lamports: u64,
nonce_lamports: u64,
) -> Result<(Keypair, Keypair)> {
let custodian_keypair = Keypair::new();
let nonce_keypair = Keypair::new();
/* Setup accounts */
let mut setup_ixs = vec![system_instruction::transfer(
&mint_keypair.pubkey(),
&custodian_keypair.pubkey(),
custodian_lamports,
)];
setup_ixs.extend_from_slice(&nonce_instruction::create_nonce_account(
&custodian_keypair.pubkey(),
&nonce_keypair.pubkey(),
nonce_lamports,
));
let setup_tx = Transaction::new_signed_instructions(
&[mint_keypair, &custodian_keypair, &nonce_keypair],
setup_ixs,
bank.last_blockhash(),
);
bank.process_transaction(&setup_tx)?;
Ok((custodian_keypair, nonce_keypair))
}
fn setup_nonce_with_bank<F>(
supply_lamports: u64,
mut genesis_cfg_fn: F,
custodian_lamports: u64,
nonce_lamports: u64,
) -> Result<(Arc<Bank>, Keypair, Keypair, Keypair)>
where
F: FnMut(&mut GenesisConfig),
{
let (mut genesis_config, mint_keypair) = create_genesis_config(supply_lamports);
genesis_cfg_fn(&mut genesis_config);
let mut bank = Arc::new(Bank::new(&genesis_config));
let (custodian_keypair, nonce_keypair) =
nonce_setup(&mut bank, &mint_keypair, custodian_lamports, nonce_lamports)?;
Ok((bank, mint_keypair, custodian_keypair, nonce_keypair))
}
#[test]
fn test_check_tx_durable_nonce_ok() {
let (bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000).unwrap();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
let tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
assert!(bank.check_tx_durable_nonce(&tx));
}
#[test]
fn test_check_tx_durable_nonce_not_durable_nonce_fail() {
let (bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000).unwrap();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
let tx = Transaction::new_signed_with_payer(
vec![
system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
nonce_instruction::nonce(&nonce_pubkey),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
assert!(!bank.check_tx_durable_nonce(&tx));
}
#[test]
fn test_check_tx_durable_nonce_missing_ix_pubkey_fail() {
let (bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000).unwrap();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
let mut tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
tx.message.instructions[0].accounts.clear();
assert!(!bank.check_tx_durable_nonce(&tx));
}
#[test]
fn test_check_tx_durable_nonce_nonce_acc_does_not_exist_fail() {
let (bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000).unwrap();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let missing_keypair = Keypair::new();
let missing_pubkey = missing_keypair.pubkey();
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
let tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&missing_pubkey),
system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &missing_keypair],
nonce_hash,
);
assert!(!bank.check_tx_durable_nonce(&tx));
}
#[test]
fn test_check_tx_durable_nonce_bad_tx_hash_fail() {
let (bank, _mint_keypair, custodian_keypair, nonce_keypair) =
setup_nonce_with_bank(10_000_000, |_| {}, 5_000_000, 250_000).unwrap();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
let tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &nonce_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
Hash::default(),
);
assert!(!bank.check_tx_durable_nonce(&tx));
}
#[test]
fn test_durable_nonce_transaction() {
let (mut bank, _mint_keypair, custodian_keypair, nonce_keypair) = setup_nonce_with_bank(
10_000_000,
|gc| {
gc.rent.lamports_per_byte_year;
},
5_000_000,
250_000,
)
.unwrap();
let alice_keypair = Keypair::new();
let alice_pubkey = alice_keypair.pubkey();
let custodian_pubkey = custodian_keypair.pubkey();
let nonce_pubkey = nonce_keypair.pubkey();
assert_eq!(bank.get_balance(&custodian_pubkey), 4_750_000);
assert_eq!(bank.get_balance(&nonce_pubkey), 250_000);
/* Grab the hash stored in the nonce account */
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
/* Kick nonce hash off the blockhash_queue */
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
goto_end_of_slot(Arc::get_mut(&mut bank).unwrap());
bank = Arc::new(new_from_parent(&bank));
}
/* Expect a non-Durable Nonce transfer to fail */
assert_eq!(
bank.process_transaction(&system_transaction::transfer(
&custodian_keypair,
&alice_pubkey,
100_000,
nonce_hash
),),
Err(TransactionError::BlockhashNotFound),
);
/* Check fee not charged */
assert_eq!(bank.get_balance(&custodian_pubkey), 4_750_000);
/* Durable Nonce transfer */
let durable_tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &alice_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
assert_eq!(bank.process_transaction(&durable_tx), Ok(()));
/* Check balances */
assert_eq!(bank.get_balance(&custodian_pubkey), 4_640_000);
assert_eq!(bank.get_balance(&nonce_pubkey), 250_000);
assert_eq!(bank.get_balance(&alice_pubkey), 100_000);
/* Confirm stored nonce has advanced */
let new_nonce = get_nonce(&bank, &nonce_pubkey).unwrap();
assert_ne!(nonce_hash, new_nonce);
/* Durable Nonce re-use fails */
let durable_tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &alice_pubkey, 100_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
assert_eq!(
bank.process_transaction(&durable_tx),
Err(TransactionError::BlockhashNotFound)
);
/* Check fee not charged */
assert_eq!(bank.get_balance(&custodian_pubkey), 4_640_000);
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
/* Kick nonce hash off the blockhash_queue */
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
goto_end_of_slot(Arc::get_mut(&mut bank).unwrap());
bank = Arc::new(new_from_parent(&bank));
}
let durable_tx = Transaction::new_signed_with_payer(
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&custodian_pubkey, &alice_pubkey, 100_000_000),
],
Some(&custodian_pubkey),
&[&custodian_keypair, &nonce_keypair],
nonce_hash,
);
assert_eq!(
bank.process_transaction(&durable_tx),
Err(TransactionError::InstructionError(
1,
system_instruction::SystemError::ResultWithNegativeLamports.into()
))
);
/* Check fee charged */
assert_eq!(bank.get_balance(&custodian_pubkey), 4_630_000);
}
}

View File

@ -2,6 +2,7 @@ use solana_sdk::{
account::Account,
fee_calculator::FeeCalculator,
genesis_config::GenesisConfig,
nonce_program::solana_nonce_program,
pubkey::Pubkey,
rent::Rent,
signature::{Keypair, KeypairUtil},
@ -74,6 +75,7 @@ pub fn create_genesis_config_with_leader(
// Bare minimum program set
let native_instruction_processors = vec![
solana_system_program(),
solana_nonce_program(),
solana_bpf_loader_program!(),
solana_vote_program!(),
solana_stake_program!(),

View File

@ -10,6 +10,7 @@ pub mod genesis_utils;
pub mod loader_utils;
pub mod message_processor;
mod native_loader;
mod nonce_utils;
pub mod rent_collector;
mod serde_utils;
pub mod stakes;

View File

@ -7,6 +7,8 @@ use solana_sdk::instruction::{CompiledInstruction, InstructionError};
use solana_sdk::instruction_processor_utils;
use solana_sdk::loader_instruction::LoaderInstruction;
use solana_sdk::message::Message;
use solana_sdk::nonce_instruction;
use solana_sdk::nonce_program;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_program;
use solana_sdk::transaction::TransactionError;
@ -198,10 +200,13 @@ pub struct MessageProcessor {
impl Default for MessageProcessor {
fn default() -> Self {
let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![(
system_program::id(),
system_instruction_processor::process_instruction,
)];
let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![
(
system_program::id(),
system_instruction_processor::process_instruction,
),
(nonce_program::id(), nonce_instruction::process_instruction),
];
Self {
instruction_processors,

195
runtime/src/nonce_utils.rs Normal file
View File

@ -0,0 +1,195 @@
use solana_sdk::{
account::Account, account_utils::State, hash::Hash, instruction::CompiledInstruction,
instruction_processor_utils::limited_deserialize, nonce_instruction::NonceInstruction,
nonce_program, nonce_state::NonceState, pubkey::Pubkey, transaction::Transaction,
};
pub fn transaction_uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> {
let message = tx.message();
message
.instructions
.get(0)
.filter(|maybe_ix| {
let prog_id_idx = maybe_ix.program_id_index as usize;
match message.account_keys.get(prog_id_idx) {
Some(program_id) => nonce_program::check_id(&program_id),
_ => false,
}
})
.filter(|maybe_ix| match limited_deserialize(&maybe_ix.data) {
Ok(NonceInstruction::Nonce) => true,
_ => false,
})
}
pub fn get_nonce_pubkey_from_instruction<'a>(
ix: &CompiledInstruction,
tx: &'a Transaction,
) -> Option<&'a Pubkey> {
ix.accounts.get(0).and_then(|idx| {
let idx = *idx as usize;
tx.message().account_keys.get(idx)
})
}
pub fn verify_nonce(acc: &Account, hash: &Hash) -> bool {
match acc.state() {
Ok(NonceState::Initialized(_meta, ref nonce)) => hash == nonce,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{
hash::Hash,
nonce_instruction,
nonce_state::{with_test_keyed_account, NonceAccount},
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_instruction,
sysvar::{recent_blockhashes::create_test_recent_blockhashes, rent::Rent},
};
use std::collections::HashSet;
fn nonced_transfer_tx() -> (Pubkey, Pubkey, Transaction) {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let tx = Transaction::new_signed_instructions(
&[&from_keypair, &nonce_keypair],
vec![
nonce_instruction::nonce(&nonce_pubkey),
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
],
Hash::default(),
);
(from_pubkey, nonce_pubkey, tx)
}
#[test]
fn tx_uses_nonce_ok() {
let (_, _, tx) = nonced_transfer_tx();
assert!(transaction_uses_durable_nonce(&tx).is_some());
}
#[test]
fn tx_uses_nonce_empty_ix_fail() {
let tx =
Transaction::new_signed_instructions(&[&Keypair::new(); 0], vec![], Hash::default());
assert!(transaction_uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_uses_nonce_bad_prog_id_idx_fail() {
let (_, _, mut tx) = nonced_transfer_tx();
tx.message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
assert!(transaction_uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let tx = Transaction::new_signed_instructions(
&[&from_keypair, &nonce_keypair],
vec![
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
nonce_instruction::nonce(&nonce_pubkey),
],
Hash::default(),
);
assert!(transaction_uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let tx = Transaction::new_signed_instructions(
&[&from_keypair, &nonce_keypair],
vec![
nonce_instruction::withdraw(&nonce_pubkey, &from_pubkey, 42),
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
],
Hash::default(),
);
assert!(transaction_uses_durable_nonce(&tx).is_none());
}
#[test]
fn get_nonce_pub_from_ix_ok() {
let (_, nonce_pubkey, tx) = nonced_transfer_tx();
let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap();
assert_eq!(
get_nonce_pubkey_from_instruction(&nonce_ix, &tx),
Some(&nonce_pubkey),
);
}
#[test]
fn get_nonce_pub_from_ix_no_accounts_fail() {
let (_, _, tx) = nonced_transfer_tx();
let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap();
let mut nonce_ix = nonce_ix.clone();
nonce_ix.accounts.clear();
assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,);
}
#[test]
fn get_nonce_pub_from_ix_bad_acc_idx_fail() {
let (_, _, tx) = nonced_transfer_tx();
let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap();
let mut nonce_ix = nonce_ix.clone();
nonce_ix.accounts[0] = 255u8;
assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,);
}
#[test]
fn verify_nonce_ok() {
with_test_keyed_account(42, true, |nonce_account| {
let mut signers = HashSet::new();
signers.insert(nonce_account.signer_key().unwrap().clone());
let state: NonceState = nonce_account.state().unwrap();
// New is in Uninitialzed state
assert_eq!(state, NonceState::Uninitialized);
let recent_blockhashes = create_test_recent_blockhashes(0);
nonce_account
.nonce(&recent_blockhashes, &Rent::default(), &signers)
.unwrap();
assert!(verify_nonce(&nonce_account.account, &recent_blockhashes[0]));
});
}
#[test]
fn verify_nonce_bad_acc_state_fail() {
with_test_keyed_account(42, true, |nonce_account| {
assert!(!verify_nonce(&nonce_account.account, &Hash::default()));
});
}
#[test]
fn verify_nonce_bad_query_hash_fail() {
with_test_keyed_account(42, true, |nonce_account| {
let mut signers = HashSet::new();
signers.insert(nonce_account.signer_key().unwrap().clone());
let state: NonceState = nonce_account.state().unwrap();
// New is in Uninitialzed state
assert_eq!(state, NonceState::Uninitialized);
let recent_blockhashes = create_test_recent_blockhashes(0);
nonce_account
.nonce(&recent_blockhashes, &Rent::default(), &signers)
.unwrap();
assert!(!verify_nonce(
&nonce_account.account,
&recent_blockhashes[1]
));
});
}
}