Add frozen account support (#8989)

automerge
This commit is contained in:
Michael Vines
2020-03-22 11:10:04 -07:00
committed by GitHub
parent 4dd0367136
commit 88ba8439fc
15 changed files with 479 additions and 70 deletions

View File

@ -15,6 +15,7 @@ byteorder = "1.3.4"
fnv = "1.0.6"
fs_extra = "1.1.0"
itertools = "0.9.0"
lazy_static = "1.4.0"
libc = "0.2.68"
libloading = "0.5.2"
log = "0.4.8"

View File

@ -32,7 +32,7 @@ fn bench_has_duplicates(bencher: &mut Bencher) {
#[bench]
fn test_accounts_create(bencher: &mut Bencher) {
let (genesis_config, _) = create_genesis_config(10_000);
let bank0 = Bank::new_with_paths(&genesis_config, vec![PathBuf::from("bench_a0")]);
let bank0 = Bank::new_with_paths(&genesis_config, vec![PathBuf::from("bench_a0")], &[]);
bencher.iter(|| {
let mut pubkeys: Vec<Pubkey> = vec![];
deposit_many(&bank0, &mut pubkeys, 1000);
@ -46,6 +46,7 @@ fn test_accounts_squash(bencher: &mut Bencher) {
banks.push(Arc::new(Bank::new_with_paths(
&genesis_config,
vec![PathBuf::from("bench_a1")],
&[],
)));
let mut pubkeys: Vec<Pubkey> = vec![];
deposit_many(&banks[0], &mut pubkeys, 250000);

View File

@ -60,15 +60,9 @@ pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, Trans
impl Accounts {
pub fn new(paths: Vec<PathBuf>) -> Self {
let accounts_db = Arc::new(AccountsDB::new(paths));
Accounts {
slot: 0,
accounts_db,
account_locks: Mutex::new(HashSet::new()),
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
}
Self::new_with_frozen_accounts(paths, &HashMap::default(), &[])
}
pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
let accounts_db = parent.accounts_db.clone();
accounts_db.set_hash(slot, parent_slot);
@ -80,13 +74,48 @@ impl Accounts {
}
}
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
&self,
pub fn new_with_frozen_accounts(
paths: Vec<PathBuf>,
ancestors: &HashMap<Slot, usize>,
frozen_account_pubkeys: &[Pubkey],
) -> Self {
let mut accounts = Accounts {
slot: 0,
accounts_db: Arc::new(AccountsDB::new(paths)),
account_locks: Mutex::new(HashSet::new()),
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
};
accounts.freeze_accounts(ancestors, frozen_account_pubkeys);
accounts
}
pub fn freeze_accounts(
&mut self,
ancestors: &HashMap<Slot, usize>,
frozen_account_pubkeys: &[Pubkey],
) {
Arc::get_mut(&mut self.accounts_db)
.unwrap()
.freeze_accounts(ancestors, frozen_account_pubkeys);
}
pub fn from_stream<R: Read, P: AsRef<Path>>(
account_paths: &[PathBuf],
ancestors: &HashMap<Slot, usize>,
frozen_account_pubkeys: &[Pubkey],
stream: &mut BufReader<R>,
append_vecs_path: P,
) -> std::result::Result<(), IOError> {
self.accounts_db
.accounts_from_stream(stream, append_vecs_path)
stream_append_vecs_path: P,
) -> std::result::Result<Self, IOError> {
let mut accounts_db = AccountsDB::new(account_paths.to_vec());
accounts_db.accounts_from_stream(stream, stream_append_vecs_path)?;
accounts_db.freeze_accounts(ancestors, frozen_account_pubkeys);
Ok(Accounts {
slot: 0,
accounts_db: Arc::new(accounts_db),
account_locks: Mutex::new(HashSet::new()),
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
})
}
/// Return true if the slice has any duplicate elements
@ -1401,10 +1430,14 @@ mod tests {
let buf = writer.into_inner();
let mut reader = BufReader::new(&buf[..]);
let (_accounts_dir, daccounts_paths) = get_temp_accounts_paths(2).unwrap();
let daccounts = Accounts::new(daccounts_paths.clone());
assert!(daccounts
.accounts_from_stream(&mut reader, copied_accounts.path())
.is_ok());
let daccounts = Accounts::from_stream(
&daccounts_paths,
&HashMap::default(),
&[],
&mut reader,
copied_accounts.path(),
)
.unwrap();
check_accounts(&daccounts, &pubkeys, 100);
assert_eq!(accounts.bank_hash_at(0), daccounts.bank_hash_at(0));
}

View File

@ -26,6 +26,7 @@ use crate::{
use bincode::{deserialize_from, serialize_into};
use byteorder::{ByteOrder, LittleEndian};
use fs_extra::dir::CopyOptions;
use lazy_static::lazy_static;
use log::*;
use rand::{thread_rng, Rng};
use rayon::{prelude::*, ThreadPool};
@ -56,6 +57,12 @@ pub const DEFAULT_FILE_SIZE: u64 = 4 * 1024 * 1024;
pub const DEFAULT_NUM_THREADS: u32 = 8;
pub const DEFAULT_NUM_DIRS: u32 = 4;
lazy_static! {
// FROZEN_ACCOUNT_PANIC is used to signal local_cluster that an AccountsDB panic has occurred,
// as |cargo test| cannot observe panics in other threads
pub static ref FROZEN_ACCOUNT_PANIC: Arc<AtomicBool> = { Arc::new(AtomicBool::new(false)) };
}
#[derive(Debug, Default)]
pub struct ErrorCounters {
pub total: usize,
@ -426,6 +433,12 @@ pub struct BankHashInfo {
pub stats: BankHashStats,
}
#[derive(Debug)]
struct FrozenAccountInfo {
pub hash: Hash, // Hash generated by hash_frozen_account_data()
pub lamports: u64, // Account balance cannot be lower than this amount
}
// This structure handles the load/store of the accounts
#[derive(Debug)]
pub struct AccountsDB {
@ -448,6 +461,9 @@ pub struct AccountsDB {
/// Starting file size of appendvecs
file_size: u64,
/// Accounts that will cause a panic! if data modified or lamports decrease
frozen_accounts: HashMap<Pubkey, FrozenAccountInfo>,
/// Thread pool used for par_iter
pub thread_pool: ThreadPool,
@ -478,6 +494,7 @@ impl Default for AccountsDB {
.unwrap(),
min_num_stores: num_threads,
bank_hashes: RwLock::new(bank_hashes),
frozen_accounts: HashMap::new(),
}
}
}
@ -491,7 +508,7 @@ impl AccountsDB {
..Self::default()
}
} else {
// Create a temprorary set of accounts directories, used primarily
// Create a temporary set of accounts directories, used primarily
// for testing
let (temp_dirs, paths) = get_temp_accounts_paths(DEFAULT_NUM_DIRS).unwrap();
Self {
@ -526,7 +543,7 @@ impl AccountsDB {
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
&self,
mut stream: &mut BufReader<R>,
append_vecs_path: P,
stream_append_vecs_path: P,
) -> Result<(), IOError> {
let _len: usize =
deserialize_from(&mut stream).map_err(|e| AccountsDB::get_io_error(&e.to_string()))?;
@ -549,8 +566,9 @@ impl AccountsDB {
// at by `local_dir`
let append_vec_relative_path =
AppendVec::new_relative_path(slot_id, storage_entry.id);
let append_vec_abs_path =
append_vecs_path.as_ref().join(&append_vec_relative_path);
let append_vec_abs_path = stream_append_vecs_path
.as_ref()
.join(&append_vec_relative_path);
let target = local_dir.join(append_vec_abs_path.file_name().unwrap());
if std::fs::rename(append_vec_abs_path.clone(), target).is_err() {
let mut copy_options = CopyOptions::new();
@ -976,6 +994,21 @@ impl AccountsDB {
)
}
fn hash_frozen_account_data(account: &Account) -> Hash {
let mut hasher = Hasher::default();
hasher.hash(&account.data);
hasher.hash(&account.owner.as_ref());
if account.executable {
hasher.hash(&[1u8; 1]);
} else {
hasher.hash(&[0u8; 1]);
}
hasher.result()
}
pub fn hash_account_data(
slot: Slot,
lamports: u64,
@ -1387,8 +1420,62 @@ impl AccountsDB {
hashes
}
pub fn freeze_accounts(
&mut self,
ancestors: &HashMap<Slot, usize>,
account_pubkeys: &[Pubkey],
) {
for account_pubkey in account_pubkeys {
if let Some((account, _slot)) = self.load_slow(ancestors, &account_pubkey) {
let frozen_account_info = FrozenAccountInfo {
hash: Self::hash_frozen_account_data(&account),
lamports: account.lamports,
};
warn!(
"Account {} is now frozen at lamports={}, hash={}",
account_pubkey, frozen_account_info.lamports, frozen_account_info.hash
);
self.frozen_accounts
.insert(*account_pubkey, frozen_account_info);
} else {
panic!(
"Unable to freeze an account that does not exist: {}",
account_pubkey
);
}
}
}
/// Cause a panic if frozen accounts would be affected by data in `accounts`
fn assert_frozen_accounts(&self, accounts: &[(&Pubkey, &Account)]) {
if self.frozen_accounts.is_empty() {
return;
}
for (account_pubkey, account) in accounts.iter() {
if let Some(frozen_account_info) = self.frozen_accounts.get(*account_pubkey) {
if account.lamports < frozen_account_info.lamports {
FROZEN_ACCOUNT_PANIC.store(true, Ordering::Relaxed);
panic!(
"Frozen account {} modified. Lamports decreased from {} to {}",
account_pubkey, frozen_account_info.lamports, account.lamports,
)
}
let hash = Self::hash_frozen_account_data(&account);
if hash != frozen_account_info.hash {
FROZEN_ACCOUNT_PANIC.store(true, Ordering::Relaxed);
panic!(
"Frozen account {} modified. Hash changed from {} to {}",
account_pubkey, frozen_account_info.hash, hash,
)
}
}
}
}
/// Store the account update.
pub fn store(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) {
self.assert_frozen_accounts(accounts);
let hashes = self.hash_accounts(slot_id, accounts);
self.store_with_hashes(slot_id, accounts, &hashes);
}
@ -2712,6 +2799,139 @@ pub mod tests {
Ok(())
}
#[test]
fn test_hash_frozen_account_data() {
let account = Account::new(1, 42, &Pubkey::default());
let hash = AccountsDB::hash_frozen_account_data(&account);
assert_ne!(hash, Hash::default()); // Better not be the default Hash
// Lamports changes to not affect the hash
let mut account_modified = account.clone();
account_modified.lamports -= 1;
assert_eq!(
hash,
AccountsDB::hash_frozen_account_data(&account_modified)
);
// Rent epoch may changes to not affect the hash
let mut account_modified = account.clone();
account_modified.rent_epoch += 1;
assert_eq!(
hash,
AccountsDB::hash_frozen_account_data(&account_modified)
);
// Account data may not be modified
let mut account_modified = account.clone();
account_modified.data[0] = 42;
assert_ne!(
hash,
AccountsDB::hash_frozen_account_data(&account_modified)
);
// Owner may not be modified
let mut account_modified = account.clone();
account_modified.owner =
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
assert_ne!(
hash,
AccountsDB::hash_frozen_account_data(&account_modified)
);
// Executable may not be modified
let mut account_modified = account.clone();
account_modified.executable = true;
assert_ne!(
hash,
AccountsDB::hash_frozen_account_data(&account_modified)
);
}
#[test]
fn test_frozen_account_lamport_increase() {
let frozen_pubkey =
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
let mut db = AccountsDB::new(Vec::new());
let mut account = Account::new(1, 42, &frozen_pubkey);
db.store(0, &[(&frozen_pubkey, &account)]);
let ancestors = vec![(0, 0)].into_iter().collect();
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
// Store with no account changes is ok
db.store(0, &[(&frozen_pubkey, &account)]);
// Store with an increase in lamports is ok
account.lamports = 2;
db.store(0, &[(&frozen_pubkey, &account)]);
// Store with an decrease that does not go below the frozen amount of lamports is tolerated
account.lamports = 1;
db.store(0, &[(&frozen_pubkey, &account)]);
// A store of any value over the frozen value of '1' across different slots is also ok
account.lamports = 3;
db.store(1, &[(&frozen_pubkey, &account)]);
account.lamports = 2;
db.store(2, &[(&frozen_pubkey, &account)]);
account.lamports = 1;
db.store(3, &[(&frozen_pubkey, &account)]);
}
#[test]
#[should_panic(
expected = "Frozen account My11111111111111111111111111111111111111111 modified. Lamports decreased from 1 to 0"
)]
fn test_frozen_account_lamport_decrease() {
let frozen_pubkey =
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
let mut db = AccountsDB::new(Vec::new());
let mut account = Account::new(1, 42, &frozen_pubkey);
db.store(0, &[(&frozen_pubkey, &account)]);
let ancestors = vec![(0, 0)].into_iter().collect();
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
// Store with a decrease below the frozen amount of lamports is not ok
account.lamports -= 1;
db.store(0, &[(&frozen_pubkey, &account)]);
}
#[test]
#[should_panic(
expected = "Unable to freeze an account that does not exist: My11111111111111111111111111111111111111111"
)]
fn test_frozen_account_nonexistent() {
let frozen_pubkey =
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
let mut db = AccountsDB::new(Vec::new());
let ancestors = vec![(0, 0)].into_iter().collect();
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
}
#[test]
#[should_panic(
expected = "Frozen account My11111111111111111111111111111111111111111 modified. Hash changed from 8wHcxDkjiwdrkPAsDnmNrF1UDGJFAtZzPQBSVweY3yRA to JdscGYB1uczVssmYuJusDD1Bfe6wpNeeho8XjcH8inN"
)]
fn test_frozen_account_data_modified() {
let frozen_pubkey =
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
let mut db = AccountsDB::new(Vec::new());
let mut account = Account::new(1, 42, &frozen_pubkey);
db.store(0, &[(&frozen_pubkey, &account)]);
let ancestors = vec![(0, 0)].into_iter().collect();
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
account.data[0] = 42;
db.store(0, &[(&frozen_pubkey, &account)]);
}
#[test]
fn test_hash_stored_account() {
// This test uses some UNSAFE trick to detect most of account's field

View File

@ -4,9 +4,7 @@
//! already been signed and verified.
use crate::{
accounts::{Accounts, TransactionAccounts, TransactionLoadResult, TransactionLoaders},
accounts_db::{
AccountsDBSerialize, AppendVecId, ErrorCounters, SnapshotStorage, SnapshotStorages,
},
accounts_db::{AccountsDBSerialize, ErrorCounters, SnapshotStorage, SnapshotStorages},
blockhash_queue::BlockhashQueue,
message_processor::{MessageProcessor, ProcessInstruction},
nonce_utils,
@ -85,30 +83,30 @@ pub struct BankRc {
}
impl BankRc {
pub fn new(account_paths: Vec<PathBuf>, id: AppendVecId, slot: Slot) -> Self {
let accounts = Accounts::new(account_paths);
accounts
.accounts_db
.next_id
.store(id as usize, Ordering::Relaxed);
BankRc {
pub fn from_stream<R: Read, P: AsRef<Path>>(
account_paths: &[PathBuf],
slot: Slot,
ancestors: &HashMap<Slot, usize>,
frozen_account_pubkeys: &[Pubkey],
mut stream: &mut BufReader<R>,
stream_append_vecs_path: P,
) -> std::result::Result<Self, IOError> {
let _len: usize =
deserialize_from(&mut stream).map_err(|e| BankRc::get_io_error(&e.to_string()))?;
let accounts = Accounts::from_stream(
account_paths,
ancestors,
frozen_account_pubkeys,
stream,
stream_append_vecs_path,
)?;
Ok(BankRc {
accounts: Arc::new(accounts),
parent: RwLock::new(None),
slot,
}
}
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
&self,
mut stream: &mut BufReader<R>,
append_vecs_path: P,
) -> std::result::Result<(), IOError> {
let _len: usize =
deserialize_from(&mut stream).map_err(|e| BankRc::get_io_error(&e.to_string()))?;
self.accounts
.accounts_from_stream(stream, append_vecs_path)?;
Ok(())
})
}
pub fn get_snapshot_storages(&self, slot: Slot) -> SnapshotStorages {
@ -358,14 +356,25 @@ impl Default for BlockhashQueue {
impl Bank {
pub fn new(genesis_config: &GenesisConfig) -> Self {
Self::new_with_paths(&genesis_config, Vec::new())
Self::new_with_paths(&genesis_config, Vec::new(), &[])
}
pub fn new_with_paths(genesis_config: &GenesisConfig, paths: Vec<PathBuf>) -> Self {
pub fn new_with_paths(
genesis_config: &GenesisConfig,
paths: Vec<PathBuf>,
frozen_account_pubkeys: &[Pubkey],
) -> Self {
let mut bank = Self::default();
bank.ancestors.insert(bank.slot(), 0);
bank.rc.accounts = Arc::new(Accounts::new(paths));
bank.process_genesis_config(genesis_config);
// Freeze accounts after process_genesis_config creates the initial append vecs
Arc::get_mut(&mut bank.rc.accounts)
.unwrap()
.freeze_accounts(&bank.ancestors, frozen_account_pubkeys);
// genesis needs stakes for all epochs up to the epoch implied by
// slot = 0 and genesis configuration
{
@ -4769,14 +4778,21 @@ mod tests {
let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
let ref_sc = StatusCacheRc::default();
ref_sc.status_cache.write().unwrap().add_root(2);
dbank.set_bank_rc(BankRc::new(dbank_paths.clone(), 0, dbank.slot()), ref_sc);
// Create a directory to simulate AppendVecs unpackaged from a snapshot tar
let copied_accounts = TempDir::new().unwrap();
copy_append_vecs(&bank2.rc.accounts.accounts_db, copied_accounts.path()).unwrap();
dbank
.rc
.accounts_from_stream(&mut reader, copied_accounts.path())
.unwrap();
dbank.set_bank_rc(
BankRc::from_stream(
&dbank_paths,
dbank.slot(),
&dbank.ancestors,
&[],
&mut reader,
copied_accounts.path(),
)
.unwrap(),
ref_sc,
);
assert_eq!(dbank.get_balance(&key1.pubkey()), 0);
assert_eq!(dbank.get_balance(&key2.pubkey()), 10);
assert_eq!(dbank.get_balance(&key3.pubkey()), 0);

View File

@ -110,7 +110,7 @@ impl PreAccount {
return Err(InstructionError::ExecutableModified);
}
// No one modifies r ent_epoch (yet).
// No one modifies rent_epoch (yet).
if self.rent_epoch != post.rent_epoch {
return Err(InstructionError::RentEpochModified);
}