parent
4dd0367136
commit
88ba8439fc
|
@ -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)",
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
)?;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
Self::new_with_frozen_accounts(paths, &HashMap::default(), &[])
|
||||
}
|
||||
|
||||
Accounts {
|
||||
slot: 0,
|
||||
accounts_db,
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue