Move Bank to its own crate

Also:
* counters.rs to solana_metrics
* genesis_block.rs to solana_sdk
This commit is contained in:
Greg Fitzgerald
2019-02-18 23:26:22 -07:00
parent 781f7ef570
commit dde886f058
61 changed files with 409 additions and 382 deletions

View File

@ -9,6 +9,16 @@ homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.1.2"
bv = { version = "0.11.0", features = ["serde"] }
fnv = "1.0.6"
hashbrown = "0.1.8"
log = "0.4.2"
rand = "0.6.5"
serde = "1.0.88"
serde_derive = "1.0.88"
serde_json = "1.0.38"
solana-metrics = { path = "../metrics", version = "0.12.0" }
solana-sdk = { path = "../sdk", version = "0.12.0" }
solana-native-loader = { path = "../programs/native/native_loader", version = "0.12.0" }
solana-system-program = { path = "../programs/native/system", version = "0.12.0" }

56
runtime/benches/bank.rs Normal file
View File

@ -0,0 +1,56 @@
#![feature(test)]
extern crate test;
use solana_runtime::bank::*;
use solana_runtime::last_id_queue::MAX_ENTRY_IDS;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction::SystemTransaction;
use test::Bencher;
#[bench]
fn bench_process_transaction(bencher: &mut Bencher) {
let (genesis_block, mint_keypair) = GenesisBlock::new(100_000_000);
let bank = Bank::new(&genesis_block);
// Create transactions between unrelated parties.
let transactions: Vec<_> = (0..4096)
.into_iter()
.map(|_| {
// Seed the 'from' account.
let rando0 = Keypair::new();
let tx = SystemTransaction::new_move(
&mint_keypair,
rando0.pubkey(),
10_000,
bank.last_id(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
// Seed the 'to' account and a cell for its signature.
let rando1 = Keypair::new();
let tx = SystemTransaction::new_move(&rando0, rando1.pubkey(), 1, bank.last_id(), 0);
assert_eq!(bank.process_transaction(&tx), Ok(()));
// Finally, return the transaction to the benchmark.
tx
})
.collect();
let mut id = bank.last_id();
for _ in 0..(MAX_ENTRY_IDS - 1) {
bank.register_tick(&id);
id = hash(&id.as_ref())
}
bencher.iter(|| {
// Since benchmarker runs this multiple times, we need to clear the signatures.
bank.clear_signatures();
let results = bank.process_transactions(&transactions);
assert!(results.iter().all(Result::is_ok));
})
}

101
runtime/benches/bloom.rs Normal file
View File

@ -0,0 +1,101 @@
#![feature(test)]
extern crate test;
use bv::BitVec;
use fnv::FnvHasher;
use solana_runtime::bloom::{Bloom, BloomHashIndex};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::Signature;
//use std::collections::HashSet;
use hashbrown::HashSet;
use std::hash::Hasher;
use test::Bencher;
#[bench]
#[ignore]
fn bench_bits_set(bencher: &mut Bencher) {
let mut bits: BitVec<u8> = BitVec::new_fill(false, 38_340_234 as u64);
let mut hasher = FnvHasher::default();
bencher.iter(|| {
let idx = hasher.finish() % bits.len();
bits.set(idx, true);
hasher.write_u64(idx);
});
// subtract the next bencher result from this one to get a number for raw
// bits.set()
}
#[bench]
#[ignore]
fn bench_bits_set_hasher(bencher: &mut Bencher) {
let bits: BitVec<u8> = BitVec::new_fill(false, 38_340_234 as u64);
let mut hasher = FnvHasher::default();
bencher.iter(|| {
let idx = hasher.finish() % bits.len();
hasher.write_u64(idx);
});
}
#[bench]
#[ignore]
fn bench_sigs_bloom(bencher: &mut Bencher) {
// 1M TPS * 1s (length of block in sigs) == 1M items in filter
// 1.0E-8 false positive rate
// https://hur.st/bloomfilter/?n=1000000&p=1.0E-8&m=&k=
let last_id = hash(Hash::default().as_ref());
// eprintln!("last_id = {:?}", last_id);
let keys = (0..27)
.into_iter()
.map(|i| last_id.hash_at_index(i))
.collect();
let mut sigs: Bloom<Signature> = Bloom::new(38_340_234, keys);
let mut id = last_id;
let mut falses = 0;
let mut iterations = 0;
bencher.iter(|| {
id = hash(id.as_ref());
let mut sigbytes = Vec::from(id.as_ref());
id = hash(id.as_ref());
sigbytes.extend(id.as_ref());
let sig = Signature::new(&sigbytes);
if sigs.contains(&sig) {
falses += 1;
}
sigs.add(&sig);
sigs.contains(&sig);
iterations += 1;
});
assert_eq!(falses, 0);
}
#[bench]
#[ignore]
fn bench_sigs_hashmap(bencher: &mut Bencher) {
// same structure as above, new
let last_id = hash(Hash::default().as_ref());
// eprintln!("last_id = {:?}", last_id);
let mut sigs: HashSet<Signature> = HashSet::new();
let mut id = last_id;
let mut falses = 0;
let mut iterations = 0;
bencher.iter(|| {
id = hash(id.as_ref());
let mut sigbytes = Vec::from(id.as_ref());
id = hash(id.as_ref());
sigbytes.extend(id.as_ref());
let sig = Signature::new(&sigbytes);
if sigs.contains(&sig) {
falses += 1;
}
sigs.insert(sig);
sigs.contains(&sig);
iterations += 1;
});
assert_eq!(falses, 0);
}

822
runtime/src/accounts.rs Normal file
View File

@ -0,0 +1,822 @@
use crate::bank::{BankError, Result};
use crate::runtime::has_duplicates;
use bincode::serialize;
use hashbrown::{HashMap, HashSet};
use log::{debug, Level};
use solana_metrics::counter::Counter;
use solana_sdk::account::Account;
use solana_sdk::hash::{hash, Hash};
use solana_sdk::native_loader;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::transaction::Transaction;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::sync::{Mutex, RwLock};
pub type InstructionAccounts = Vec<Account>;
pub type InstructionLoaders = Vec<Vec<(Pubkey, Account)>>;
#[derive(Debug, Default)]
pub struct ErrorCounters {
pub account_not_found: usize,
pub account_in_use: usize,
pub account_loaded_twice: usize,
pub last_id_not_found: usize,
pub last_id_too_old: usize,
pub reserve_last_id: usize,
pub insufficient_funds: usize,
pub duplicate_signature: usize,
pub call_chain_too_deep: usize,
pub missing_signature_for_fee: usize,
}
/// This structure handles the load/store of the accounts
pub struct AccountsDB {
/// Mapping of known public keys/IDs to accounts
pub accounts: HashMap<Pubkey, Account>,
/// The number of transactions the bank has processed without error since the
/// start of the ledger.
transaction_count: u64,
}
/// This structure handles synchronization for db
pub struct Accounts {
pub accounts_db: RwLock<AccountsDB>,
/// set of accounts which are currently in the pipeline
account_locks: Mutex<HashSet<Pubkey>>,
}
impl Default for AccountsDB {
fn default() -> Self {
Self {
accounts: HashMap::new(),
transaction_count: 0,
}
}
}
impl Default for Accounts {
fn default() -> Self {
Self {
account_locks: Mutex::new(HashSet::new()),
accounts_db: RwLock::new(AccountsDB::default()),
}
}
}
impl AccountsDB {
pub fn hash_internal_state(&self) -> Hash {
let mut ordered_accounts = BTreeMap::new();
// only hash internal state of the part being voted upon, i.e. since last
// checkpoint
for (pubkey, account) in &self.accounts {
ordered_accounts.insert(*pubkey, account.clone());
}
hash(&serialize(&ordered_accounts).unwrap())
}
fn load<U>(checkpoints: &[U], pubkey: &Pubkey) -> Option<Account>
where
U: Deref<Target = Self>,
{
for db in checkpoints {
if let Some(account) = db.accounts.get(pubkey) {
return Some(account.clone());
}
}
None
}
/// Store the account update. If the update is to delete the account because the token balance
/// is 0, purge needs to be set to true for the delete to occur in place.
pub fn store(&mut self, purge: bool, pubkey: &Pubkey, account: &Account) {
if account.tokens == 0 {
if purge {
// purge if balance is 0 and no checkpoints
self.accounts.remove(pubkey);
} else {
// store default account if balance is 0 and there's a checkpoint
self.accounts.insert(pubkey.clone(), Account::default());
}
} else {
self.accounts.insert(pubkey.clone(), account.clone());
}
}
pub fn store_accounts(
&mut self,
purge: bool,
txs: &[Transaction],
res: &[Result<()>],
loaded: &[Result<(InstructionAccounts, InstructionLoaders)>],
) {
for (i, raccs) in loaded.iter().enumerate() {
if res[i].is_err() || raccs.is_err() {
continue;
}
let tx = &txs[i];
let acc = raccs.as_ref().unwrap();
for (key, account) in tx.account_keys.iter().zip(acc.0.iter()) {
self.store(purge, key, account);
}
}
}
fn load_tx_accounts<U>(
checkpoints: &[U],
tx: &Transaction,
error_counters: &mut ErrorCounters,
) -> Result<Vec<Account>>
where
U: Deref<Target = Self>,
{
// Copy all the accounts
if tx.signatures.is_empty() && tx.fee != 0 {
Err(BankError::MissingSignatureForFee)
} else {
// Check for unique account keys
if has_duplicates(&tx.account_keys) {
error_counters.account_loaded_twice += 1;
return Err(BankError::AccountLoadedTwice);
}
// There is no way to predict what program will execute without an error
// If a fee can pay for execution then the program will be scheduled
let mut called_accounts: Vec<Account> = vec![];
for key in &tx.account_keys {
called_accounts.push(Self::load(checkpoints, key).unwrap_or_default());
}
if called_accounts.is_empty() || called_accounts[0].tokens == 0 {
error_counters.account_not_found += 1;
Err(BankError::AccountNotFound)
} else if called_accounts[0].tokens < tx.fee {
error_counters.insufficient_funds += 1;
Err(BankError::InsufficientFundsForFee)
} else {
called_accounts[0].tokens -= tx.fee;
Ok(called_accounts)
}
}
}
fn load_executable_accounts<U>(
checkpoints: &[U],
mut program_id: Pubkey,
error_counters: &mut ErrorCounters,
) -> Result<Vec<(Pubkey, Account)>>
where
U: Deref<Target = Self>,
{
let mut accounts = Vec::new();
let mut depth = 0;
loop {
if native_loader::check_id(&program_id) {
// at the root of the chain, ready to dispatch
break;
}
if depth >= 5 {
error_counters.call_chain_too_deep += 1;
return Err(BankError::CallChainTooDeep);
}
depth += 1;
let program = match Self::load(checkpoints, &program_id) {
Some(program) => program,
None => {
error_counters.account_not_found += 1;
return Err(BankError::AccountNotFound);
}
};
if !program.executable || program.owner == Pubkey::default() {
error_counters.account_not_found += 1;
return Err(BankError::AccountNotFound);
}
// add loader to chain
accounts.insert(0, (program_id, program.clone()));
program_id = program.owner;
}
Ok(accounts)
}
/// For each program_id in the transaction, load its loaders.
fn load_loaders<U>(
checkpoints: &[U],
tx: &Transaction,
error_counters: &mut ErrorCounters,
) -> Result<Vec<Vec<(Pubkey, Account)>>>
where
U: Deref<Target = Self>,
{
tx.instructions
.iter()
.map(|ix| {
if tx.program_ids.len() <= ix.program_ids_index as usize {
error_counters.account_not_found += 1;
return Err(BankError::AccountNotFound);
}
let program_id = tx.program_ids[ix.program_ids_index as usize];
Self::load_executable_accounts(checkpoints, program_id, error_counters)
})
.collect()
}
fn load_accounts<U>(
checkpoints: &[U],
txs: &[Transaction],
lock_results: Vec<Result<()>>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>>
where
U: Deref<Target = Self>,
{
txs.iter()
.zip(lock_results.into_iter())
.map(|etx| match etx {
(tx, Ok(())) => {
let accounts = Self::load_tx_accounts(checkpoints, tx, error_counters)?;
let loaders = Self::load_loaders(checkpoints, tx, error_counters)?;
Ok((accounts, loaders))
}
(_, Err(e)) => Err(e),
})
.collect()
}
pub fn increment_transaction_count(&mut self, tx_count: usize) {
self.transaction_count += tx_count as u64
}
pub fn transaction_count(&self) -> u64 {
self.transaction_count
}
//pub fn account_values_slow(&self) -> Vec<(Pubkey, solana_sdk::account::Account)> {
// self.accounts.iter().map(|(x, y)| (*x, y.clone())).collect()
//}
//fn merge(&mut self, other: Self) {
// self.transaction_count += other.transaction_count;
// self.accounts.extend(other.accounts)
//}
}
impl Accounts {
/// Slow because lock is held for 1 operation insted of many
pub fn load_slow<U>(checkpoints: &[U], pubkey: &Pubkey) -> Option<Account>
where
U: Deref<Target = Self>,
{
let dbs: Vec<_> = checkpoints
.iter()
.map(|obj| obj.accounts_db.read().unwrap())
.collect();
AccountsDB::load(&dbs, pubkey)
}
/// Slow because lock is held for 1 operation insted of many
/// * purge - if the account token value is 0 and purge is true then delete the account.
/// purge should be set to false for overlays, and true for the root checkpoint.
pub fn store_slow(&self, purge: bool, pubkey: &Pubkey, account: &Account) {
self.accounts_db
.write()
.unwrap()
.store(purge, pubkey, account)
}
fn lock_account(
account_locks: &mut HashSet<Pubkey>,
keys: &[Pubkey],
error_counters: &mut ErrorCounters,
) -> Result<()> {
// Copy all the accounts
for k in keys {
if account_locks.contains(k) {
error_counters.account_in_use += 1;
return Err(BankError::AccountInUse);
}
}
for k in keys {
account_locks.insert(*k);
}
Ok(())
}
fn unlock_account(tx: &Transaction, result: &Result<()>, account_locks: &mut HashSet<Pubkey>) {
match result {
Err(BankError::AccountInUse) => (),
_ => {
for k in &tx.account_keys {
account_locks.remove(k);
}
}
}
}
pub fn hash_internal_state(&self) -> Hash {
self.accounts_db.read().unwrap().hash_internal_state()
}
/// This function will prevent multiple threads from modifying the same account state at the
/// same time
#[must_use]
pub fn lock_accounts(&self, txs: &[Transaction]) -> Vec<Result<()>> {
let mut account_locks = self.account_locks.lock().unwrap();
let mut error_counters = ErrorCounters::default();
let rv = txs
.iter()
.map(|tx| Self::lock_account(&mut account_locks, &tx.account_keys, &mut error_counters))
.collect();
if error_counters.account_in_use != 0 {
inc_new_counter_info!(
"bank-process_transactions-account_in_use",
error_counters.account_in_use
);
}
rv
}
/// Once accounts are unlocked, new transactions that modify that state can enter the pipeline
pub fn unlock_accounts(&self, txs: &[Transaction], results: &[Result<()>]) {
let mut account_locks = self.account_locks.lock().unwrap();
debug!("bank unlock accounts");
txs.iter()
.zip(results.iter())
.for_each(|(tx, result)| Self::unlock_account(tx, result, &mut account_locks));
}
pub fn load_accounts<U>(
checkpoints: &[U],
txs: &[Transaction],
results: Vec<Result<()>>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>>
where
U: Deref<Target = Self>,
{
let dbs: Vec<_> = checkpoints
.iter()
.map(|obj| obj.accounts_db.read().unwrap())
.collect();
AccountsDB::load_accounts(&dbs, txs, results, error_counters)
}
/// Store the accounts into the DB
/// * purge - if the account token value is 0 and purge is true then delete the account.
/// purge should be set to false for overlays, and true for the root checkpoint.
pub fn store_accounts(
&self,
purge: bool,
txs: &[Transaction],
res: &[Result<()>],
loaded: &[Result<(InstructionAccounts, InstructionLoaders)>],
) {
self.accounts_db
.write()
.unwrap()
.store_accounts(purge, txs, res, loaded)
}
pub fn increment_transaction_count(&self, tx_count: usize) {
self.accounts_db
.write()
.unwrap()
.increment_transaction_count(tx_count)
}
pub fn transaction_count(&self) -> u64 {
self.accounts_db.read().unwrap().transaction_count()
}
///// accounts starts with an empty data structure for every fork
///// self is root, merge the fork into self
//pub fn merge_into_root(&self, other: Self) {
// assert!(other.account_locks.lock().unwrap().is_empty());
// let db = other.accounts_db.into_inner().unwrap();
// let mut mydb = self.accounts_db.write().unwrap();
// mydb.merge(db)
//}
//pub fn copy_for_tpu(&self) -> Self {
// //TODO: deprecate this in favor of forks and merge_into_root
// let copy = Accounts::default();
// {
// let mut accounts_db = copy.accounts_db.write().unwrap();
// for (key, val) in self.accounts_db.read().unwrap().accounts.iter() {
// accounts_db.accounts.insert(key.clone(), val.clone());
// }
// accounts_db.transaction_count = self.transaction_count();
// }
// copy
//}
}
#[cfg(test)]
mod tests {
// TODO: all the bank tests are bank specific, issue: 2194
use super::*;
use solana_sdk::account::Account;
use solana_sdk::hash::Hash;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::KeypairUtil;
use solana_sdk::transaction::Instruction;
use solana_sdk::transaction::Transaction;
#[test]
fn test_purge() {
let mut db = AccountsDB::default();
let key = Pubkey::default();
let account = Account::new(0, 0, Pubkey::default());
// accounts are deleted when their token value is 0 and purge is true
db.store(false, &key, &account);
assert_eq!(AccountsDB::load(&[&db], &key), Some(account.clone()));
// purge should be set to true for the root checkpoint
db.store(true, &key, &account);
assert_eq!(AccountsDB::load(&[&db], &key), None);
}
fn load_accounts(
tx: Transaction,
ka: &Vec<(Pubkey, Account)>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
let accounts = Accounts::default();
for ka in ka.iter() {
accounts.store_slow(true, &ka.0, &ka.1);
}
Accounts::load_accounts(&[&accounts], &[tx], vec![Ok(())], error_counters)
}
#[test]
fn test_load_accounts_no_key() {
let accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let instructions = vec![Instruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_instructions::<Keypair>(
&[],
&[],
Hash::default(),
0,
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_no_account_0_exists() {
let accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let instructions = vec![Instruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_unknown_program_id() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let account = Account::new(2, 1, Pubkey::default());
accounts.push((key1, account));
let instructions = vec![Instruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![Pubkey::default()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_insufficient_funds() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let instructions = vec![Instruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
10,
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.insufficient_funds, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::InsufficientFundsForFee));
}
#[test]
fn test_load_accounts_no_loaders() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let account = Account::new(2, 1, Pubkey::default());
accounts.push((key1, account));
let instructions = vec![Instruction::new(0, &(), vec![0, 1])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[key1],
Hash::default(),
0,
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
Ok((a, l)) => {
assert_eq!(a.len(), 2);
assert_eq!(a[0], accounts[0].1);
assert_eq!(l.len(), 1);
assert_eq!(l[0].len(), 0);
}
Err(e) => Err(e).unwrap(),
}
}
#[test]
fn test_load_accounts_max_call_depth() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let key3 = Pubkey::new(&[7u8; 32]);
let key4 = Pubkey::new(&[8u8; 32]);
let key5 = Pubkey::new(&[9u8; 32]);
let key6 = Pubkey::new(&[10u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let mut account = Account::new(40, 1, Pubkey::default());
account.executable = true;
account.owner = native_loader::id();
accounts.push((key1, account));
let mut account = Account::new(41, 1, Pubkey::default());
account.executable = true;
account.owner = key1;
accounts.push((key2, account));
let mut account = Account::new(42, 1, Pubkey::default());
account.executable = true;
account.owner = key2;
accounts.push((key3, account));
let mut account = Account::new(43, 1, Pubkey::default());
account.executable = true;
account.owner = key3;
accounts.push((key4, account));
let mut account = Account::new(44, 1, Pubkey::default());
account.executable = true;
account.owner = key4;
accounts.push((key5, account));
let mut account = Account::new(45, 1, Pubkey::default());
account.executable = true;
account.owner = key5;
accounts.push((key6, account));
let instructions = vec![Instruction::new(0, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![key6],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.call_chain_too_deep, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::CallChainTooDeep));
}
#[test]
fn test_load_accounts_bad_program_id() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let mut account = Account::new(40, 1, Pubkey::default());
account.executable = true;
account.owner = Pubkey::default();
accounts.push((key1, account));
let instructions = vec![Instruction::new(0, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![key1],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_not_executable() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let mut account = Account::new(40, 1, Pubkey::default());
account.owner = native_loader::id();
accounts.push((key1, account));
let instructions = vec![Instruction::new(0, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![key1],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_multiple_loaders() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let key3 = Pubkey::new(&[7u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let mut account = Account::new(40, 1, Pubkey::default());
account.executable = true;
account.owner = native_loader::id();
accounts.push((key1, account));
let mut account = Account::new(41, 1, Pubkey::default());
account.executable = true;
account.owner = key1;
accounts.push((key2, account));
let mut account = Account::new(42, 1, Pubkey::default());
account.executable = true;
account.owner = key2;
accounts.push((key3, account));
let instructions = vec![
Instruction::new(0, &(), vec![0]),
Instruction::new(1, &(), vec![0]),
];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![key1, key2],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
Ok((a, l)) => {
assert_eq!(a.len(), 1);
assert_eq!(a[0], accounts[0].1);
assert_eq!(l.len(), 2);
assert_eq!(l[0].len(), 1);
assert_eq!(l[1].len(), 2);
for instruction_loaders in l.iter() {
for (i, a) in instruction_loaders.iter().enumerate() {
// +1 to skip first not loader account
assert_eq![a.1, accounts[i + 1].1];
}
}
}
Err(e) => Err(e).unwrap(),
}
}
#[test]
fn test_load_account_pay_to_self() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let account = Account::new(10, 1, Pubkey::default());
accounts.push((pubkey, account));
let instructions = vec![Instruction::new(0, &(), vec![0, 1])];
// Simulate pay-to-self transaction, which loads the same account twice
let tx = Transaction::new_with_instructions(
&[&keypair],
&[pubkey],
Hash::default(),
0,
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_loaded_twice, 1);
assert_eq!(loaded_accounts.len(), 1);
loaded_accounts[0].clone().unwrap_err();
assert_eq!(loaded_accounts[0], Err(BankError::AccountLoadedTwice));
}
}

1116
runtime/src/bank.rs Normal file

File diff suppressed because it is too large Load Diff

126
runtime/src/bloom.rs Normal file
View File

@ -0,0 +1,126 @@
//! Simple Bloom Filter
use bv::BitVec;
use fnv::FnvHasher;
use rand::{self, Rng};
use serde::{Deserialize, Serialize};
use std::cmp;
use std::hash::Hasher;
use std::marker::PhantomData;
/// Generate a stable hash of `self` for each `hash_index`
/// Best effort can be made for uniqueness of each hash.
pub trait BloomHashIndex {
fn hash_at_index(&self, hash_index: u64) -> u64;
}
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
pub struct Bloom<T: BloomHashIndex> {
pub keys: Vec<u64>,
pub bits: BitVec<u8>,
_phantom: PhantomData<T>,
}
impl<T: BloomHashIndex> Bloom<T> {
pub fn new(num_bits: usize, keys: Vec<u64>) -> Self {
let bits = BitVec::new_fill(false, num_bits as u64);
Bloom {
keys,
bits,
_phantom: PhantomData::default(),
}
}
/// create filter optimal for num size given the `false_rate`
/// the keys are randomized for picking data out of a collision resistant hash of size
/// `keysize` bytes
/// https://hur.st/bloomfilter/
pub fn random(num: usize, false_rate: f64, max_bits: usize) -> Self {
let min_num_bits = ((num as f64 * false_rate.log(2f64))
/ (1f64 / 2f64.powf(2f64.log(2f64))).log(2f64))
.ceil() as usize;
let num_bits = cmp::max(1, cmp::min(min_num_bits, max_bits));
let num_keys = ((num_bits as f64 / num as f64) * 2f64.log(2f64)).round() as usize;
let keys: Vec<u64> = (0..num_keys).map(|_| rand::thread_rng().gen()).collect();
Self::new(num_bits, keys)
}
fn pos(&self, key: &T, k: u64) -> u64 {
key.hash_at_index(k) % self.bits.len()
}
pub fn clear(&mut self) {
self.bits = BitVec::new_fill(false, self.bits.len());
}
pub fn add(&mut self, key: &T) {
for k in &self.keys {
let pos = self.pos(key, *k);
self.bits.set(pos, true);
}
}
pub fn contains(&self, key: &T) -> bool {
for k in &self.keys {
let pos = self.pos(key, *k);
if !self.bits.get(pos) {
return false;
}
}
true
}
}
fn slice_hash(slice: &[u8], hash_index: u64) -> u64 {
let mut hasher = FnvHasher::with_key(hash_index);
hasher.write(slice);
hasher.finish()
}
impl<T: AsRef<[u8]>> BloomHashIndex for T {
fn hash_at_index(&self, hash_index: u64) -> u64 {
slice_hash(self.as_ref(), hash_index)
}
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::hash::{hash, Hash};
#[test]
fn test_bloom_filter() {
//empty
let bloom: Bloom<Hash> = Bloom::random(0, 0.1, 100);
assert_eq!(bloom.keys.len(), 0);
assert_eq!(bloom.bits.len(), 1);
//normal
let bloom: Bloom<Hash> = Bloom::random(10, 0.1, 100);
assert_eq!(bloom.keys.len(), 3);
assert_eq!(bloom.bits.len(), 34);
//saturated
let bloom: Bloom<Hash> = Bloom::random(100, 0.1, 100);
assert_eq!(bloom.keys.len(), 1);
assert_eq!(bloom.bits.len(), 100);
}
#[test]
fn test_add_contains() {
let mut bloom: Bloom<Hash> = Bloom::random(100, 0.1, 100);
//known keys to avoid false positives in the test
bloom.keys = vec![0, 1, 2, 3];
let key = hash(b"hello");
assert!(!bloom.contains(&key));
bloom.add(&key);
assert!(bloom.contains(&key));
let key = hash(b"world");
assert!(!bloom.contains(&key));
bloom.add(&key);
assert!(bloom.contains(&key));
}
#[test]
fn test_random() {
let mut b1: Bloom<Hash> = Bloom::random(10, 0.1, 100);
let mut b2: Bloom<Hash> = Bloom::random(10, 0.1, 100);
b1.keys.sort();
b2.keys.sort();
assert_ne!(b1.keys, b2.keys);
}
}

View File

@ -0,0 +1,188 @@
use hashbrown::HashMap;
use solana_sdk::hash::Hash;
use solana_sdk::timing::{timestamp, MAX_ENTRY_IDS};
#[derive(Debug, PartialEq, Eq, Clone)]
struct LastIdEntry {
timestamp: u64,
tick_height: u64,
}
/// Low memory overhead, so can be cloned for every checkpoint
#[derive(Clone)]
pub struct LastIdQueue {
/// updated whenever an id is registered, at each tick ;)
pub tick_height: u64,
/// last tick to be registered
pub last_id: Option<Hash>,
entries: HashMap<Hash, LastIdEntry>,
}
impl Default for LastIdQueue {
fn default() -> Self {
Self {
entries: HashMap::new(),
tick_height: 0,
last_id: None,
}
}
}
impl LastIdQueue {
/// Check if the age of the entry_id is within the max_age
/// return false for any entries with an age equal to or above max_age
pub fn check_entry_id_age(&self, entry_id: Hash, max_age: usize) -> bool {
let entry = self.entries.get(&entry_id);
match entry {
Some(entry) => self.tick_height - entry.tick_height < max_age as u64,
_ => false,
}
}
/// check if entry is valid
#[cfg(test)]
pub fn check_entry(&self, entry_id: Hash) -> bool {
self.entries.get(&entry_id).is_some()
}
pub fn genesis_last_id(&mut self, last_id: &Hash) {
self.entries.insert(
*last_id,
LastIdEntry {
tick_height: 0,
timestamp: timestamp(),
},
);
self.last_id = Some(*last_id);
}
/// Tell the bank which Entry IDs exist on the ledger. This function
/// assumes subsequent calls correspond to later entries, and will boot
/// the oldest ones once its internal cache is full. Once boot, the
/// bank will reject transactions using that `last_id`.
pub fn register_tick(&mut self, last_id: &Hash) {
self.tick_height += 1;
let tick_height = self.tick_height;
// this clean up can be deferred until sigs gets larger
// because we verify entry.nth every place we check for validity
if self.entries.len() >= MAX_ENTRY_IDS as usize {
self.entries
.retain(|_, entry| tick_height - entry.tick_height <= MAX_ENTRY_IDS as u64);
}
self.entries.insert(
*last_id,
LastIdEntry {
tick_height,
timestamp: timestamp(),
},
);
self.last_id = Some(*last_id);
}
/// Looks through a list of tick heights and stakes, and finds the latest
/// tick that has achieved confirmation
pub fn get_confirmation_timestamp(
&self,
ticks_and_stakes: &mut [(u64, u64)],
supermajority_stake: u64,
) -> Option<u64> {
// Sort by tick height
ticks_and_stakes.sort_by(|a, b| a.0.cmp(&b.0));
let current_tick_height = self.tick_height;
let mut total = 0;
for (tick_height, stake) in ticks_and_stakes.iter() {
if current_tick_height >= *tick_height
&& ((current_tick_height - tick_height) as usize) < MAX_ENTRY_IDS
{
total += stake;
if total > supermajority_stake {
return self.tick_height_to_timestamp(*tick_height);
}
}
}
None
}
/// Maps a tick height to a timestamp
fn tick_height_to_timestamp(&self, tick_height: u64) -> Option<u64> {
for entry in self.entries.values() {
if entry.tick_height == tick_height {
return Some(entry.timestamp);
}
}
None
}
#[cfg(test)]
pub fn clear(&mut self) {
self.entries = HashMap::new();
self.tick_height = 0;
self.last_id = None;
}
/// fork for LastIdQueue is a simple clone
#[cfg(test)]
pub fn fork(&self) -> Self {
Self {
entries: self.entries.clone(),
tick_height: self.tick_height,
last_id: self.last_id,
}
}
/// merge for entryq is a swap
#[cfg(test)]
pub fn merge_into_root(&mut self, other: Self) {
let (entries, tick_height, last_id) = { (other.entries, other.tick_height, other.last_id) };
self.entries = entries;
self.tick_height = tick_height;
self.last_id = last_id;
}
}
#[cfg(test)]
mod tests {
use super::*;
use bincode::serialize;
use solana_sdk::hash::hash;
#[test]
fn test_register_tick() {
let last_id = Hash::default();
let mut entry_queue = LastIdQueue::default();
assert!(!entry_queue.check_entry(last_id));
entry_queue.register_tick(&last_id);
assert!(entry_queue.check_entry(last_id));
}
#[test]
fn test_reject_old_last_id() {
let last_id = Hash::default();
let mut entry_queue = LastIdQueue::default();
for i in 0..MAX_ENTRY_IDS {
let last_id = hash(&serialize(&i).unwrap()); // Unique hash
entry_queue.register_tick(&last_id);
}
// Assert we're no longer able to use the oldest entry ID.
assert!(!entry_queue.check_entry(last_id));
}
#[test]
fn test_fork() {
let last_id = Hash::default();
let mut first = LastIdQueue::default();
assert!(!first.check_entry(last_id));
first.register_tick(&last_id);
let second = first.fork();
assert!(second.check_entry(last_id));
}
#[test]
fn test_merge() {
let last_id = Hash::default();
let mut first = LastIdQueue::default();
assert!(!first.check_entry(last_id));
let mut second = first.fork();
second.register_tick(&last_id);
first.merge_into_root(second);
assert!(first.check_entry(last_id));
}
}

View File

@ -1,209 +1,9 @@
use solana_native_loader;
use solana_sdk::account::{create_keyed_accounts, Account, KeyedAccount};
use solana_sdk::native_program::ProgramError;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_program;
use solana_sdk::transaction::Transaction;
use solana_system_program;
mod accounts;
pub mod bank;
pub mod bloom;
mod last_id_queue;
pub mod runtime;
mod status_cache;
/// Reasons the runtime might have rejected a transaction.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RuntimeError {
/// Executing the instruction at the given index produced an error.
ProgramError(u8, ProgramError),
}
/// Process an instruction
/// This method calls the instruction's program entrypoint method
fn process_instruction(
tx: &Transaction,
instruction_index: usize,
executable_accounts: &mut [(Pubkey, Account)],
program_accounts: &mut [&mut Account],
tick_height: u64,
) -> Result<(), ProgramError> {
let program_id = tx.program_id(instruction_index);
let mut keyed_accounts = create_keyed_accounts(executable_accounts);
let mut keyed_accounts2: Vec<_> = tx.instructions[instruction_index]
.accounts
.iter()
.map(|&index| {
let index = index as usize;
let key = &tx.account_keys[index];
(key, index < tx.signatures.len())
})
.zip(program_accounts.iter_mut())
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
.collect();
keyed_accounts.append(&mut keyed_accounts2);
if system_program::check_id(&program_id) {
solana_system_program::entrypoint(
&program_id,
&mut keyed_accounts[1..],
&tx.instructions[instruction_index].userdata,
tick_height,
)
} else {
solana_native_loader::entrypoint(
&program_id,
&mut keyed_accounts,
&tx.instructions[instruction_index].userdata,
tick_height,
)
}
}
fn verify_instruction(
program_id: &Pubkey,
pre_program_id: &Pubkey,
pre_tokens: u64,
account: &Account,
) -> Result<(), ProgramError> {
// Verify the transaction
// Make sure that program_id is still the same or this was just assigned by the system program
if *pre_program_id != account.owner && !system_program::check_id(&program_id) {
return Err(ProgramError::ModifiedProgramId);
}
// For accounts unassigned to the program, the individual balance of each accounts cannot decrease.
if *program_id != account.owner && pre_tokens > account.tokens {
return Err(ProgramError::ExternalAccountTokenSpend);
}
Ok(())
}
/// Execute an instruction
/// This method calls the instruction's program entrypoint method and verifies that the result of
/// the call does not violate the bank's accounting rules.
/// The accounts are committed back to the bank only if this function returns Ok(_).
fn execute_instruction(
tx: &Transaction,
instruction_index: usize,
executable_accounts: &mut [(Pubkey, Account)],
program_accounts: &mut [&mut Account],
tick_height: u64,
) -> Result<(), ProgramError> {
let program_id = tx.program_id(instruction_index);
// TODO: the runtime should be checking read/write access to memory
// we are trusting the hard-coded programs not to clobber or allocate
let pre_total: u64 = program_accounts.iter().map(|a| a.tokens).sum();
let pre_data: Vec<_> = program_accounts
.iter_mut()
.map(|a| (a.owner, a.tokens))
.collect();
process_instruction(
tx,
instruction_index,
executable_accounts,
program_accounts,
tick_height,
)?;
// Verify the instruction
for ((pre_program_id, pre_tokens), post_account) in pre_data.iter().zip(program_accounts.iter())
{
verify_instruction(&program_id, pre_program_id, *pre_tokens, post_account)?;
}
// The total sum of all the tokens in all the accounts cannot change.
let post_total: u64 = program_accounts.iter().map(|a| a.tokens).sum();
if pre_total != post_total {
return Err(ProgramError::UnbalancedInstruction);
}
Ok(())
}
/// Return true if the slice has any duplicate elements
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
// `bench_has_duplicates` in benches/runtime.rs shows that this implementation is
// ~50 times faster than using HashSet for very short slices.
for i in 1..xs.len() {
if xs[i..].contains(&xs[i - 1]) {
return true;
}
}
false
}
/// Get mut references to a subset of elements.
fn get_subset_unchecked_mut<'a, T>(xs: &'a mut [T], indexes: &[u8]) -> Vec<&'a mut T> {
// Since the compiler doesn't know the indexes are unique, dereferencing
// multiple mut elements is assumed to be unsafe. If, however, all
// indexes are unique, it's perfectly safe. The returned elements will share
// the liftime of the input slice.
// Make certain there are no duplicate indexes. If there are, panic because we
// can't return multiple mut references to the same element.
if has_duplicates(indexes) {
panic!("duplicate indexes");
}
indexes
.iter()
.map(|i| {
let ptr = &mut xs[*i as usize] as *mut T;
unsafe { &mut *ptr }
})
.collect()
}
/// Execute a transaction.
/// This method calls each instruction in the transaction over the set of loaded Accounts
/// The accounts are committed back to the bank only if every instruction succeeds
pub fn execute_transaction(
tx: &Transaction,
loaders: &mut [Vec<(Pubkey, Account)>],
tx_accounts: &mut [Account],
tick_height: u64,
) -> Result<(), RuntimeError> {
for (instruction_index, instruction) in tx.instructions.iter().enumerate() {
let executable_accounts = &mut (&mut loaders[instruction.program_ids_index as usize]);
let mut program_accounts = get_subset_unchecked_mut(tx_accounts, &instruction.accounts);
execute_instruction(
tx,
instruction_index,
executable_accounts,
&mut program_accounts,
tick_height,
)
.map_err(|err| RuntimeError::ProgramError(instruction_index as u8, err))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_duplicates() {
assert!(!has_duplicates(&[1, 2]));
assert!(has_duplicates(&[1, 2, 1]));
}
#[test]
fn test_get_subset_unchecked_mut() {
assert_eq!(get_subset_unchecked_mut(&mut [7, 8], &[0]), vec![&mut 7]);
assert_eq!(
get_subset_unchecked_mut(&mut [7, 8], &[0, 1]),
vec![&mut 7, &mut 8]
);
}
#[test]
#[should_panic]
fn test_get_subset_unchecked_mut_duplicate_index() {
// This panics, because it assumes duplicate detection is done elsewhere.
get_subset_unchecked_mut(&mut [7, 8], &[0, 0]);
}
#[test]
#[should_panic]
fn test_get_subset_unchecked_mut_out_of_bounds() {
// This panics, because it assumes bounds validation is done elsewhere.
get_subset_unchecked_mut(&mut [7, 8], &[2]);
}
}
#[macro_use]
extern crate solana_metrics;

209
runtime/src/runtime.rs Normal file
View File

@ -0,0 +1,209 @@
use solana_native_loader;
use solana_sdk::account::{create_keyed_accounts, Account, KeyedAccount};
use solana_sdk::native_program::ProgramError;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::system_program;
use solana_sdk::transaction::Transaction;
use solana_system_program;
/// Reasons the runtime might have rejected a transaction.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RuntimeError {
/// Executing the instruction at the given index produced an error.
ProgramError(u8, ProgramError),
}
/// Process an instruction
/// This method calls the instruction's program entrypoint method
fn process_instruction(
tx: &Transaction,
instruction_index: usize,
executable_accounts: &mut [(Pubkey, Account)],
program_accounts: &mut [&mut Account],
tick_height: u64,
) -> Result<(), ProgramError> {
let program_id = tx.program_id(instruction_index);
let mut keyed_accounts = create_keyed_accounts(executable_accounts);
let mut keyed_accounts2: Vec<_> = tx.instructions[instruction_index]
.accounts
.iter()
.map(|&index| {
let index = index as usize;
let key = &tx.account_keys[index];
(key, index < tx.signatures.len())
})
.zip(program_accounts.iter_mut())
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
.collect();
keyed_accounts.append(&mut keyed_accounts2);
if system_program::check_id(&program_id) {
solana_system_program::entrypoint(
&program_id,
&mut keyed_accounts[1..],
&tx.instructions[instruction_index].userdata,
tick_height,
)
} else {
solana_native_loader::entrypoint(
&program_id,
&mut keyed_accounts,
&tx.instructions[instruction_index].userdata,
tick_height,
)
}
}
fn verify_instruction(
program_id: &Pubkey,
pre_program_id: &Pubkey,
pre_tokens: u64,
account: &Account,
) -> Result<(), ProgramError> {
// Verify the transaction
// Make sure that program_id is still the same or this was just assigned by the system program
if *pre_program_id != account.owner && !system_program::check_id(&program_id) {
return Err(ProgramError::ModifiedProgramId);
}
// For accounts unassigned to the program, the individual balance of each accounts cannot decrease.
if *program_id != account.owner && pre_tokens > account.tokens {
return Err(ProgramError::ExternalAccountTokenSpend);
}
Ok(())
}
/// Execute an instruction
/// This method calls the instruction's program entrypoint method and verifies that the result of
/// the call does not violate the bank's accounting rules.
/// The accounts are committed back to the bank only if this function returns Ok(_).
fn execute_instruction(
tx: &Transaction,
instruction_index: usize,
executable_accounts: &mut [(Pubkey, Account)],
program_accounts: &mut [&mut Account],
tick_height: u64,
) -> Result<(), ProgramError> {
let program_id = tx.program_id(instruction_index);
// TODO: the runtime should be checking read/write access to memory
// we are trusting the hard-coded programs not to clobber or allocate
let pre_total: u64 = program_accounts.iter().map(|a| a.tokens).sum();
let pre_data: Vec<_> = program_accounts
.iter_mut()
.map(|a| (a.owner, a.tokens))
.collect();
process_instruction(
tx,
instruction_index,
executable_accounts,
program_accounts,
tick_height,
)?;
// Verify the instruction
for ((pre_program_id, pre_tokens), post_account) in pre_data.iter().zip(program_accounts.iter())
{
verify_instruction(&program_id, pre_program_id, *pre_tokens, post_account)?;
}
// The total sum of all the tokens in all the accounts cannot change.
let post_total: u64 = program_accounts.iter().map(|a| a.tokens).sum();
if pre_total != post_total {
return Err(ProgramError::UnbalancedInstruction);
}
Ok(())
}
/// Return true if the slice has any duplicate elements
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
// `bench_has_duplicates` in benches/runtime.rs shows that this implementation is
// ~50 times faster than using HashSet for very short slices.
for i in 1..xs.len() {
if xs[i..].contains(&xs[i - 1]) {
return true;
}
}
false
}
/// Get mut references to a subset of elements.
fn get_subset_unchecked_mut<'a, T>(xs: &'a mut [T], indexes: &[u8]) -> Vec<&'a mut T> {
// Since the compiler doesn't know the indexes are unique, dereferencing
// multiple mut elements is assumed to be unsafe. If, however, all
// indexes are unique, it's perfectly safe. The returned elements will share
// the liftime of the input slice.
// Make certain there are no duplicate indexes. If there are, panic because we
// can't return multiple mut references to the same element.
if has_duplicates(indexes) {
panic!("duplicate indexes");
}
indexes
.iter()
.map(|i| {
let ptr = &mut xs[*i as usize] as *mut T;
unsafe { &mut *ptr }
})
.collect()
}
/// Execute a transaction.
/// This method calls each instruction in the transaction over the set of loaded Accounts
/// The accounts are committed back to the bank only if every instruction succeeds
pub fn execute_transaction(
tx: &Transaction,
loaders: &mut [Vec<(Pubkey, Account)>],
tx_accounts: &mut [Account],
tick_height: u64,
) -> Result<(), RuntimeError> {
for (instruction_index, instruction) in tx.instructions.iter().enumerate() {
let executable_accounts = &mut (&mut loaders[instruction.program_ids_index as usize]);
let mut program_accounts = get_subset_unchecked_mut(tx_accounts, &instruction.accounts);
execute_instruction(
tx,
instruction_index,
executable_accounts,
&mut program_accounts,
tick_height,
)
.map_err(|err| RuntimeError::ProgramError(instruction_index as u8, err))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_duplicates() {
assert!(!has_duplicates(&[1, 2]));
assert!(has_duplicates(&[1, 2, 1]));
}
#[test]
fn test_get_subset_unchecked_mut() {
assert_eq!(get_subset_unchecked_mut(&mut [7, 8], &[0]), vec![&mut 7]);
assert_eq!(
get_subset_unchecked_mut(&mut [7, 8], &[0, 1]),
vec![&mut 7, &mut 8]
);
}
#[test]
#[should_panic]
fn test_get_subset_unchecked_mut_duplicate_index() {
// This panics, because it assumes duplicate detection is done elsewhere.
get_subset_unchecked_mut(&mut [7, 8], &[0, 0]);
}
#[test]
#[should_panic]
fn test_get_subset_unchecked_mut_out_of_bounds() {
// This panics, because it assumes bounds validation is done elsewhere.
get_subset_unchecked_mut(&mut [7, 8], &[2]);
}
}

288
runtime/src/status_cache.rs Normal file
View File

@ -0,0 +1,288 @@
use crate::bloom::{Bloom, BloomHashIndex};
use hashbrown::HashMap;
use solana_sdk::hash::Hash;
use solana_sdk::signature::Signature;
use solana_sdk::timing::{MAX_ENTRY_IDS, NUM_TICKS_PER_SECOND};
use std::collections::VecDeque;
use std::ops::Deref;
#[cfg(test)]
use std::ops::DerefMut;
/// This cache is designed to last 1 second
const MAX_CACHE_ENTRIES: usize = MAX_ENTRY_IDS / NUM_TICKS_PER_SECOND;
type FailureMap<T> = HashMap<Signature, T>;
#[derive(Clone)]
pub struct StatusCache<T> {
/// all signatures seen at this checkpoint
signatures: Bloom<Signature>,
/// failures
failures: FailureMap<T>,
/// Merges are empty unless this is the root checkpoint which cannot be unrolled
merges: VecDeque<StatusCache<T>>,
}
impl<T: Clone> Default for StatusCache<T> {
fn default() -> Self {
Self::new(&Hash::default())
}
}
impl<T: Clone> StatusCache<T> {
pub fn new(last_id: &Hash) -> Self {
let keys = (0..27).map(|i| last_id.hash_at_index(i)).collect();
Self {
signatures: Bloom::new(38_340_234, keys),
failures: HashMap::new(),
merges: VecDeque::new(),
}
}
fn has_signature_merged(&self, sig: &Signature) -> bool {
for c in &self.merges {
if c.has_signature(sig) {
return true;
}
}
false
}
/// test if a signature is known
pub fn has_signature(&self, sig: &Signature) -> bool {
self.signatures.contains(&sig) || self.has_signature_merged(sig)
}
/// add a signature
pub fn add(&mut self, sig: &Signature) {
self.signatures.add(&sig)
}
/// Save an error status for a signature
pub fn save_failure_status(&mut self, sig: &Signature, err: T) {
assert!(self.has_signature(sig), "sig not found");
self.failures.insert(*sig, err);
}
/// Forget all signatures. Useful for benchmarking.
pub fn clear(&mut self) {
self.failures.clear();
self.signatures.clear();
self.merges = VecDeque::new();
}
fn get_signature_status_merged(&self, sig: &Signature) -> Option<Result<(), T>> {
for c in &self.merges {
if c.has_signature(sig) {
return c.get_signature_status(sig);
}
}
None
}
pub fn get_signature_status(&self, sig: &Signature) -> Option<Result<(), T>> {
if let Some(res) = self.failures.get(sig) {
return Some(Err(res.clone()));
} else if self.signatures.contains(sig) {
return Some(Ok(()));
}
self.get_signature_status_merged(sig)
}
/// like accounts, status cache starts with an new data structure for every checkpoint
/// so only merge is implemented
/// but the merges maintains a history
#[cfg(test)]
pub fn merge_into_root(&mut self, other: Self) {
// merges should be empty for every other checkpoint accept the root
// which cannot be rolled back
assert!(other.merges.is_empty());
self.merges.push_front(other);
if self.merges.len() > MAX_CACHE_ENTRIES {
self.merges.pop_back();
}
}
/// Crate a new cache, pushing the old cache into the merged queue
pub fn new_cache(&mut self, last_id: &Hash) {
let mut old = Self::new(last_id);
std::mem::swap(&mut old.signatures, &mut self.signatures);
std::mem::swap(&mut old.failures, &mut self.failures);
assert!(old.merges.is_empty());
self.merges.push_front(old);
if self.merges.len() > MAX_CACHE_ENTRIES {
self.merges.pop_back();
}
}
pub fn get_signature_status_all<U>(
checkpoints: &[U],
signature: &Signature,
) -> Option<Result<(), T>>
where
U: Deref<Target = Self>,
{
for c in checkpoints {
if let Some(status) = c.get_signature_status(signature) {
return Some(status);
}
}
None
}
pub fn has_signature_all<U>(checkpoints: &[U], signature: &Signature) -> bool
where
U: Deref<Target = Self>,
{
for c in checkpoints {
if c.has_signature(signature) {
return true;
}
}
false
}
#[cfg(test)]
pub fn clear_all<U>(checkpoints: &mut [U]) -> bool
where
U: DerefMut<Target = Self>,
{
for c in checkpoints.iter_mut() {
c.clear();
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bank::BankError;
use solana_sdk::hash::hash;
type BankStatusCache = StatusCache<BankError>;
#[test]
fn test_has_signature() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut status_cache = BankStatusCache::new(&last_id);
assert_eq!(status_cache.has_signature(&sig), false);
assert_eq!(status_cache.get_signature_status(&sig), None,);
status_cache.add(&sig);
assert_eq!(status_cache.has_signature(&sig), true);
assert_eq!(status_cache.get_signature_status(&sig), Some(Ok(())),);
}
#[test]
fn test_has_signature_checkpoint() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = BankStatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
let last_id = hash(last_id.as_ref());
let second = StatusCache::new(&last_id);
let checkpoints = [&second, &first];
assert_eq!(
BankStatusCache::get_signature_status_all(&checkpoints, &sig),
Some(Ok(())),
);
assert!(StatusCache::has_signature_all(&checkpoints, &sig));
}
#[test]
fn test_new_cache() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = BankStatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
let last_id = hash(last_id.as_ref());
first.new_cache(&last_id);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())),);
assert!(first.has_signature(&sig));
first.clear();
assert_eq!(first.get_signature_status(&sig), None);
assert!(!first.has_signature(&sig));
}
#[test]
fn test_new_cache_full() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = BankStatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
for _ in 0..(MAX_CACHE_ENTRIES + 1) {
let last_id = hash(last_id.as_ref());
first.new_cache(&last_id);
}
assert_eq!(first.get_signature_status(&sig), None);
assert!(!first.has_signature(&sig));
}
#[test]
fn test_has_signature_merged1() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = BankStatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
let last_id = hash(last_id.as_ref());
let second = BankStatusCache::new(&last_id);
first.merge_into_root(second);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())),);
assert!(first.has_signature(&sig));
}
#[test]
fn test_has_signature_merged2() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = BankStatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
let last_id = hash(last_id.as_ref());
let mut second = BankStatusCache::new(&last_id);
second.merge_into_root(first);
assert_eq!(second.get_signature_status(&sig), Some(Ok(())),);
assert!(second.has_signature(&sig));
}
#[test]
fn test_failure_status() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = StatusCache::new(&last_id);
first.add(&sig);
first.save_failure_status(&sig, BankError::DuplicateSignature);
assert_eq!(first.has_signature(&sig), true);
assert_eq!(
first.get_signature_status(&sig),
Some(Err(BankError::DuplicateSignature)),
);
}
#[test]
fn test_clear_signatures() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = StatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.has_signature(&sig), true);
first.save_failure_status(&sig, BankError::DuplicateSignature);
assert_eq!(
first.get_signature_status(&sig),
Some(Err(BankError::DuplicateSignature)),
);
first.clear();
assert_eq!(first.has_signature(&sig), false);
assert_eq!(first.get_signature_status(&sig), None,);
}
#[test]
fn test_clear_signatures_all() {
let sig = Signature::default();
let last_id = hash(Hash::default().as_ref());
let mut first = StatusCache::new(&last_id);
first.add(&sig);
assert_eq!(first.has_signature(&sig), true);
let mut second = StatusCache::new(&last_id);
let mut checkpoints = [&mut second, &mut first];
BankStatusCache::clear_all(&mut checkpoints);
assert_eq!(
BankStatusCache::has_signature_all(&checkpoints, &sig),
false
);
}
}