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:
parent
6469606baf
commit
1ffd6b4b4d
|
@ -3877,6 +3877,7 @@ dependencies = [
|
||||||
"solana-crate-features 0.22.0",
|
"solana-crate-features 0.22.0",
|
||||||
"solana-logger 0.22.0",
|
"solana-logger 0.22.0",
|
||||||
"solana-sdk-macro 0.22.0",
|
"solana-sdk-macro 0.22.0",
|
||||||
|
"thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,11 @@ use solana_ledger::{
|
||||||
use solana_measure::measure::Measure;
|
use solana_measure::measure::Measure;
|
||||||
use solana_metrics::{inc_new_counter_debug, inc_new_counter_info, inc_new_counter_warn};
|
use solana_metrics::{inc_new_counter_debug, inc_new_counter_info, inc_new_counter_warn};
|
||||||
use solana_perf::{cuda_runtime::PinnedVec, perf_libs};
|
use solana_perf::{cuda_runtime::PinnedVec, perf_libs};
|
||||||
use solana_runtime::{accounts_db::ErrorCounters, bank::Bank, transaction_batch::TransactionBatch};
|
use solana_runtime::{
|
||||||
|
accounts_db::ErrorCounters,
|
||||||
|
bank::{Bank, TransactionProcessResult},
|
||||||
|
transaction_batch::TransactionBatch,
|
||||||
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{
|
clock::{
|
||||||
Slot, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE,
|
Slot, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE,
|
||||||
|
@ -443,7 +447,7 @@ impl BankingStage {
|
||||||
fn record_transactions(
|
fn record_transactions(
|
||||||
bank_slot: Slot,
|
bank_slot: Slot,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
results: &[transaction::Result<()>],
|
results: &[TransactionProcessResult],
|
||||||
poh: &Arc<Mutex<PohRecorder>>,
|
poh: &Arc<Mutex<PohRecorder>>,
|
||||||
) -> (Result<usize>, Vec<usize>) {
|
) -> (Result<usize>, Vec<usize>) {
|
||||||
let mut processed_generation = Measure::start("record::process_generation");
|
let mut processed_generation = Measure::start("record::process_generation");
|
||||||
|
@ -451,7 +455,7 @@ impl BankingStage {
|
||||||
.iter()
|
.iter()
|
||||||
.zip(txs.iter())
|
.zip(txs.iter())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, (r, x))| {
|
.filter_map(|(i, ((r, _h), x))| {
|
||||||
if Bank::can_commit(r) {
|
if Bank::can_commit(r) {
|
||||||
Some((x.clone(), i))
|
Some((x.clone(), i))
|
||||||
} else {
|
} else {
|
||||||
|
@ -678,13 +682,13 @@ impl BankingStage {
|
||||||
// This function returns a vector containing index of all valid transactions. A valid
|
// This function returns a vector containing index of all valid transactions. A valid
|
||||||
// transaction has result Ok() as the value
|
// transaction has result Ok() as the value
|
||||||
fn filter_valid_transaction_indexes(
|
fn filter_valid_transaction_indexes(
|
||||||
valid_txs: &[transaction::Result<()>],
|
valid_txs: &[TransactionProcessResult],
|
||||||
transaction_indexes: &[usize],
|
transaction_indexes: &[usize],
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
let valid_transactions = valid_txs
|
let valid_transactions = valid_txs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, x)| if x.is_ok() { Some(index) } else { None })
|
.filter_map(|(index, (x, _h))| if x.is_ok() { Some(index) } else { None })
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
valid_transactions
|
valid_transactions
|
||||||
|
@ -1022,6 +1026,7 @@ mod tests {
|
||||||
entry::{next_entry, Entry, EntrySlice},
|
entry::{next_entry, Entry, EntrySlice},
|
||||||
get_tmp_ledger_path,
|
get_tmp_ledger_path,
|
||||||
};
|
};
|
||||||
|
use solana_runtime::bank::HashAgeKind;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
instruction::InstructionError,
|
instruction::InstructionError,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
|
@ -1369,7 +1374,10 @@ mod tests {
|
||||||
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()),
|
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut results = vec![Ok(()), Ok(())];
|
let mut results = vec![
|
||||||
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
|
];
|
||||||
let _ = BankingStage::record_transactions(
|
let _ = BankingStage::record_transactions(
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
&transactions,
|
&transactions,
|
||||||
|
@ -1380,10 +1388,13 @@ mod tests {
|
||||||
assert_eq!(entry.transactions.len(), transactions.len());
|
assert_eq!(entry.transactions.len(), transactions.len());
|
||||||
|
|
||||||
// InstructionErrors should still be recorded
|
// InstructionErrors should still be recorded
|
||||||
results[0] = Err(TransactionError::InstructionError(
|
results[0] = (
|
||||||
1,
|
Err(TransactionError::InstructionError(
|
||||||
InstructionError::new_result_with_negative_lamports(),
|
1,
|
||||||
));
|
InstructionError::new_result_with_negative_lamports(),
|
||||||
|
)),
|
||||||
|
Some(HashAgeKind::Extant),
|
||||||
|
);
|
||||||
let (res, retryable) = BankingStage::record_transactions(
|
let (res, retryable) = BankingStage::record_transactions(
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
&transactions,
|
&transactions,
|
||||||
|
@ -1396,7 +1407,7 @@ mod tests {
|
||||||
assert_eq!(entry.transactions.len(), transactions.len());
|
assert_eq!(entry.transactions.len(), transactions.len());
|
||||||
|
|
||||||
// Other TransactionErrors should not be recorded
|
// Other TransactionErrors should not be recorded
|
||||||
results[0] = Err(TransactionError::AccountNotFound);
|
results[0] = (Err(TransactionError::AccountNotFound), None);
|
||||||
let (res, retryable) = BankingStage::record_transactions(
|
let (res, retryable) = BankingStage::record_transactions(
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
&transactions,
|
&transactions,
|
||||||
|
@ -1559,12 +1570,12 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BankingStage::filter_valid_transaction_indexes(
|
BankingStage::filter_valid_transaction_indexes(
|
||||||
&vec![
|
&vec![
|
||||||
Err(TransactionError::BlockhashNotFound),
|
(Err(TransactionError::BlockhashNotFound), None),
|
||||||
Err(TransactionError::BlockhashNotFound),
|
(Err(TransactionError::BlockhashNotFound), None),
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Err(TransactionError::BlockhashNotFound),
|
(Err(TransactionError::BlockhashNotFound), None),
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Ok(())
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
],
|
],
|
||||||
&vec![2, 4, 5, 9, 11, 13]
|
&vec![2, 4, 5, 9, 11, 13]
|
||||||
),
|
),
|
||||||
|
@ -1574,12 +1585,12 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BankingStage::filter_valid_transaction_indexes(
|
BankingStage::filter_valid_transaction_indexes(
|
||||||
&vec![
|
&vec![
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Err(TransactionError::BlockhashNotFound),
|
(Err(TransactionError::BlockhashNotFound), None),
|
||||||
Err(TransactionError::BlockhashNotFound),
|
(Err(TransactionError::BlockhashNotFound), None),
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Ok(())
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
],
|
],
|
||||||
&vec![1, 6, 7, 9, 31, 43]
|
&vec![1, 6, 7, 9, 31, 43]
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::result::{Error, Result};
|
||||||
use crossbeam_channel::{Receiver, RecvTimeoutError};
|
use crossbeam_channel::{Receiver, RecvTimeoutError};
|
||||||
use solana_client::rpc_request::RpcTransactionStatus;
|
use solana_client::rpc_request::RpcTransactionStatus;
|
||||||
use solana_ledger::{blocktree::Blocktree, blocktree_processor::TransactionStatusBatch};
|
use solana_ledger::{blocktree::Blocktree, blocktree_processor::TransactionStatusBatch};
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::{Bank, HashAgeKind};
|
||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
@ -56,10 +56,15 @@ impl TransactionStatusService {
|
||||||
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
|
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
|
||||||
|
|
||||||
let slot = bank.slot();
|
let slot = bank.slot();
|
||||||
for (transaction, status) in transactions.iter().zip(statuses) {
|
for (transaction, (status, hash_age_kind)) in transactions.iter().zip(statuses) {
|
||||||
if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
|
if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
|
||||||
|
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
|
||||||
|
bank.last_blockhash()
|
||||||
|
} else {
|
||||||
|
transaction.message().recent_blockhash
|
||||||
|
};
|
||||||
let fee_calculator = bank
|
let fee_calculator = bank
|
||||||
.get_fee_calculator(&transaction.message().recent_blockhash)
|
.get_fee_calculator(&fee_hash)
|
||||||
.expect("FeeCalculator must exist");
|
.expect("FeeCalculator must exist");
|
||||||
let fee = fee_calculator.calculate_fee(transaction.message());
|
let fee = fee_calculator.calculate_fee(transaction.message());
|
||||||
blocktree
|
blocktree
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::Epoch, genesis_config::OperatingMode, inflation::Inflation,
|
clock::Epoch, genesis_config::OperatingMode, inflation::Inflation,
|
||||||
move_loader::solana_move_loader_program, pubkey::Pubkey, system_program::solana_system_program,
|
move_loader::solana_move_loader_program, nonce_program::solana_nonce_program, pubkey::Pubkey,
|
||||||
|
system_program::solana_system_program,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -58,6 +59,7 @@ pub fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option<Vec<(
|
||||||
solana_system_program(),
|
solana_system_program(),
|
||||||
solana_bpf_loader_program!(),
|
solana_bpf_loader_program!(),
|
||||||
solana_config_program!(),
|
solana_config_program!(),
|
||||||
|
solana_nonce_program(),
|
||||||
solana_stake_program!(),
|
solana_stake_program!(),
|
||||||
solana_storage_program!(),
|
solana_storage_program!(),
|
||||||
solana_vest_program!(),
|
solana_vest_program!(),
|
||||||
|
@ -73,8 +75,9 @@ pub fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option<Vec<(
|
||||||
}
|
}
|
||||||
OperatingMode::SoftLaunch => {
|
OperatingMode::SoftLaunch => {
|
||||||
if epoch == 0 {
|
if epoch == 0 {
|
||||||
// Voting, Staking and System Program only at epoch 0
|
// Nonce, Voting, Staking and System Program only at epoch 0
|
||||||
Some(vec![
|
Some(vec![
|
||||||
|
solana_nonce_program(),
|
||||||
solana_stake_program!(),
|
solana_stake_program!(),
|
||||||
solana_system_program(),
|
solana_system_program(),
|
||||||
solana_vote_program!(),
|
solana_vote_program!(),
|
||||||
|
@ -124,6 +127,7 @@ pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpoch
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use solana_sdk::nonce_program::solana_nonce_program;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -146,7 +150,7 @@ mod tests {
|
||||||
fn test_development_programs() {
|
fn test_development_programs() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_programs(OperatingMode::Development, 0).unwrap().len(),
|
get_programs(OperatingMode::Development, 0).unwrap().len(),
|
||||||
10
|
11
|
||||||
);
|
);
|
||||||
assert_eq!(get_programs(OperatingMode::Development, 1), None);
|
assert_eq!(get_programs(OperatingMode::Development, 1), None);
|
||||||
}
|
}
|
||||||
|
@ -169,6 +173,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_programs(OperatingMode::SoftLaunch, 0),
|
get_programs(OperatingMode::SoftLaunch, 0),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
|
solana_nonce_program(),
|
||||||
solana_stake_program!(),
|
solana_stake_program!(),
|
||||||
solana_system_program(),
|
solana_system_program(),
|
||||||
solana_vote_program!(),
|
solana_vote_program!(),
|
||||||
|
|
|
@ -14,7 +14,7 @@ use rayon::{prelude::*, ThreadPool};
|
||||||
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
|
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
|
||||||
use solana_rayon_threadlimit::get_thread_count;
|
use solana_rayon_threadlimit::get_thread_count;
|
||||||
use solana_runtime::{
|
use solana_runtime::{
|
||||||
bank::{Bank, TransactionResults},
|
bank::{Bank, TransactionProcessResult, TransactionResults},
|
||||||
transaction_batch::TransactionBatch,
|
transaction_batch::TransactionBatch,
|
||||||
};
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
@ -550,14 +550,14 @@ fn process_pending_slots(
|
||||||
pub struct TransactionStatusBatch {
|
pub struct TransactionStatusBatch {
|
||||||
pub bank: Arc<Bank>,
|
pub bank: Arc<Bank>,
|
||||||
pub transactions: Vec<Transaction>,
|
pub transactions: Vec<Transaction>,
|
||||||
pub statuses: Vec<Result<()>>,
|
pub statuses: Vec<TransactionProcessResult>,
|
||||||
}
|
}
|
||||||
pub type TransactionStatusSender = Sender<TransactionStatusBatch>;
|
pub type TransactionStatusSender = Sender<TransactionStatusBatch>;
|
||||||
|
|
||||||
pub fn send_transaction_status_batch(
|
pub fn send_transaction_status_batch(
|
||||||
bank: Arc<Bank>,
|
bank: Arc<Bank>,
|
||||||
transactions: &[Transaction],
|
transactions: &[Transaction],
|
||||||
statuses: Vec<Result<()>>,
|
statuses: Vec<TransactionProcessResult>,
|
||||||
transaction_status_sender: TransactionStatusSender,
|
transaction_status_sender: TransactionStatusSender,
|
||||||
) {
|
) {
|
||||||
let slot = bank.slot();
|
let slot = bank.slot();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::accounts_db::{AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters};
|
use crate::accounts_db::{AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters};
|
||||||
use crate::accounts_index::AccountsIndex;
|
use crate::accounts_index::AccountsIndex;
|
||||||
use crate::append_vec::StoredAccount;
|
use crate::append_vec::StoredAccount;
|
||||||
|
use crate::bank::{HashAgeKind, TransactionProcessResult};
|
||||||
use crate::blockhash_queue::BlockhashQueue;
|
use crate::blockhash_queue::BlockhashQueue;
|
||||||
use crate::message_processor::has_duplicates;
|
use crate::message_processor::has_duplicates;
|
||||||
use crate::rent_collector::RentCollector;
|
use crate::rent_collector::RentCollector;
|
||||||
|
@ -224,11 +225,11 @@ impl Accounts {
|
||||||
ancestors: &HashMap<Slot, usize>,
|
ancestors: &HashMap<Slot, usize>,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
txs_iteration_order: Option<&[usize]>,
|
txs_iteration_order: Option<&[usize]>,
|
||||||
lock_results: Vec<Result<()>>,
|
lock_results: Vec<TransactionProcessResult>,
|
||||||
hash_queue: &BlockhashQueue,
|
hash_queue: &BlockhashQueue,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
rent_collector: &RentCollector,
|
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
|
//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?
|
//TODO: two locks usually leads to deadlocks, should this be one structure?
|
||||||
let accounts_index = self.accounts_db.accounts_index.read().unwrap();
|
let accounts_index = self.accounts_db.accounts_index.read().unwrap();
|
||||||
|
@ -236,13 +237,20 @@ impl Accounts {
|
||||||
OrderedIterator::new(txs, txs_iteration_order)
|
OrderedIterator::new(txs, txs_iteration_order)
|
||||||
.zip(lock_results.into_iter())
|
.zip(lock_results.into_iter())
|
||||||
.map(|etx| match etx {
|
.map(|etx| match etx {
|
||||||
(tx, Ok(())) => {
|
(tx, (Ok(()), hash_age_kind)) => {
|
||||||
let fee_calculator = hash_queue
|
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
|
||||||
.get_fee_calculator(&tx.message().recent_blockhash)
|
hash_queue.last_hash()
|
||||||
.ok_or(TransactionError::BlockhashNotFound)?;
|
} 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 load_res = self.load_tx_accounts(
|
||||||
let (accounts, rents) = self.load_tx_accounts(
|
|
||||||
&storage,
|
&storage,
|
||||||
ancestors,
|
ancestors,
|
||||||
&accounts_index,
|
&accounts_index,
|
||||||
|
@ -250,17 +258,27 @@ impl Accounts {
|
||||||
fee,
|
fee,
|
||||||
error_counters,
|
error_counters,
|
||||||
rent_collector,
|
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,
|
&storage,
|
||||||
ancestors,
|
ancestors,
|
||||||
&accounts_index,
|
&accounts_index,
|
||||||
tx,
|
tx,
|
||||||
error_counters,
|
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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -520,8 +538,8 @@ impl Accounts {
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
txs_iteration_order: Option<&[usize]>,
|
txs_iteration_order: Option<&[usize]>,
|
||||||
res: &[Result<()>],
|
res: &[TransactionProcessResult],
|
||||||
loaded: &mut [Result<TransactionLoadResult>],
|
loaded: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
|
||||||
rent_collector: &RentCollector,
|
rent_collector: &RentCollector,
|
||||||
) {
|
) {
|
||||||
let accounts_to_store =
|
let accounts_to_store =
|
||||||
|
@ -543,17 +561,18 @@ impl Accounts {
|
||||||
&self,
|
&self,
|
||||||
txs: &'a [Transaction],
|
txs: &'a [Transaction],
|
||||||
txs_iteration_order: Option<&'a [usize]>,
|
txs_iteration_order: Option<&'a [usize]>,
|
||||||
res: &'a [Result<()>],
|
res: &'a [TransactionProcessResult],
|
||||||
loaded: &'a mut [Result<TransactionLoadResult>],
|
loaded: &'a mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
|
||||||
rent_collector: &RentCollector,
|
rent_collector: &RentCollector,
|
||||||
) -> Vec<(&'a Pubkey, &'a Account)> {
|
) -> Vec<(&'a Pubkey, &'a Account)> {
|
||||||
let mut accounts = Vec::with_capacity(loaded.len());
|
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()
|
.iter_mut()
|
||||||
.zip(OrderedIterator::new(txs, txs_iteration_order))
|
.zip(OrderedIterator::new(txs, txs_iteration_order))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
if res[i].is_err() || raccs.is_err() {
|
let (res, _hash_age_kind) = &res[i];
|
||||||
|
if res.is_err() || raccs.is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +618,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::accounts_db::tests::copy_append_vecs;
|
use crate::accounts_db::tests::copy_append_vecs;
|
||||||
use crate::accounts_db::{get_temp_accounts_paths, AccountsDBSerialize};
|
use crate::accounts_db::{get_temp_accounts_paths, AccountsDBSerialize};
|
||||||
|
use crate::bank::HashAgeKind;
|
||||||
use bincode::serialize_into;
|
use bincode::serialize_into;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use solana_sdk::account::Account;
|
use solana_sdk::account::Account;
|
||||||
|
@ -618,7 +638,7 @@ mod tests {
|
||||||
ka: &Vec<(Pubkey, Account)>,
|
ka: &Vec<(Pubkey, Account)>,
|
||||||
fee_calculator: &FeeCalculator,
|
fee_calculator: &FeeCalculator,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<TransactionLoadResult>> {
|
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
|
||||||
let mut hash_queue = BlockhashQueue::new(100);
|
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 accounts = Accounts::new(Vec::new());
|
let accounts = Accounts::new(Vec::new());
|
||||||
|
@ -632,7 +652,7 @@ mod tests {
|
||||||
&ancestors,
|
&ancestors,
|
||||||
&[tx],
|
&[tx],
|
||||||
None,
|
None,
|
||||||
vec![Ok(())],
|
vec![(Ok(()), Some(HashAgeKind::Extant))],
|
||||||
&hash_queue,
|
&hash_queue,
|
||||||
error_counters,
|
error_counters,
|
||||||
&rent_collector,
|
&rent_collector,
|
||||||
|
@ -644,7 +664,7 @@ mod tests {
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
ka: &Vec<(Pubkey, Account)>,
|
ka: &Vec<(Pubkey, Account)>,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<TransactionLoadResult>> {
|
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
|
||||||
let fee_calculator = FeeCalculator::default();
|
let fee_calculator = FeeCalculator::default();
|
||||||
load_accounts_with_fee(tx, ka, &fee_calculator, error_counters)
|
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!(error_counters.account_not_found, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 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]
|
#[test]
|
||||||
|
@ -690,7 +716,13 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(error_counters.account_not_found, 1);
|
assert_eq!(error_counters.account_not_found, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 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]
|
#[test]
|
||||||
|
@ -723,7 +755,10 @@ mod tests {
|
||||||
assert_eq!(loaded_accounts.len(), 1);
|
assert_eq!(loaded_accounts.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_accounts[0],
|
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.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_accounts[0].clone(),
|
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.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_accounts[0],
|
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!(error_counters.account_not_found, 0);
|
||||||
assert_eq!(loaded_accounts.len(), 1);
|
assert_eq!(loaded_accounts.len(), 1);
|
||||||
match &loaded_accounts[0] {
|
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.len(), 2);
|
||||||
assert_eq!(transaction_accounts[0], accounts[0].1);
|
assert_eq!(transaction_accounts[0], accounts[0].1);
|
||||||
assert_eq!(transaction_loaders.len(), 1);
|
assert_eq!(transaction_loaders.len(), 1);
|
||||||
assert_eq!(transaction_loaders[0].len(), 0);
|
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!(error_counters.call_chain_too_deep, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 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]
|
#[test]
|
||||||
|
@ -925,7 +975,13 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(error_counters.account_not_found, 1);
|
assert_eq!(error_counters.account_not_found, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 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]
|
#[test]
|
||||||
|
@ -957,7 +1013,13 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(error_counters.account_not_found, 1);
|
assert_eq!(error_counters.account_not_found, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 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]
|
#[test]
|
||||||
|
@ -1003,7 +1065,10 @@ mod tests {
|
||||||
assert_eq!(error_counters.account_not_found, 0);
|
assert_eq!(error_counters.account_not_found, 0);
|
||||||
assert_eq!(loaded_accounts.len(), 1);
|
assert_eq!(loaded_accounts.len(), 1);
|
||||||
match &loaded_accounts[0] {
|
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.len(), 1);
|
||||||
assert_eq!(transaction_accounts[0], accounts[0].1);
|
assert_eq!(transaction_accounts[0], accounts[0].1);
|
||||||
assert_eq!(transaction_loaders.len(), 2);
|
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.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_accounts[0],
|
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 tx1 = Transaction::new(&[&keypair1], message, Hash::default());
|
||||||
let txs = vec![tx0, tx1];
|
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 account0 = Account::new(1, 0, &Pubkey::default());
|
||||||
let account1 = Account::new(2, 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_accounts0 = vec![account0, account2.clone()];
|
||||||
let transaction_loaders0 = vec![];
|
let transaction_loaders0 = vec![];
|
||||||
let transaction_rent0 = 0;
|
let transaction_rent0 = 0;
|
||||||
let loaded0 = Ok((
|
let loaded0 = (
|
||||||
transaction_accounts0,
|
Ok((
|
||||||
transaction_loaders0,
|
transaction_accounts0,
|
||||||
transaction_rent0,
|
transaction_loaders0,
|
||||||
));
|
transaction_rent0,
|
||||||
|
)),
|
||||||
|
Some(HashAgeKind::Extant),
|
||||||
|
);
|
||||||
|
|
||||||
let transaction_accounts1 = vec![account1, account2.clone()];
|
let transaction_accounts1 = vec![account1, account2.clone()];
|
||||||
let transaction_loaders1 = vec![];
|
let transaction_loaders1 = vec![];
|
||||||
let transaction_rent1 = 0;
|
let transaction_rent1 = 0;
|
||||||
let loaded1 = Ok((
|
let loaded1 = (
|
||||||
transaction_accounts1,
|
Ok((
|
||||||
transaction_loaders1,
|
transaction_accounts1,
|
||||||
transaction_rent1,
|
transaction_loaders1,
|
||||||
));
|
transaction_rent1,
|
||||||
|
)),
|
||||||
|
Some(HashAgeKind::Extant),
|
||||||
|
);
|
||||||
|
|
||||||
let mut loaded = vec![loaded0, loaded1];
|
let mut loaded = vec![loaded0, loaded1];
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
accounts_db::{AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters},
|
accounts_db::{AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters},
|
||||||
blockhash_queue::BlockhashQueue,
|
blockhash_queue::BlockhashQueue,
|
||||||
message_processor::{MessageProcessor, ProcessInstruction},
|
message_processor::{MessageProcessor, ProcessInstruction},
|
||||||
|
nonce_utils,
|
||||||
rent_collector::RentCollector,
|
rent_collector::RentCollector,
|
||||||
serde_utils::{
|
serde_utils::{
|
||||||
deserialize_atomicbool, deserialize_atomicu64, serialize_atomicbool, serialize_atomicu64,
|
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 EnteredEpochCallback = Box<dyn Fn(&mut Bank) -> () + Sync + Send>;
|
||||||
|
|
||||||
|
pub type TransactionProcessResult = (Result<()>, Option<HashAgeKind>);
|
||||||
pub struct TransactionResults {
|
pub struct TransactionResults {
|
||||||
pub fee_collection_results: Vec<Result<()>>,
|
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.
|
/// Manager for the state of all accounts and programs after processing its entries.
|
||||||
|
@ -791,16 +799,17 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
res: &[Result<()>],
|
res: &[TransactionProcessResult],
|
||||||
) {
|
) {
|
||||||
let mut status_cache = self.src.status_cache.write().unwrap();
|
let mut status_cache = self.src.status_cache.write().unwrap();
|
||||||
for (i, tx) in OrderedIterator::new(txs, iteration_order).enumerate() {
|
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(
|
status_cache.insert(
|
||||||
&tx.message().recent_blockhash,
|
&tx.message().recent_blockhash,
|
||||||
&tx.signatures[0],
|
&tx.signatures[0],
|
||||||
self.slot(),
|
self.slot(),
|
||||||
res[i].clone(),
|
res.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -868,9 +877,9 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
results: Vec<Result<()>>,
|
results: Vec<TransactionProcessResult>,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<TransactionLoadResult>> {
|
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
|
||||||
self.rc.accounts.load_accounts(
|
self.rc.accounts.load_accounts(
|
||||||
&self.ancestors,
|
&self.ancestors,
|
||||||
txs,
|
txs,
|
||||||
|
@ -907,19 +916,23 @@ impl Bank {
|
||||||
lock_results: Vec<Result<()>>,
|
lock_results: Vec<Result<()>>,
|
||||||
max_age: usize,
|
max_age: usize,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<TransactionProcessResult> {
|
||||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||||
OrderedIterator::new(txs, iteration_order)
|
OrderedIterator::new(txs, iteration_order)
|
||||||
.zip(lock_results.into_iter())
|
.zip(lock_results.into_iter())
|
||||||
.map(|(tx, lock_res)| {
|
.map(|(tx, lock_res)| match lock_res {
|
||||||
if lock_res.is_ok()
|
Ok(()) => {
|
||||||
&& !hash_queue.check_hash_age(&tx.message().recent_blockhash, max_age)
|
let message = tx.message();
|
||||||
{
|
if hash_queue.check_hash_age(&message.recent_blockhash, max_age) {
|
||||||
error_counters.reserve_blockhash += 1;
|
(Ok(()), Some(HashAgeKind::Extant))
|
||||||
Err(TransactionError::BlockhashNotFound)
|
} else if self.check_tx_durable_nonce(&tx) {
|
||||||
} else {
|
(Ok(()), Some(HashAgeKind::DurableNonce))
|
||||||
lock_res
|
} else {
|
||||||
|
error_counters.reserve_blockhash += 1;
|
||||||
|
(Err(TransactionError::BlockhashNotFound), None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => (Err(e), None),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -927,9 +940,9 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
lock_results: Vec<Result<()>>,
|
lock_results: Vec<TransactionProcessResult>,
|
||||||
error_counters: &mut ErrorCounters,
|
error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<TransactionProcessResult> {
|
||||||
let rcache = self.src.status_cache.read().unwrap();
|
let rcache = self.src.status_cache.read().unwrap();
|
||||||
OrderedIterator::new(txs, iteration_order)
|
OrderedIterator::new(txs, iteration_order)
|
||||||
.zip(lock_results.into_iter())
|
.zip(lock_results.into_iter())
|
||||||
|
@ -937,20 +950,25 @@ impl Bank {
|
||||||
if tx.signatures.is_empty() {
|
if tx.signatures.is_empty() {
|
||||||
return lock_res;
|
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;
|
let (lock_res, hash_age_kind) = &lock_res;
|
||||||
Err(TransactionError::DuplicateSignature)
|
if lock_res.is_ok()
|
||||||
} else {
|
&& rcache
|
||||||
lock_res
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -962,6 +980,18 @@ impl Bank {
|
||||||
.check_hash_age(hash, max_age)
|
.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(
|
pub fn check_transactions(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
|
@ -969,7 +999,7 @@ impl Bank {
|
||||||
lock_results: &[Result<()>],
|
lock_results: &[Result<()>],
|
||||||
max_age: usize,
|
max_age: usize,
|
||||||
mut error_counters: &mut ErrorCounters,
|
mut error_counters: &mut ErrorCounters,
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<TransactionProcessResult> {
|
||||||
let refs_results = self.check_refs(txs, iteration_order, lock_results, &mut error_counters);
|
let refs_results = self.check_refs(txs, iteration_order, lock_results, &mut error_counters);
|
||||||
let age_results = self.check_age(
|
let age_results = self.check_age(
|
||||||
txs,
|
txs,
|
||||||
|
@ -1032,8 +1062,8 @@ impl Bank {
|
||||||
batch: &TransactionBatch,
|
batch: &TransactionBatch,
|
||||||
max_age: usize,
|
max_age: usize,
|
||||||
) -> (
|
) -> (
|
||||||
Vec<Result<TransactionLoadResult>>,
|
Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>,
|
||||||
Vec<Result<()>>,
|
Vec<TransactionProcessResult>,
|
||||||
Vec<usize>,
|
Vec<usize>,
|
||||||
u64,
|
u64,
|
||||||
u64,
|
u64,
|
||||||
|
@ -1071,15 +1101,18 @@ impl Bank {
|
||||||
|
|
||||||
let mut execution_time = Measure::start("execution_time");
|
let mut execution_time = Measure::start("execution_time");
|
||||||
let mut signature_count: u64 = 0;
|
let mut signature_count: u64 = 0;
|
||||||
let executed: Vec<Result<()>> = loaded_accounts
|
let executed: Vec<TransactionProcessResult> = loaded_accounts
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(OrderedIterator::new(txs, batch.iteration_order()))
|
.zip(OrderedIterator::new(txs, batch.iteration_order()))
|
||||||
.map(|(accs, tx)| match accs {
|
.map(|(accs, tx)| match accs {
|
||||||
Err(e) => Err(e.clone()),
|
(Err(e), hash_age_kind) => (Err(e.clone()), hash_age_kind.clone()),
|
||||||
Ok((accounts, loaders, _rents)) => {
|
(Ok((accounts, loaders, _rents)), hash_age_kind) => {
|
||||||
signature_count += u64::from(tx.message().header.num_required_signatures);
|
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();
|
.collect();
|
||||||
|
@ -1094,7 +1127,7 @@ impl Bank {
|
||||||
);
|
);
|
||||||
let mut tx_count: u64 = 0;
|
let mut tx_count: u64 = 0;
|
||||||
let mut err_count = 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() {
|
if r.is_ok() {
|
||||||
tx_count += 1;
|
tx_count += 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1127,17 +1160,22 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
executed: &[Result<()>],
|
executed: &[TransactionProcessResult],
|
||||||
) -> Vec<Result<()>> {
|
) -> Vec<Result<()>> {
|
||||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||||
let mut fees = 0;
|
let mut fees = 0;
|
||||||
let results = OrderedIterator::new(txs, iteration_order)
|
let results = OrderedIterator::new(txs, iteration_order)
|
||||||
.zip(executed.iter())
|
.zip(executed.iter())
|
||||||
.map(|(tx, res)| {
|
.map(|(tx, (res, hash_age_kind))| {
|
||||||
let fee_calculator = hash_queue
|
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
|
||||||
.get_fee_calculator(&tx.message().recent_blockhash)
|
self.last_blockhash()
|
||||||
.ok_or(TransactionError::BlockhashNotFound)?;
|
} else {
|
||||||
let fee = fee_calculator.calculate_fee(tx.message());
|
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();
|
let message = tx.message();
|
||||||
match *res {
|
match *res {
|
||||||
|
@ -1166,8 +1204,8 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
loaded_accounts: &mut [Result<TransactionLoadResult>],
|
loaded_accounts: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
|
||||||
executed: &[Result<()>],
|
executed: &[TransactionProcessResult],
|
||||||
tx_count: u64,
|
tx_count: u64,
|
||||||
signature_count: u64,
|
signature_count: u64,
|
||||||
) -> TransactionResults {
|
) -> 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-txs", tx_count as usize);
|
||||||
inc_new_counter_info!("bank-process_transactions-sigs", signature_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);
|
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);
|
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;
|
let mut collected_rent: u64 = 0;
|
||||||
for (i, raccs) in loaded_accounts.iter().enumerate() {
|
for (i, (raccs, _hash_age_kind)) in loaded_accounts.iter().enumerate() {
|
||||||
if res[i].is_err() || raccs.is_err() {
|
let (res, _hash_age_kind) = &res[i];
|
||||||
|
if res.is_err() || raccs.is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1543,15 +1589,16 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
iteration_order: Option<&[usize]>,
|
iteration_order: Option<&[usize]>,
|
||||||
res: &[Result<()>],
|
res: &[TransactionProcessResult],
|
||||||
loaded: &[Result<TransactionLoadResult>],
|
loaded: &[(Result<TransactionLoadResult>, Option<HashAgeKind>)],
|
||||||
) {
|
) {
|
||||||
for (i, (raccs, tx)) in loaded
|
for (i, ((raccs, _load_hash_age_kind), tx)) in loaded
|
||||||
.iter()
|
.iter()
|
||||||
.zip(OrderedIterator::new(txs, iteration_order))
|
.zip(OrderedIterator::new(txs, iteration_order))
|
||||||
.enumerate()
|
.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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1706,11 +1753,13 @@ mod tests {
|
||||||
use solana_sdk::system_program::solana_system_program;
|
use solana_sdk::system_program::solana_system_program;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::KeyedAccount,
|
account::KeyedAccount,
|
||||||
|
account_utils::State,
|
||||||
clock::DEFAULT_TICKS_PER_SLOT,
|
clock::DEFAULT_TICKS_PER_SLOT,
|
||||||
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
||||||
genesis_config::create_genesis_config,
|
genesis_config::create_genesis_config,
|
||||||
instruction::{Instruction, InstructionError},
|
instruction::{Instruction, InstructionError},
|
||||||
message::{Message, MessageHeader},
|
message::{Message, MessageHeader},
|
||||||
|
nonce_instruction, nonce_state,
|
||||||
poh_config::PohConfig,
|
poh_config::PohConfig,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
|
@ -2943,11 +2992,14 @@ mod tests {
|
||||||
system_transaction::transfer(&mint_keypair, &key.pubkey(), 5, genesis_config.hash());
|
system_transaction::transfer(&mint_keypair, &key.pubkey(), 5, genesis_config.hash());
|
||||||
|
|
||||||
let results = vec![
|
let results = vec![
|
||||||
Ok(()),
|
(Ok(()), Some(HashAgeKind::Extant)),
|
||||||
Err(TransactionError::InstructionError(
|
(
|
||||||
1,
|
Err(TransactionError::InstructionError(
|
||||||
InstructionError::new_result_with_negative_lamports(),
|
1,
|
||||||
)),
|
InstructionError::new_result_with_negative_lamports(),
|
||||||
|
)),
|
||||||
|
Some(HashAgeKind::Extant),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
let initial_balance = bank.get_balance(&leader);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
genesis_config::GenesisConfig,
|
genesis_config::GenesisConfig,
|
||||||
|
nonce_program::solana_nonce_program,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
|
@ -74,6 +75,7 @@ pub fn create_genesis_config_with_leader(
|
||||||
// Bare minimum program set
|
// Bare minimum program set
|
||||||
let native_instruction_processors = vec![
|
let native_instruction_processors = vec![
|
||||||
solana_system_program(),
|
solana_system_program(),
|
||||||
|
solana_nonce_program(),
|
||||||
solana_bpf_loader_program!(),
|
solana_bpf_loader_program!(),
|
||||||
solana_vote_program!(),
|
solana_vote_program!(),
|
||||||
solana_stake_program!(),
|
solana_stake_program!(),
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub mod genesis_utils;
|
||||||
pub mod loader_utils;
|
pub mod loader_utils;
|
||||||
pub mod message_processor;
|
pub mod message_processor;
|
||||||
mod native_loader;
|
mod native_loader;
|
||||||
|
mod nonce_utils;
|
||||||
pub mod rent_collector;
|
pub mod rent_collector;
|
||||||
mod serde_utils;
|
mod serde_utils;
|
||||||
pub mod stakes;
|
pub mod stakes;
|
||||||
|
|
|
@ -7,6 +7,8 @@ use solana_sdk::instruction::{CompiledInstruction, InstructionError};
|
||||||
use solana_sdk::instruction_processor_utils;
|
use solana_sdk::instruction_processor_utils;
|
||||||
use solana_sdk::loader_instruction::LoaderInstruction;
|
use solana_sdk::loader_instruction::LoaderInstruction;
|
||||||
use solana_sdk::message::Message;
|
use solana_sdk::message::Message;
|
||||||
|
use solana_sdk::nonce_instruction;
|
||||||
|
use solana_sdk::nonce_program;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::system_program;
|
use solana_sdk::system_program;
|
||||||
use solana_sdk::transaction::TransactionError;
|
use solana_sdk::transaction::TransactionError;
|
||||||
|
@ -198,10 +200,13 @@ pub struct MessageProcessor {
|
||||||
|
|
||||||
impl Default for MessageProcessor {
|
impl Default for MessageProcessor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![(
|
let instruction_processors: Vec<(Pubkey, ProcessInstruction)> = vec![
|
||||||
system_program::id(),
|
(
|
||||||
system_instruction_processor::process_instruction,
|
system_program::id(),
|
||||||
)];
|
system_instruction_processor::process_instruction,
|
||||||
|
),
|
||||||
|
(nonce_program::id(), nonce_instruction::process_instruction),
|
||||||
|
];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
instruction_processors,
|
instruction_processors,
|
||||||
|
|
|
@ -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]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ serde_bytes = "0.11"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
serde_json = { version = "1.0.42", optional = true }
|
serde_json = { version = "1.0.42", optional = true }
|
||||||
sha2 = "0.8.0"
|
sha2 = "0.8.0"
|
||||||
|
thiserror = "1.0"
|
||||||
ed25519-dalek = { version = "1.0.0-pre.1", optional = true }
|
ed25519-dalek = { version = "1.0.0-pre.1", optional = true }
|
||||||
solana-crate-features = { path = "../crate-features", version = "0.22.0", optional = true }
|
solana-crate-features = { path = "../crate-features", version = "0.22.0", optional = true }
|
||||||
solana-logger = { path = "../logger", version = "0.22.0", optional = true }
|
solana-logger = { path = "../logger", version = "0.22.0", optional = true }
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
hash::{hash, Hash},
|
hash::{hash, Hash},
|
||||||
inflation::Inflation,
|
inflation::Inflation,
|
||||||
|
nonce_program::solana_nonce_program,
|
||||||
poh_config::PohConfig,
|
poh_config::PohConfig,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
|
@ -52,7 +53,7 @@ pub fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) {
|
||||||
faucet_keypair.pubkey(),
|
faucet_keypair.pubkey(),
|
||||||
Account::new(lamports, 0, &system_program::id()),
|
Account::new(lamports, 0, &system_program::id()),
|
||||||
)],
|
)],
|
||||||
&[solana_system_program()],
|
&[solana_nonce_program(), solana_system_program()],
|
||||||
),
|
),
|
||||||
faucet_keypair,
|
faucet_keypair,
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,9 @@ pub mod message;
|
||||||
pub mod move_loader;
|
pub mod move_loader;
|
||||||
pub mod native_loader;
|
pub mod native_loader;
|
||||||
pub mod native_token;
|
pub mod native_token;
|
||||||
|
pub mod nonce_instruction;
|
||||||
|
pub mod nonce_program;
|
||||||
|
pub mod nonce_state;
|
||||||
pub mod poh_config;
|
pub mod poh_config;
|
||||||
pub mod pubkey;
|
pub mod pubkey;
|
||||||
pub mod rent;
|
pub mod rent;
|
||||||
|
|
|
@ -0,0 +1,432 @@
|
||||||
|
use crate::{
|
||||||
|
account::{get_signers, KeyedAccount},
|
||||||
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
|
instruction_processor_utils::{limited_deserialize, next_keyed_account, DecodeError},
|
||||||
|
nonce_program::id,
|
||||||
|
nonce_state::{NonceAccount, NonceState},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
system_instruction,
|
||||||
|
sysvar::{
|
||||||
|
recent_blockhashes::{self, RecentBlockhashes},
|
||||||
|
rent::{self, Rent},
|
||||||
|
Sysvar,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||||
|
pub enum NonceError {
|
||||||
|
#[error("recent blockhash list is empty")]
|
||||||
|
NoRecentBlockhashes,
|
||||||
|
#[error("stored nonce is still in recent_blockhashes")]
|
||||||
|
NotExpired,
|
||||||
|
#[error("specified nonce does not match stored nonce")]
|
||||||
|
UnexpectedValue,
|
||||||
|
#[error("cannot handle request in current account state")]
|
||||||
|
BadAccountState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> DecodeError<E> for NonceError {
|
||||||
|
fn type_of() -> &'static str {
|
||||||
|
"NonceError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
pub enum NonceInstruction {
|
||||||
|
/// `Nonce` consumes a stored nonce, replacing it with a successor
|
||||||
|
///
|
||||||
|
/// Expects 3 Accounts:
|
||||||
|
/// 0 - A NonceAccount
|
||||||
|
/// 1 - RecentBlockhashes sysvar
|
||||||
|
/// 2 - Rent sysvar
|
||||||
|
///
|
||||||
|
Nonce,
|
||||||
|
|
||||||
|
/// `Withdraw` transfers funds out of the nonce account
|
||||||
|
///
|
||||||
|
/// Expects 4 Accounts:
|
||||||
|
/// 0 - A NonceAccount
|
||||||
|
/// 1 - A system account to which the lamports will be transferred
|
||||||
|
/// 2 - RecentBlockhashes sysvar
|
||||||
|
/// 3 - Rent sysvar
|
||||||
|
///
|
||||||
|
/// The `u64` parameter is the lamports to withdraw, which must leave the
|
||||||
|
/// account balance above the rent exempt reserve or at zero.
|
||||||
|
Withdraw(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_nonce_account(
|
||||||
|
from_pubkey: &Pubkey,
|
||||||
|
nonce_pubkey: &Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
) -> Vec<Instruction> {
|
||||||
|
vec![
|
||||||
|
system_instruction::create_account(
|
||||||
|
from_pubkey,
|
||||||
|
nonce_pubkey,
|
||||||
|
lamports,
|
||||||
|
NonceState::size() as u64,
|
||||||
|
&id(),
|
||||||
|
),
|
||||||
|
nonce(nonce_pubkey),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nonce(nonce_pubkey: &Pubkey) -> Instruction {
|
||||||
|
Instruction::new(
|
||||||
|
id(),
|
||||||
|
&NonceInstruction::Nonce,
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(*nonce_pubkey, true),
|
||||||
|
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||||
|
AccountMeta::new_readonly(rent::id(), false),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn withdraw(nonce_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
|
||||||
|
Instruction::new(
|
||||||
|
id(),
|
||||||
|
&NonceInstruction::Withdraw(lamports),
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(*nonce_pubkey, true),
|
||||||
|
AccountMeta::new(*to_pubkey, false),
|
||||||
|
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||||
|
AccountMeta::new_readonly(rent::id(), false),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
keyed_accounts: &mut [KeyedAccount],
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
let signers = get_signers(keyed_accounts);
|
||||||
|
|
||||||
|
let keyed_accounts = &mut keyed_accounts.iter_mut();
|
||||||
|
let me = &mut next_keyed_account(keyed_accounts)?;
|
||||||
|
|
||||||
|
match limited_deserialize(data)? {
|
||||||
|
NonceInstruction::Nonce => me.nonce(
|
||||||
|
&RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
&Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
&signers,
|
||||||
|
),
|
||||||
|
NonceInstruction::Withdraw(lamports) => {
|
||||||
|
let to = &mut next_keyed_account(keyed_accounts)?;
|
||||||
|
me.withdraw(
|
||||||
|
lamports,
|
||||||
|
to,
|
||||||
|
&RecentBlockhashes::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
&Rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{account::Account, hash::Hash, nonce_state, system_program, sysvar};
|
||||||
|
use bincode::serialize;
|
||||||
|
|
||||||
|
fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
|
||||||
|
let mut accounts: Vec<_> = instruction
|
||||||
|
.accounts
|
||||||
|
.iter()
|
||||||
|
.map(|meta| {
|
||||||
|
if sysvar::recent_blockhashes::check_id(&meta.pubkey) {
|
||||||
|
sysvar::recent_blockhashes::create_account_with_data(
|
||||||
|
1,
|
||||||
|
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||||
|
)
|
||||||
|
} else if sysvar::rent::check_id(&meta.pubkey) {
|
||||||
|
sysvar::rent::create_account(1, &Rent::default())
|
||||||
|
} else {
|
||||||
|
Account::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut keyed_accounts: Vec<_> = instruction
|
||||||
|
.accounts
|
||||||
|
.iter()
|
||||||
|
.zip(accounts.iter_mut())
|
||||||
|
.map(|(meta, account)| KeyedAccount::new(&meta.pubkey, meta.is_signer, account))
|
||||||
|
.collect();
|
||||||
|
super::process_instruction(&Pubkey::default(), &mut keyed_accounts, &instruction.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_account() {
|
||||||
|
let from_pubkey = Pubkey::new_rand();
|
||||||
|
let nonce_pubkey = Pubkey::new_rand();
|
||||||
|
let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, 42);
|
||||||
|
assert_eq!(ixs.len(), 2);
|
||||||
|
let ix = &ixs[0];
|
||||||
|
assert_eq!(ix.program_id, system_program::id());
|
||||||
|
let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
|
||||||
|
assert!(pubkeys.contains(&from_pubkey));
|
||||||
|
assert!(pubkeys.contains(&nonce_pubkey));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_no_acc_data_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
process_instruction(&nonce(&Pubkey::default(),)),
|
||||||
|
Err(InstructionError::InvalidAccountData),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_no_keyed_accs_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [],
|
||||||
|
&serialize(&NonceInstruction::Nonce).unwrap()
|
||||||
|
),
|
||||||
|
Err(InstructionError::NotEnoughAccountKeys),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_only_nonce_acc_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [KeyedAccount::new(
|
||||||
|
&Pubkey::default(),
|
||||||
|
true,
|
||||||
|
&mut Account::default(),
|
||||||
|
),],
|
||||||
|
&serialize(&NonceInstruction::Nonce).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::NotEnoughAccountKeys),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_bad_recent_blockhash_state_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut Account::default(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Nonce).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidArgument),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_bad_rent_state_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut sysvar::recent_blockhashes::create_account(1),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Nonce).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidArgument),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_nonce_ix_ok() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(
|
||||||
|
&Pubkey::default(),
|
||||||
|
true,
|
||||||
|
&mut nonce_state::create_account(1_000_000),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||||
|
1,
|
||||||
|
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::rent::id(),
|
||||||
|
false,
|
||||||
|
&mut sysvar::rent::create_account(1, &Rent::default()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Nonce).unwrap(),
|
||||||
|
),
|
||||||
|
Ok(()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_no_acc_data_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
process_instruction(&withdraw(&Pubkey::default(), &Pubkey::default(), 1,)),
|
||||||
|
Err(InstructionError::InvalidAccountData),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_no_keyed_accs_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [],
|
||||||
|
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::NotEnoughAccountKeys),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_only_nonce_acc_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [KeyedAccount::new(
|
||||||
|
&Pubkey::default(),
|
||||||
|
true,
|
||||||
|
&mut Account::default(),
|
||||||
|
),],
|
||||||
|
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::NotEnoughAccountKeys),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_bad_recent_blockhash_state_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut Account::default(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidArgument),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_bad_rent_state_fail() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut Account::default(),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
|
||||||
|
),
|
||||||
|
Err(InstructionError::InvalidArgument),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_withdraw_ix_ok() {
|
||||||
|
assert_eq!(
|
||||||
|
super::process_instruction(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&mut [
|
||||||
|
KeyedAccount::new(
|
||||||
|
&Pubkey::default(),
|
||||||
|
true,
|
||||||
|
&mut nonce_state::create_account(1_000_000),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default(),),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::recent_blockhashes::id(),
|
||||||
|
false,
|
||||||
|
&mut sysvar::recent_blockhashes::create_account_with_data(
|
||||||
|
1,
|
||||||
|
vec![(0u64, &Hash::default()); 32].into_iter(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
KeyedAccount::new(
|
||||||
|
&sysvar::rent::id(),
|
||||||
|
false,
|
||||||
|
&mut sysvar::rent::create_account(1, &Rent::default())
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&serialize(&NonceInstruction::Withdraw(42)).unwrap(),
|
||||||
|
),
|
||||||
|
Ok(()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_error_decode() {
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
fn pretty_err<T>(err: InstructionError) -> String
|
||||||
|
where
|
||||||
|
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
|
||||||
|
{
|
||||||
|
if let InstructionError::CustomError(code) = err {
|
||||||
|
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
|
||||||
|
format!(
|
||||||
|
"{:?}: {}::{:?} - {}",
|
||||||
|
err,
|
||||||
|
T::type_of(),
|
||||||
|
specific_error,
|
||||||
|
specific_error,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
"CustomError(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
|
||||||
|
pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"CustomError(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
|
||||||
|
pretty_err::<NonceError>(NonceError::NotExpired.into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"CustomError(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
|
||||||
|
pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"CustomError(3): NonceError::BadAccountState - cannot handle request in current account state",
|
||||||
|
pretty_err::<NonceError>(NonceError::BadAccountState.into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
crate::declare_id!("Nonce11111111111111111111111111111111111111");
|
||||||
|
|
||||||
|
pub fn solana_nonce_program() -> (String, crate::pubkey::Pubkey) {
|
||||||
|
("solana_nonce_program".to_string(), id())
|
||||||
|
}
|
|
@ -0,0 +1,618 @@
|
||||||
|
use crate::{
|
||||||
|
account::{Account, KeyedAccount},
|
||||||
|
account_utils::State,
|
||||||
|
hash::Hash,
|
||||||
|
instruction::InstructionError,
|
||||||
|
nonce_instruction::NonceError,
|
||||||
|
nonce_program,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
sysvar::recent_blockhashes::RecentBlockhashes,
|
||||||
|
sysvar::rent::Rent,
|
||||||
|
};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||||
|
pub struct Meta {}
|
||||||
|
|
||||||
|
impl Meta {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||||
|
pub enum NonceState {
|
||||||
|
Uninitialized,
|
||||||
|
Initialized(Meta, Hash),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NonceState {
|
||||||
|
fn default() -> Self {
|
||||||
|
NonceState::Uninitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonceState {
|
||||||
|
pub fn size() -> usize {
|
||||||
|
bincode::serialized_size(&NonceState::Initialized(Meta::default(), Hash::default()))
|
||||||
|
.unwrap() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NonceAccount {
|
||||||
|
fn nonce(
|
||||||
|
&mut self,
|
||||||
|
recent_blockhashes: &RecentBlockhashes,
|
||||||
|
rent: &Rent,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
) -> Result<(), InstructionError>;
|
||||||
|
fn withdraw(
|
||||||
|
&mut self,
|
||||||
|
lamports: u64,
|
||||||
|
to: &mut KeyedAccount,
|
||||||
|
recent_blockhashes: &RecentBlockhashes,
|
||||||
|
rent: &Rent,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
) -> Result<(), InstructionError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NonceAccount for KeyedAccount<'a> {
|
||||||
|
fn nonce(
|
||||||
|
&mut self,
|
||||||
|
recent_blockhashes: &RecentBlockhashes,
|
||||||
|
rent: &Rent,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
if recent_blockhashes.is_empty() {
|
||||||
|
return Err(NonceError::NoRecentBlockhashes.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !signers.contains(self.unsigned_key()) {
|
||||||
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
let meta = match self.state()? {
|
||||||
|
NonceState::Initialized(meta, ref hash) => {
|
||||||
|
if *hash == recent_blockhashes[0] {
|
||||||
|
return Err(NonceError::NotExpired.into());
|
||||||
|
}
|
||||||
|
meta
|
||||||
|
}
|
||||||
|
NonceState::Uninitialized => {
|
||||||
|
let min_balance = rent.minimum_balance(self.account.data.len());
|
||||||
|
if self.account.lamports < min_balance {
|
||||||
|
return Err(InstructionError::InsufficientFunds);
|
||||||
|
}
|
||||||
|
Meta::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_state(&NonceState::Initialized(meta, recent_blockhashes[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn withdraw(
|
||||||
|
&mut self,
|
||||||
|
lamports: u64,
|
||||||
|
to: &mut KeyedAccount,
|
||||||
|
recent_blockhashes: &RecentBlockhashes,
|
||||||
|
rent: &Rent,
|
||||||
|
signers: &HashSet<Pubkey>,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
if !signers.contains(self.unsigned_key()) {
|
||||||
|
return Err(InstructionError::MissingRequiredSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.state()? {
|
||||||
|
NonceState::Uninitialized => {
|
||||||
|
if lamports > self.account.lamports {
|
||||||
|
return Err(InstructionError::InsufficientFunds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NonceState::Initialized(_meta, ref hash) => {
|
||||||
|
if lamports == self.account.lamports {
|
||||||
|
if *hash == recent_blockhashes[0] {
|
||||||
|
return Err(NonceError::NotExpired.into());
|
||||||
|
}
|
||||||
|
self.set_state(&NonceState::Uninitialized)?;
|
||||||
|
} else {
|
||||||
|
let min_balance = rent.minimum_balance(self.account.data.len());
|
||||||
|
if lamports + min_balance > self.account.lamports {
|
||||||
|
return Err(InstructionError::InsufficientFunds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.account.lamports -= lamports;
|
||||||
|
to.account.lamports += lamports;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_account(lamports: u64) -> Account {
|
||||||
|
Account::new_data_with_space(
|
||||||
|
lamports,
|
||||||
|
&NonceState::Uninitialized,
|
||||||
|
NonceState::size(),
|
||||||
|
&nonce_program::id(),
|
||||||
|
)
|
||||||
|
.expect("nonce_account")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for working with keyed accounts in tests
|
||||||
|
#[cfg(not(feature = "program"))]
|
||||||
|
pub fn with_test_keyed_account<F>(lamports: u64, signer: bool, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&mut KeyedAccount),
|
||||||
|
{
|
||||||
|
let pubkey = Pubkey::new_rand();
|
||||||
|
let mut account = create_account(lamports);
|
||||||
|
let mut keyed_account = KeyedAccount::new(&pubkey, signer, &mut account);
|
||||||
|
f(&mut keyed_account)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
account::KeyedAccount,
|
||||||
|
nonce_instruction::NonceError,
|
||||||
|
sysvar::recent_blockhashes::{create_test_recent_blockhashes, RecentBlockhashes},
|
||||||
|
};
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_is_uninitialized() {
|
||||||
|
assert_eq!(NonceState::default(), NonceState::Uninitialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_meta() {
|
||||||
|
assert_eq!(Meta::new(), Meta {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyed_account_expected_behavior() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
let meta = Meta::new();
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(keyed_account.signer_key().unwrap().clone());
|
||||||
|
let state: NonceState = keyed_account.state().unwrap();
|
||||||
|
// New is in Uninitialzed state
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||||
|
keyed_account
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = keyed_account.state().unwrap();
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
// First nonce instruction drives state from Uninitialized to Initialized
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||||
|
keyed_account
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = keyed_account.state().unwrap();
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
// Second nonce instruction consumes and replaces stored nonce
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||||
|
keyed_account
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = keyed_account.state().unwrap();
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
// Third nonce instruction for fun and profit
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let withdraw_lamports = keyed_account.account.lamports;
|
||||||
|
let expect_nonce_lamports = keyed_account.account.lamports - withdraw_lamports;
|
||||||
|
let expect_to_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
keyed_account
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = keyed_account.state().unwrap();
|
||||||
|
// Withdraw instruction...
|
||||||
|
// Deinitializes NonceAccount state
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
// Empties NonceAccount balance
|
||||||
|
assert_eq!(keyed_account.account.lamports, expect_nonce_lamports);
|
||||||
|
// NonceAccount balance goes to `to`
|
||||||
|
assert_eq!(to_keyed.account.lamports, expect_to_lamports);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonce_inx_uninitialized_account_not_signer_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, false, |nonce_account| {
|
||||||
|
let signers = HashSet::new();
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers);
|
||||||
|
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonce_inx_initialized_account_not_signer_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
let meta = Meta::new();
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_account| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_account.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
nonce_account
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let pubkey = nonce_account.account.owner.clone();
|
||||||
|
let mut nonce_account = KeyedAccount::new(&pubkey, false, nonce_account.account);
|
||||||
|
let state: NonceState = nonce_account.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
let signers = HashSet::new();
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let result = nonce_account.nonce(&recent_blockhashes, &rent, &signers);
|
||||||
|
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonce_inx_with_empty_recent_blockhashes_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(keyed_account.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = RecentBlockhashes::from_iter(vec![].into_iter());
|
||||||
|
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers);
|
||||||
|
assert_eq!(result, Err(NonceError::NoRecentBlockhashes.into()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonce_inx_too_early_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |keyed_account| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(keyed_account.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||||
|
keyed_account
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers);
|
||||||
|
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonce_inx_uninitialized_acc_insuff_funds_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports - 42, true, |keyed_account| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(keyed_account.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||||
|
let result = keyed_account.nonce(&recent_blockhashes, &rent, &signers);
|
||||||
|
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_unintialized_acc_ok() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports;
|
||||||
|
let expect_nonce_lamports = nonce_keyed.account.lamports - withdraw_lamports;
|
||||||
|
let expect_to_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
nonce_keyed
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
// Withdraw instruction...
|
||||||
|
// Deinitializes NonceAccount state
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
// Empties NonceAccount balance
|
||||||
|
assert_eq!(nonce_keyed.account.lamports, expect_nonce_lamports);
|
||||||
|
// NonceAccount balance goes to `to`
|
||||||
|
assert_eq!(to_keyed.account.lamports, expect_to_lamports);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_unintialized_acc_unsigned_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, false, |nonce_keyed| {
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let signers = HashSet::new();
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let result = nonce_keyed.withdraw(
|
||||||
|
nonce_keyed.account.lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
);
|
||||||
|
assert_eq!(result, Err(InstructionError::MissingRequiredSignature),);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_unintialized_acc_insuff_funds_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let result = nonce_keyed.withdraw(
|
||||||
|
nonce_keyed.account.lamports + 1,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
);
|
||||||
|
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_uninitialized_acc_two_withdraws_ok() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports / 2;
|
||||||
|
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
|
||||||
|
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
nonce_keyed
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
|
||||||
|
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports;
|
||||||
|
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
|
||||||
|
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
nonce_keyed
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
|
||||||
|
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_initialized_acc_two_withdraws_ok() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
let meta = Meta::new();
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(31);
|
||||||
|
nonce_keyed
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports - min_lamports;
|
||||||
|
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
|
||||||
|
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
nonce_keyed
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
let stored = recent_blockhashes[0];
|
||||||
|
assert_eq!(state, NonceState::Initialized(meta, stored));
|
||||||
|
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
|
||||||
|
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports;
|
||||||
|
let nonce_expect_lamports = nonce_keyed.account.lamports - withdraw_lamports;
|
||||||
|
let to_expect_lamports = to_keyed.account.lamports + withdraw_lamports;
|
||||||
|
nonce_keyed
|
||||||
|
.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let state: NonceState = nonce_keyed.state().unwrap();
|
||||||
|
assert_eq!(state, NonceState::Uninitialized);
|
||||||
|
assert_eq!(nonce_keyed.account.lamports, nonce_expect_lamports);
|
||||||
|
assert_eq!(to_keyed.account.lamports, to_expect_lamports);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_initialized_acc_nonce_too_early_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(0);
|
||||||
|
nonce_keyed
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports;
|
||||||
|
let result = nonce_keyed.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
);
|
||||||
|
assert_eq!(result, Err(NonceError::NotExpired.into()));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_initialized_acc_insuff_funds_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||||
|
nonce_keyed
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports + 1;
|
||||||
|
let result = nonce_keyed.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
);
|
||||||
|
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn withdraw_inx_initialized_acc_insuff_rent_fail() {
|
||||||
|
let rent = Rent {
|
||||||
|
lamports_per_byte_year: 42,
|
||||||
|
..Rent::default()
|
||||||
|
};
|
||||||
|
let min_lamports = rent.minimum_balance(NonceState::size());
|
||||||
|
with_test_keyed_account(min_lamports + 42, true, |nonce_keyed| {
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(95);
|
||||||
|
nonce_keyed
|
||||||
|
.nonce(&recent_blockhashes, &rent, &signers)
|
||||||
|
.unwrap();
|
||||||
|
with_test_keyed_account(42, false, |mut to_keyed| {
|
||||||
|
let recent_blockhashes = create_test_recent_blockhashes(63);
|
||||||
|
let mut signers = HashSet::new();
|
||||||
|
signers.insert(nonce_keyed.signer_key().unwrap().clone());
|
||||||
|
let withdraw_lamports = nonce_keyed.account.lamports - min_lamports + 1;
|
||||||
|
let result = nonce_keyed.withdraw(
|
||||||
|
withdraw_lamports,
|
||||||
|
&mut to_keyed,
|
||||||
|
&recent_blockhashes,
|
||||||
|
&rent,
|
||||||
|
&signers,
|
||||||
|
);
|
||||||
|
assert_eq!(result, Err(InstructionError::InsufficientFunds));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
use crate::{account::Account, hash::Hash, sysvar::Sysvar};
|
use crate::{
|
||||||
|
account::Account,
|
||||||
|
hash::{hash, Hash},
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
use bincode::serialize;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -69,6 +74,13 @@ where
|
||||||
account
|
account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_test_recent_blockhashes(start: usize) -> RecentBlockhashes {
|
||||||
|
let bhq: Vec<_> = (start..start + (MAX_ENTRIES - 1))
|
||||||
|
.map(|i| hash(&serialize(&i).unwrap()))
|
||||||
|
.collect();
|
||||||
|
RecentBlockhashes::from_iter(bhq.iter())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in New Issue