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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 479 additions and 70 deletions

1
Cargo.lock generated
View File

@ -4488,6 +4488,7 @@ dependencies = [
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -78,6 +78,7 @@ pub struct ValidatorConfig {
pub trusted_validators: Option<HashSet<Pubkey>>, // None = trust all
pub halt_on_trusted_validators_accounts_hash_mismatch: bool,
pub accounts_hash_fault_injection_slots: u64, // 0 = no fault injection
pub frozen_accounts: Vec<Pubkey>,
}
impl Default for ValidatorConfig {
@ -102,6 +103,7 @@ impl Default for ValidatorConfig {
trusted_validators: None,
halt_on_trusted_validators_accounts_hash_mismatch: false,
accounts_hash_fault_injection_slots: 0,
frozen_accounts: vec![],
}
}
}
@ -594,6 +596,7 @@ fn new_banks_from_blockstore(
poh_verify,
dev_halt_at_slot: config.dev_halt_at_slot,
new_hard_forks: config.new_hard_forks.clone(),
frozen_accounts: config.frozen_accounts.clone(),
..blockstore_processor::ProcessOptions::default()
};

View File

@ -45,6 +45,7 @@ mod tests {
let bank0 = Bank::new_with_paths(
&genesis_config_info.genesis_config,
vec![accounts_dir.path().to_path_buf()],
&[],
);
bank0.freeze();
let mut bank_forks = BankForks::new(0, bank0);
@ -80,6 +81,7 @@ mod tests {
let deserialized_bank = snapshot_utils::bank_from_archive(
&account_paths,
&[],
&old_bank_forks
.snapshot_config
.as_ref()

View File

@ -66,6 +66,7 @@ pub fn load(
let deserialized_bank = snapshot_utils::bank_from_archive(
&account_paths,
&process_options.frozen_accounts,
&snapshot_config.snapshot_path,
&archive_filename,
)

View File

@ -23,6 +23,7 @@ use solana_sdk::{
clock::{Slot, MAX_RECENT_BLOCKHASHES},
genesis_config::GenesisConfig,
hash::Hash,
pubkey::Pubkey,
signature::Keypair,
timing::duration_as_ms,
transaction::{Result, Transaction, TransactionError},
@ -271,6 +272,7 @@ pub struct ProcessOptions {
pub entry_callback: Option<ProcessCallback>,
pub override_num_threads: Option<usize>,
pub new_hard_forks: Option<Vec<Slot>>,
pub frozen_accounts: Vec<Pubkey>,
}
pub fn process_blockstore(
@ -289,7 +291,11 @@ pub fn process_blockstore(
}
// Setup bank for slot 0
let bank0 = Arc::new(Bank::new_with_paths(&genesis_config, account_paths));
let bank0 = Arc::new(Bank::new_with_paths(
&genesis_config,
account_paths,
&opts.frozen_accounts,
));
info!("processing ledger for slot 0...");
let recyclers = VerifyRecyclers::default();
process_bank_0(&bank0, blockstore, &opts, &recyclers)?;
@ -2611,7 +2617,7 @@ pub mod tests {
genesis_config: &GenesisConfig,
account_paths: Vec<PathBuf>,
) -> EpochSchedule {
let bank = Bank::new_with_paths(&genesis_config, account_paths);
let bank = Bank::new_with_paths(&genesis_config, account_paths, &[]);
bank.epoch_schedule().clone()
}

View File

@ -12,7 +12,7 @@ use solana_runtime::{
MAX_SNAPSHOT_DATA_FILE_SIZE,
},
};
use solana_sdk::{clock::Slot, hash::Hash};
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
use std::{
cmp::Ordering,
env,
@ -432,6 +432,7 @@ pub fn remove_snapshot<P: AsRef<Path>>(slot: Slot, snapshot_path: P) -> Result<(
pub fn bank_from_archive<P: AsRef<Path>>(
account_paths: &[PathBuf],
frozen_account_pubkeys: &[Pubkey],
snapshot_path: &PathBuf,
snapshot_tar: P,
) -> Result<Bank> {
@ -450,6 +451,7 @@ pub fn bank_from_archive<P: AsRef<Path>>(
let bank = rebuild_bank_from_snapshots(
snapshot_version.trim(),
account_paths,
frozen_account_pubkeys,
&unpacked_snapshots_dir,
unpacked_accounts_dir,
)?;
@ -575,6 +577,7 @@ pub fn untar_snapshot_in<P: AsRef<Path>, Q: AsRef<Path>>(
fn rebuild_bank_from_snapshots<P>(
snapshot_version: &str,
account_paths: &[PathBuf],
frozen_account_pubkeys: &[Pubkey],
unpacked_snapshots_dir: &PathBuf,
append_vecs_path: P,
) -> Result<Bank>
@ -606,12 +609,16 @@ where
}
};
info!("Rebuilding accounts...");
bank.set_bank_rc(
bank::BankRc::new(account_paths.to_vec(), 0, bank.slot()),
bank::StatusCacheRc::default(),
);
bank.rc
.accounts_from_stream(stream.by_ref(), &append_vecs_path)?;
let rc = bank::BankRc::from_stream(
account_paths,
bank.slot(),
&bank.ancestors,
frozen_account_pubkeys,
stream.by_ref(),
&append_vecs_path,
)?;
bank.set_bank_rc(rc, bank::StatusCacheRc::default());
Ok(bank)
},
)?;

View File

@ -300,7 +300,7 @@ impl LocalCluster {
self.exit();
for (_, node) in self.validators.iter_mut() {
if let Some(v) = node.validator.take() {
v.join().unwrap();
v.join().expect("Validator join failed");
}
}

View File

@ -18,13 +18,14 @@ use solana_local_cluster::{
local_cluster::{ClusterConfig, LocalCluster},
};
use solana_sdk::{
client::SyncClient,
client::{AsyncClient, SyncClient},
clock::{self, Slot},
commitment_config::CommitmentConfig,
epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
genesis_config::OperatingMode,
hash::Hash,
poh_config::PohConfig,
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use std::sync::atomic::{AtomicBool, Ordering};
@ -548,7 +549,7 @@ fn test_listener_startup() {
#[test]
#[serial]
fn test_softlaunch_operating_mode() {
fn test_stable_operating_mode() {
solana_logger::setup();
let config = ClusterConfig {
@ -567,7 +568,7 @@ fn test_softlaunch_operating_mode() {
solana_core::cluster_info::VALIDATOR_PORT_RANGE,
);
// Programs that are available at soft launch epoch 0
// Programs that are available at epoch 0
for program_id in [
&solana_config_program::id(),
&solana_sdk::system_program::id(),
@ -587,7 +588,7 @@ fn test_softlaunch_operating_mode() {
);
}
// Programs that are not available at soft launch epoch 0
// Programs that are not available at epoch 0
for program_id in [
&solana_sdk::bpf_loader::id(),
&solana_storage_program::id(),
@ -607,6 +608,110 @@ fn test_softlaunch_operating_mode() {
}
}
fn generate_frozen_account_panic(mut cluster: LocalCluster, frozen_account: Arc<Keypair>) {
let client = cluster
.get_validator_client(&frozen_account.pubkey())
.unwrap();
// Check the validator is alive by poking it over RPC
trace!(
"validator slot: {}",
client
.get_slot_with_commitment(CommitmentConfig::recent())
.expect("get slot")
);
// Reset the frozen account panic signal
solana_runtime::accounts_db::FROZEN_ACCOUNT_PANIC.store(false, Ordering::Relaxed);
// Wait for the frozen account panic signal
let mut i = 0;
while !solana_runtime::accounts_db::FROZEN_ACCOUNT_PANIC.load(Ordering::Relaxed) {
// Transfer from frozen account
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.unwrap();
client
.async_transfer(1, &frozen_account, &Pubkey::new_rand(), blockhash)
.unwrap();
sleep(Duration::from_secs(1));
i += 1;
if i > 10 {
panic!("FROZEN_ACCOUNT_PANIC still false");
}
}
// The validator is now broken and won't shutdown properly. Avoid LocalCluster panic in Drop
// with some manual cleanup:
cluster.exit();
cluster.validators = HashMap::default();
}
#[test]
#[serial]
fn test_frozen_account_from_genesis() {
solana_logger::setup();
let validator_identity =
Arc::new(solana_sdk::signature::keypair_from_seed(&[0u8; 32]).unwrap());
let config = ClusterConfig {
validator_keys: Some(vec![validator_identity.clone()]),
node_stakes: vec![100; 1],
cluster_lamports: 1_000,
validator_configs: vec![
ValidatorConfig {
// Freeze the validator identity account
frozen_accounts: vec![validator_identity.pubkey()],
..ValidatorConfig::default()
};
1
],
..ClusterConfig::default()
};
generate_frozen_account_panic(LocalCluster::new(&config), validator_identity);
}
#[test]
#[serial]
fn test_frozen_account_from_snapshot() {
solana_logger::setup();
let validator_identity =
Arc::new(solana_sdk::signature::keypair_from_seed(&[0u8; 32]).unwrap());
let mut snapshot_test_config = setup_snapshot_validator_config(5, 1);
// Freeze the validator identity account
snapshot_test_config.validator_config.frozen_accounts = vec![validator_identity.pubkey()];
let config = ClusterConfig {
validator_keys: Some(vec![validator_identity.clone()]),
node_stakes: vec![100; 1],
cluster_lamports: 1_000,
validator_configs: vec![snapshot_test_config.validator_config.clone()],
..ClusterConfig::default()
};
let mut cluster = LocalCluster::new(&config);
let snapshot_package_output_path = &snapshot_test_config
.validator_config
.snapshot_config
.as_ref()
.unwrap()
.snapshot_package_output_path;
trace!("Waiting for snapshot at {:?}", snapshot_package_output_path);
let (archive_filename, _archive_snapshot_hash) =
wait_for_next_snapshot(&cluster, &snapshot_package_output_path);
trace!("Found snapshot: {:?}", archive_filename);
// Restart the validator from a snapshot
let validator_info = cluster.exit_node(&validator_identity.pubkey());
cluster.restart_node(&validator_identity.pubkey(), validator_info);
generate_frozen_account_panic(cluster, validator_identity);
}
#[test]
#[serial]
fn test_consistency_halt() {

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);
}

View File

@ -1,5 +1,6 @@
use clap::{
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, Arg, ArgMatches,
crate_description, crate_name, value_t, value_t_or_exit, values_t, values_t_or_exit, App, Arg,
ArgMatches,
};
use log::*;
use rand::{thread_rng, Rng};
@ -662,6 +663,17 @@ pub fn main() {
.takes_value(false)
.help("Abort the validator if a bank hash mismatch is detected within trusted validator set"),
)
.arg(
clap::Arg::with_name("frozen_accounts")
.long("frozen-account")
.validator(is_pubkey)
.value_name("PUBKEY")
.multiple(true)
.takes_value(true)
.help("Freeze the specified account. This will cause the validator to \
intentionally crash should any transaction modify the frozen account in any way \
other than increasing the account balance"),
)
.get_matches();
let identity_keypair = Arc::new(keypair_of(&matches, "identity").unwrap_or_else(Keypair::new));
@ -733,6 +745,7 @@ pub fn main() {
voting_disabled: matches.is_present("no_voting"),
wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(),
trusted_validators,
frozen_accounts: values_t!(matches, "frozen_accounts", Pubkey).unwrap_or_default(),
..ValidatorConfig::default()
};