Manual v1.0 backports (#9015)

automerge
This commit is contained in:
Michael Vines
2020-03-22 22:44:55 -07:00
committed by GitHub
parent a5e4a1f2d8
commit 7ffaf2ad29
32 changed files with 1201 additions and 447 deletions

1
Cargo.lock generated
View File

@ -4460,6 +4460,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.8.2 (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.66 (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

@ -301,9 +301,7 @@ pub enum CliCommand {
},
StakeAuthorize {
stake_account_pubkey: Pubkey,
new_authorized_pubkey: Pubkey,
stake_authorize: StakeAuthorize,
authority: SignerIndex,
new_authorizations: Vec<(StakeAuthorize, Pubkey, SignerIndex)>,
sign_only: bool,
blockhash_query: BlockhashQuery,
nonce_account: Option<Pubkey>,
@ -644,18 +642,9 @@ pub fn parse_command(
("split-stake", Some(matches)) => {
parse_split_stake(matches, default_signer_path, wallet_manager)
}
("stake-authorize-staker", Some(matches)) => parse_stake_authorize(
matches,
default_signer_path,
wallet_manager,
StakeAuthorize::Staker,
),
("stake-authorize-withdrawer", Some(matches)) => parse_stake_authorize(
matches,
default_signer_path,
wallet_manager,
StakeAuthorize::Withdrawer,
),
("stake-authorize", Some(matches)) => {
parse_stake_authorize(matches, default_signer_path, wallet_manager)
}
("stake-set-lockup", Some(matches)) => {
parse_stake_set_lockup(matches, default_signer_path, wallet_manager)
}
@ -1825,9 +1814,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
}
CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey,
stake_authorize,
authority,
ref new_authorizations,
sign_only,
blockhash_query,
nonce_account,
@ -1837,9 +1824,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
&rpc_client,
config,
&stake_account_pubkey,
&new_authorized_pubkey,
*stake_authorize,
*authority,
new_authorizations,
*sign_only,
blockhash_query,
*nonce_account,
@ -2264,10 +2249,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("to")
.index(2)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.validator(is_valid_pubkey)
.help("The pubkey of airdrop recipient"),
.help("The account address of airdrop recipient"),
),
)
.subcommand(
@ -2276,10 +2261,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.validator(is_valid_pubkey)
.help("The public key of the balance to check"),
.help("The account address of the balance to check"),
)
.arg(
Arg::with_name("lamports")
@ -2294,11 +2279,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("process_id")
.index(1)
.value_name("PROCESS_PUBKEY")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The process id of the transfer to cancel"),
.help("The account address of the transfer to cancel"),
),
)
.subcommand(
@ -2327,7 +2312,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("program_id")
.index(2)
.value_name("PROGRAM_PUBKEY")
.value_name("PROGRAM_ID")
.takes_value(true)
.required(true)
.help(
@ -2363,11 +2348,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("to")
.index(1)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("The pubkey of recipient"),
.help("The account address of recipient"),
)
.arg(
Arg::with_name("amount")
@ -2432,19 +2417,19 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("to")
.index(1)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
.help("The account address of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_PUBKEY")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.help("The process id of the transfer to authorize"),
.help("The account address of the transfer to authorize"),
),
)
.subcommand(
@ -2453,19 +2438,19 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("to")
.index(1)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
.help("The account address of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_PUBKEY")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.help("The process id of the transfer to unlock"),
.help("The account address of the transfer to unlock"),
)
.arg(
Arg::with_name("datetime")
@ -2481,11 +2466,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("to")
.index(1)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("The pubkey of recipient"),
.help("The account address of recipient"),
)
.arg(
Arg::with_name("amount")
@ -2516,7 +2501,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)

View File

@ -95,7 +95,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("NONCE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -156,7 +156,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("NONCE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -169,7 +169,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("NONCE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -184,7 +184,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("NONCE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -203,7 +203,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("NONCE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -212,7 +212,7 @@ impl NonceSubCommands for App<'_, '_> {
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)

View File

@ -160,7 +160,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -169,7 +169,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -182,53 +182,36 @@ impl StakeSubCommands for App<'_, '_> {
.arg(fee_payer_arg())
)
.subcommand(
SubCommand::with_name("stake-authorize-staker")
.about("Authorize a new stake signing keypair for the given stake account")
SubCommand::with_name("stake-authorize")
.about("Authorize a new signing keypair for the given stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.takes_value(true)
.required(true)
.index(1)
.takes_value(true)
.value_name("STAKE_ACCOUNT_ADDRESS")
.validator(is_valid_pubkey)
.help("Stake account in which to set the authorized staker")
.help("Stake account in which to set a new authority")
)
.arg(
Arg::with_name("authorized_pubkey")
.index(2)
.value_name("AUTHORIZED_PUBKEY")
Arg::with_name("new_stake_authority")
.long("new-stake-authority")
.required_unless("new_withdraw_authority")
.takes_value(true)
.required(true)
.value_name("PUBKEY")
.validator(is_valid_pubkey)
.help("New authorized staker")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.arg(fee_payer_arg())
)
.subcommand(
SubCommand::with_name("stake-authorize-withdrawer")
.about("Authorize a new withdraw signing keypair for the given stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
Arg::with_name("new_withdraw_authority")
.long("new-withdraw-authority")
.required_unless("new_stake_authority")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("Stake account in which to set the authorized withdrawer")
)
.arg(
Arg::with_name("authorized_pubkey")
.index(2)
.value_name("AUTHORIZED_PUBKEY")
.takes_value(true)
.required(true)
.value_name("PUBKEY")
.validator(is_valid_pubkey)
.help("New authorized withdrawer")
)
.arg(stake_authority_arg())
.arg(withdraw_authority_arg())
.offline_args()
.arg(nonce_arg())
@ -241,7 +224,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -259,7 +242,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -302,7 +285,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -311,7 +294,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -338,7 +321,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -390,7 +373,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STAKE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -515,37 +498,75 @@ pub fn parse_stake_authorize(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
stake_authorize: StakeAuthorize,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
let new_authorized_pubkey =
pubkey_of_signer(matches, "authorized_pubkey", wallet_manager)?.unwrap();
let authority_flag = match stake_authorize {
StakeAuthorize::Staker => STAKE_AUTHORITY_ARG.name,
StakeAuthorize::Withdrawer => WITHDRAW_AUTHORITY_ARG.name,
let mut new_authorizations = Vec::new();
let mut bulk_signers = Vec::new();
if let Some(new_authority_pubkey) =
pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?
{
let (authority, authority_pubkey) = {
let (authority, authority_pubkey) =
signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
// Withdraw authority may also change the staker
if authority.is_none() {
signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
} else {
(authority, authority_pubkey)
}
};
new_authorizations.push((
StakeAuthorize::Staker,
new_authority_pubkey,
authority_pubkey,
));
bulk_signers.push(authority);
};
if let Some(new_authority_pubkey) =
pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?
{
let (authority, authority_pubkey) =
signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
new_authorizations.push((
StakeAuthorize::Withdrawer,
new_authority_pubkey,
authority_pubkey,
));
bulk_signers.push(authority);
};
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let (authority, authority_pubkey) = signer_of(matches, authority_flag, wallet_manager)?;
let blockhash_query = BlockhashQuery::new_from_matches(matches);
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
let (nonce_authority, nonce_authority_pubkey) =
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
let mut bulk_signers = vec![authority, fee_payer];
bulk_signers.push(fee_payer);
if nonce_account.is_some() {
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
let new_authorizations = new_authorizations
.into_iter()
.map(
|(stake_authorize, new_authority_pubkey, authority_pubkey)| {
(
stake_authorize,
new_authority_pubkey,
signer_info.index_of(authority_pubkey).unwrap(),
)
},
)
.collect();
Ok(CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey,
stake_authorize,
authority: signer_info.index_of(authority_pubkey).unwrap(),
new_authorizations,
sign_only,
blockhash_query,
nonce_account,
@ -871,28 +892,30 @@ pub fn process_stake_authorize(
rpc_client: &RpcClient,
config: &CliConfig,
stake_account_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
stake_authorize: StakeAuthorize,
authority: SignerIndex,
new_authorizations: &[(StakeAuthorize, Pubkey, SignerIndex)],
sign_only: bool,
blockhash_query: &BlockhashQuery,
nonce_account: Option<Pubkey>,
nonce_authority: SignerIndex,
fee_payer: SignerIndex,
) -> ProcessResult {
check_unique_pubkeys(
(stake_account_pubkey, "stake_account_pubkey".to_string()),
(authorized_pubkey, "new_authorized_pubkey".to_string()),
)?;
let authority = config.signers[authority];
let mut ixs = Vec::new();
for (stake_authorize, authorized_pubkey, authority) in new_authorizations.iter() {
check_unique_pubkeys(
(stake_account_pubkey, "stake_account_pubkey".to_string()),
(authorized_pubkey, "new_authorized_pubkey".to_string()),
)?;
let authority = config.signers[*authority];
ixs.push(stake_instruction::authorize(
stake_account_pubkey, // stake account to update
&authority.pubkey(), // currently authorized
authorized_pubkey, // new stake signer
*stake_authorize, // stake or withdraw
));
}
let (recent_blockhash, fee_calculator) =
blockhash_query.get_blockhash_and_fee_calculator(rpc_client)?;
let ixs = vec![stake_instruction::authorize(
stake_account_pubkey, // stake account to update
&authority.pubkey(), // currently authorized
authorized_pubkey, // new stake signer
stake_authorize, // stake or withdraw
)];
let nonce_authority = config.signers[nonce_authority];
let fee_payer = config.signers[fee_payer];
@ -1236,8 +1259,8 @@ pub fn process_stake_set_lockup(
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
fn show_authorized(authorized: &Authorized) {
println!("Authorized Staker: {}", authorized.staker);
println!("Authorized Withdrawer: {}", authorized.withdrawer);
println!("Stake Authority: {}", authorized.staker);
println!("Withdraw Authority: {}", authorized.withdrawer);
}
fn show_lockup(lockup: &Lockup) {
println!(
@ -1266,7 +1289,10 @@ pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamp
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
);
if stake.delegation.voter_pubkey != Pubkey::default() {
println!("Delegated Voter Pubkey: {}", stake.delegation.voter_pubkey);
println!(
"Delegated Vote Account Address: {}",
stake.delegation.voter_pubkey
);
}
println!(
"Stake activates starting from epoch: {}",
@ -1486,83 +1512,279 @@ mod tests {
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
}
fn parse_authorize_tests(
test_commands: &App,
stake_account_pubkey: Pubkey,
authority_keypair_file: &str,
stake_authorize: StakeAuthorize,
) {
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let stake_account_pubkey = stake_account_keypair.pubkey();
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
let stake_authority_keypair = Keypair::new();
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
// stake-authorize subcommand
let stake_account_string = stake_account_pubkey.to_string();
let (subcommand, authority_flag) = match stake_authorize {
StakeAuthorize::Staker => ("stake-authorize-staker", "--stake-authority"),
StakeAuthorize::Withdrawer => ("stake-authorize-withdrawer", "--withdraw-authority"),
};
// Test Staker Subcommand
let test_authorize = test_commands.clone().get_matches_from(vec![
let new_stake_authority = Pubkey::new(&[1u8; 32]);
let new_stake_string = new_stake_authority.to_string();
let new_withdraw_authority = Pubkey::new(&[2u8; 32]);
let new_withdraw_string = new_withdraw_authority.to_string();
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
&stake_account_string,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&new_stake_string,
"--new-withdraw-authority",
&new_withdraw_string,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![
(StakeAuthorize::Staker, new_stake_authority, 0,),
(StakeAuthorize::Withdrawer, new_withdraw_authority, 0,),
],
sign_only: false,
blockhash_query: BlockhashQuery::default(),
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
},
);
// Test Staker Subcommand w/ authority
let test_authorize = test_commands.clone().get_matches_from(vec![
let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
let withdraw_authority_keypair = Keypair::new();
write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
&stake_account_string,
&authority_flag,
&authority_keypair_file,
"--new-stake-authority",
&new_stake_string,
"--new-withdraw-authority",
&new_withdraw_string,
"--stake-authority",
&stake_authority_keypair_file,
"--withdraw-authority",
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 1,
new_authorizations: vec![
(StakeAuthorize::Staker, new_stake_authority, 1,),
(StakeAuthorize::Withdrawer, new_withdraw_authority, 2,),
],
sign_only: false,
blockhash_query: BlockhashQuery::default(),
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&authority_keypair_file).unwrap().into()
read_keypair_file(&stake_authority_keypair_file)
.unwrap()
.into(),
read_keypair_file(&withdraw_authority_keypair_file)
.unwrap()
.into(),
],
}
},
);
// Withdraw authority may set both new authorities
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&new_stake_string,
"--new-withdraw-authority",
&new_withdraw_string,
"--withdraw-authority",
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![
(StakeAuthorize::Staker, new_stake_authority, 1,),
(StakeAuthorize::Withdrawer, new_withdraw_authority, 1,),
],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&withdraw_authority_keypair_file)
.unwrap()
.into(),
],
},
);
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&new_stake_string,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 0,),],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
},
);
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&new_stake_string,
"--stake-authority",
&stake_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 1,),],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&stake_authority_keypair_file)
.unwrap()
.into(),
],
},
);
// Withdraw authority may set new stake authority
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&new_stake_string,
"--withdraw-authority",
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 1,),],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&withdraw_authority_keypair_file)
.unwrap()
.into(),
],
},
);
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-withdraw-authority",
&new_withdraw_string,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(
StakeAuthorize::Withdrawer,
new_withdraw_authority,
0,
),],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
},
);
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize",
&stake_account_string,
"--new-withdraw-authority",
&new_withdraw_string,
"--withdraw-authority",
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(
StakeAuthorize::Withdrawer,
new_withdraw_authority,
1,
),],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&withdraw_authority_keypair_file)
.unwrap()
.into(),
],
},
);
// Test Authorize Subcommand w/ sign-only
let blockhash = Hash::default();
let blockhash_string = format!("{}", blockhash);
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--blockhash",
&blockhash_string,
@ -1573,9 +1795,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
@ -1592,8 +1812,9 @@ mod tests {
let signer = format!("{}={}", keypair.pubkey(), sig);
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--blockhash",
&blockhash_string,
@ -1607,9 +1828,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::Cluster,
@ -1633,8 +1852,9 @@ mod tests {
let nonce_account = Pubkey::new(&[1u8; 32]);
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--blockhash",
&blockhash_string,
@ -1654,9 +1874,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account),
@ -1676,8 +1894,9 @@ mod tests {
// Test Authorize Subcommand w/ blockhash
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--blockhash",
&blockhash_string,
@ -1687,9 +1906,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::Cluster,
@ -1710,8 +1927,9 @@ mod tests {
let nonce_account_string = nonce_account_pubkey.to_string();
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--blockhash",
&blockhash_string,
@ -1725,9 +1943,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account_pubkey),
@ -1751,8 +1967,9 @@ mod tests {
let fee_payer_string = fee_payer_pubkey.to_string();
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--fee-payer",
&fee_payer_keypair_file,
@ -1762,9 +1979,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
@ -1782,8 +1997,9 @@ mod tests {
let signer = format!("{}={}", fee_payer_string, sig);
let test_authorize = test_commands.clone().get_matches_from(vec![
"test",
&subcommand,
"stake-authorize",
&stake_account_string,
"--new-stake-authority",
&stake_account_string,
"--fee-payer",
&fee_payer_string,
@ -1797,9 +2013,7 @@ mod tests {
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: stake_account_pubkey,
stake_authorize,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::Cluster,
@ -1815,34 +2029,6 @@ mod tests {
],
}
);
}
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let stake_account_pubkey = stake_account_keypair.pubkey();
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
let stake_authority_keypair = Keypair::new();
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
parse_authorize_tests(
&test_commands,
stake_account_pubkey,
&stake_authority_keypair_file,
StakeAuthorize::Staker,
);
parse_authorize_tests(
&test_commands,
stake_account_pubkey,
&stake_authority_keypair_file,
StakeAuthorize::Withdrawer,
);
// Test CreateStakeAccount SubCommand
let custodian = Pubkey::new_rand();

View File

@ -66,7 +66,7 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("node_account_pubkey")
.index(1)
.value_name("NODE_PUBKEY")
.value_name("NODE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -75,7 +75,7 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("ACCOUNT_PUBKEY")
.value_name("STORAGE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -89,11 +89,11 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("storage_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("STORAGE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("Storage account pubkey"),
.help("Storage account address"),
),
)
}

View File

@ -86,7 +86,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -108,7 +108,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -130,7 +130,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -170,7 +170,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -189,7 +189,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("ACCOUNT_PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
@ -198,7 +198,7 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("RECIPIENT_PUBKEY")
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)

View File

@ -626,9 +626,7 @@ fn test_stake_authorize() {
config.signers.pop();
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
@ -644,13 +642,40 @@ fn test_stake_authorize() {
};
assert_eq!(current_authority, online_authority_pubkey);
// Assign new offline stake authority
// Assign new online stake and withdraw authorities
let online_authority2 = Keypair::new();
let online_authority2_pubkey = online_authority2.pubkey();
let withdraw_authority = Keypair::new();
let withdraw_authority_pubkey = withdraw_authority.pubkey();
config.signers.push(&online_authority);
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: offline_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 1,
new_authorizations: vec![
(StakeAuthorize::Staker, online_authority2_pubkey, 1),
(StakeAuthorize::Withdrawer, withdraw_authority_pubkey, 0),
],
sign_only: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
let stake_state: StakeState = stake_account.state().unwrap();
let (current_staker, current_withdrawer) = match stake_state {
StakeState::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
_ => panic!("Unexpected stake state!"),
};
assert_eq!(current_staker, online_authority2_pubkey);
assert_eq!(current_withdrawer, withdraw_authority_pubkey);
// Assign new offline stake authority
config.signers.pop();
config.signers.push(&online_authority2);
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorizations: vec![(StakeAuthorize::Staker, offline_authority_pubkey, 1)],
sign_only: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
@ -672,9 +697,7 @@ fn test_stake_authorize() {
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: nonced_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
@ -688,9 +711,7 @@ fn test_stake_authorize() {
config.signers = vec![&offline_presigner];
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: nonced_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,
@ -732,9 +753,7 @@ fn test_stake_authorize() {
config_offline.signers.push(&nonced_authority);
config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 1,
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
sign_only: true,
blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_account.pubkey()),
@ -750,9 +769,7 @@ fn test_stake_authorize() {
config.signers = vec![&offline_presigner, &nonced_authority_presigner];
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 1,
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
@ -859,9 +876,7 @@ fn test_stake_authorize_with_fee_payer() {
config.signers = vec![&default_signer, &payer_keypair];
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: offline_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
@ -879,9 +894,7 @@ fn test_stake_authorize_with_fee_payer() {
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: payer_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
@ -895,9 +908,7 @@ fn test_stake_authorize_with_fee_payer() {
config.signers = vec![&offline_presigner];
config.command = CliCommand::StakeAuthorize {
stake_account_pubkey,
new_authorized_pubkey: payer_pubkey,
stake_authorize: StakeAuthorize::Staker,
authority: 0,
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,

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 {
@ -103,6 +104,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

@ -47,6 +47,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);
@ -82,6 +83,7 @@ mod tests {
let deserialized_bank = snapshot_utils::bank_from_archive(
&account_paths,
&[],
&old_bank_forks
.snapshot_config
.as_ref()

View File

@ -10,6 +10,7 @@
* [Paper Wallet Usage](paper-wallet/usage.md)
* [Generate Keys](cli/generate-keys.md)
* [Send and Receive Tokens](cli/transfer-tokens.md)
* [Delegate Stake](cli/delegate-stake.md)
* [Offline Signing](offline-signing/README.md)
* [Durable Transaction Nonces](offline-signing/durable-nonce.md)
* [Command-line Reference](cli/usage.md)

View File

@ -8,7 +8,7 @@ The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-l
```bash
// Command
$ solana address
$ solana-keygen pubkey
// Return
<PUBKEY>

View File

@ -1,5 +1,9 @@
# Command-line Guide
This section describes the command-line tools for interacting with Solana. One
could use these tools to send payments, stake validators, and check account
balances.
In this section, we will describe how to create a Solana *wallet*, how to send
and receive tokens, and how to participate in the cluster by delegating stake.
To interact with a Solana cluster, we will use its command-line interface, also
known as the CLI. We use the command-line because it is the first place the
Solana core team deploys new functionality. The command-line interface is not
necessarily the easiest to use, but it provides the most direct, flexible, and
secure access to your Solana accounts.

View File

@ -3,7 +3,7 @@
Keypairs are stored in *wallets* and wallets come in many forms. A wallet might
be a directory in your computer's file system, a piece of paper, or a
specialized device called a *hardware wallet*. Some wallets are easier to use
than others. Some are more secure than others. In this section, we'll compare
than others. Some are more secure than others. In this section, we will compare
and contrast different types of wallets and help you choose the wallet that
best fits your needs.
@ -16,8 +16,8 @@ file system. Each file in the directory holds a keypair.
An FS wallet is the most convenient and least secure form of wallet. It is
convenient because the keypair is stored in a simple file. You can generate as
many keys as you'd like and trivially back them up by copying the files. It is
insecure because the keypair files are **unencrypted**. If you are the only
many keys as you would like and trivially back them up by copying the files. It
is insecure because the keypair files are **unencrypted**. If you are the only
user of your computer and you are confident it is free of malware, an FS wallet
is a fine solution for small amounts of cryptocurrency. If, however, your
computer contains malware and is connected to the Internet, that malware may
@ -72,8 +72,8 @@ To keep your hardware wallet tokens safe, we suggest:
## Which Wallet is Best?
Different people will have different needs, but if you are still unsure what's
best for you after reading the comparisons above, go with a
Different people will have different needs, but if you are still unsure what
is best for you after reading the comparisons above, go with a
[Ledger Nano S](https://shop.ledger.com/products/ledger-nano-s). The
[Nano S is well-integrated into Solana's tool suite](../remote-wallet/ledger)
and offers an outstanding blend of security and convenience.

View File

@ -0,0 +1,197 @@
# Delegate Stake
After you have [received SOL](transfer-tokens.md), you might consider putting
it to use by delegating *stake* to a validator. Stake is what we call tokens
in a *stake account*. Solana weights validator votes by the amount of stake
delegated to them, which gives those validators more influence in determining
then next valid block of transactions in the blockchain. Solana then generates
new SOL periodically to reward stakers and validators. You earn more rewards
the more stake you delegate.
## Create a Stake Account
To delegate stake, you will need to transfer some tokens into a stake account.
To create an account, you will need a keypair. Its public key will be used as
the stake account address. No need for a password or encryption here; this
keypair will be discarded right after creating the stake account.
```bash
solana-keygen new --no-passphrase -o stake-account.json
```
The output will contain the public key after the text `pubkey:`.
```text
============================================================================
pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
============================================================================
```
Copy the public key and store it for safekeeping. You will need it any time you
want to perform an action on the stake account you create next.
Now, create a stake account:
```bash
solana create-stake-account --from=<KEYPAIR> stake-account.json <AMOUNT> --stake-authority=<KEYPAIR> --withdraw-authority=<KEYPAIR>
```
`<AMOUNT>` tokens are transferred from the account at `<KEYPAIR>` to a new
stake account at the public key of stake-account.json.
The stake-account.json file can now be discarded. To authorize additional
actions, you will use the `--stake-authority` or `withdraw-authority` keypair,
not stake-account.json.
View the new stake account with the `solana stake-account` command:
```bash
solana stake-account <STAKE_ACCOUNT_ADDRESS>
```
The output will look similar to this:
```text
Total Stake: 5000 SOL
Stake account is undelegated
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
```
### Set Stake and Withdraw Authorities
Staking commands look to keypairs to authorize certain stake account
operations. They use the stake authority to authorize stake delegation,
deactivating stake, splitting stake, and setting a new stake authority. They
use the withdraw authority to authorize withdrawing stake, and when setting
either a new stake or withdraw authority.
Stake and withdraw authorities can be set when creating an account via the
`--stake-authority` and `--withdraw-authority` options, or afterward with the
`solana stake-authorize` command. For example, to set a new stake authority,
run:
```bash
solana stake-authorize <STAKE_ACCOUNT_ADDRESS> --stake-authority=<KEYPAIR> --new-stake-authority=<PUBKEY>
```
This will use the existing stake authority `<KEYPAIR>` to authorize a new stake
authority `<PUBKEY>` on the stake account `<STAKE_ACCOUNT_ADDRESS>`.
### Advanced: Derive Stake Account Addresses
When you delegate stake, you delegate all tokens in the stake account to a
single validator. To delegate to multiple validators, you will need multiple
stake accounts. Creating a new keypair for each account and managing those
addresses can be cumbersome. Fortunately, you can derive stake addresses using
the `--seed` option:
```bash
solana create-stake-account --from=<KEYPAIR> <STAKE_ACCOUNT_KEYPAIR> --seed=<STRING> <AMOUNT> --stake-authority=<PUBKEY> --withdraw-authority=<PUBKEY>
```
`<STRING>` is an arbitrary string up to 32 bytes, but will typically be a
number corresponding to which derived account this is. The first account might
be "0", then "1", and so on. The public key of `<STAKE_ACCOUNT_KEYPAIR>` acts
as the base address. The command derives a new address from the base address
and seed string. To see what stake address the command will derive, use `solana
create-address-with-seed`:
```bash
solana create-address-with-seed --from=<PUBKEY> <SEED_STRING> STAKE
```
`<PUBKEY>` is the public key of the `<STAKE_ACCOUNT_KEYPAIR>` passed to
`solana create-stake-account`.
The command will output a derived address, which can be used for the
`<STAKE_ACCOUNT_ADDRESS>` argument in staking operations.
## Delegate Stake
To delegate your stake to a validator, you will need its vote account address.
Find it by querying the cluster for the list of all validators and their vote
accounts with the `solana validators` command:
```bash
solana validators
```
The first column of each row contains the validator's identity and the second
is the vote account address. Choose a validator and use its vote account
address in `solana delegate-stake`:
```bash
solana delegate-stake --stake-authority=<KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <VOTE_ACCOUNT_ADDRESS>
```
`<KEYPAIR>` authorizes the operation on the account with address
`<STAKE_ACCOUNT_ADDRESS>`. The stake is delegated to the vote account with
address `<VOTE_ACCOUNT_ADDRESS>`.
After delegating stake, use `solana stake-account` to observe the changes
to the stake account:
```bash
solana stake-account <STAKE_ACCOUNT_ADDRESS>
```
You will see new fields "Delegated Stake" and "Delegated Vote Account Address"
in the output. The output will look similar to this:
```text
Total Stake: 5000 SOL
Credits Observed: 147462
Delegated Stake: 4999.99771712 SOL
Delegated Vote Account Address: CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1
Stake activates starting from epoch: 42
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
```
## Deactivate Stake
Once delegated, you can undelegate stake with the `solana deactivate-stake`
command:
```bash
solana deactivate-stake --stake-authority=<KEYPAIR> <STAKE_ACCOUNT_ADDRESS>
```
`<KEYPAIR>` authorizes the operation on the account with address
`<STAKE_ACCOUNT_ADDRESS>`.
Note that stake takes several epochs to "cool down". Attempts to delegate stake
in the cool down period will fail.
## Withdraw Stake
Transfer tokens out of a stake account with the `solana withdraw-stake` command:
```bash
solana withdraw-stake --withdraw-authority=<KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <RECIPIENT_ADDRESS> <AMOUNT>
```
`<STAKE_ACCOUNT_ADDRESS>` is the existing stake account, `<KEYPAIR>` is the
withdraw authority, and `<AMOUNT>` is the number of tokens to transfer to
`<RECIPIENT_ADDRESS>`.
## Split Stake
You may want to delegate stake to additional validators while your existing
stake is not eligible for withdrawal. It might not be eligible because it is
currently staked, cooling down, or locked up. To transfer tokens from an
existing stake account to a new one, use the `solana split-stake` command:
```bash
solana split-stake --stake-authority=<KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <NEW_STAKE_ACCOUNT_KEYPAIR> <AMOUNT>
```
`<STAKE_ACCOUNT_ADDRESS>` is the existing stake account, `<KEYPAIR>` is the
stake authority, `<NEW_STAKE_ACCOUNT_KEYPAIR>` is the keypair for the new account,
and `<AMOUNT>` is the number of tokens to transfer to the new account.
To split a stake account into a derived account address, use the `--seed`
option. See
[Derive Stake Account Addresses](#advanced-derive-stake-account-addresses)
for details.

View File

@ -1,7 +1,7 @@
# Generate a Keypair and its Public Key
In this section, we'll generate a keypair, query it for its public key,
and verify you control its private key. Before you begin, you'll need
In this section, we will generate a keypair, query it for its public key,
and verify you control its private key. Before you begin, you will need
to:
* [Install the Solana Tool Suite](../install-solana.md)

View File

@ -6,7 +6,7 @@ To receive tokens, you will need an address for others to send tokens to. In
Solana, an address is the public key of a keypair. There are a variety
of techniques for generating keypairs. The method you choose will depend on how
you choose to store keypairs. Keypairs are stored in wallets. Before receiving
tokens, you'll need to [choose a wallet](choose-a-wallet.md) and
tokens, you will need to [choose a wallet](choose-a-wallet.md) and
[generate keys](generate-keys.md). Once completed, you should have a public key
for each keypair you generated. The public key is a long string of base58
characters. Its length varies from 32 to 44 characters.
@ -32,14 +32,14 @@ where you replace the text `<COMMAND>` with the name of the command you want
to learn more about.
The command's usage message will typically contain words such as `<AMOUNT>`,
`<PUBKEY>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of text
you can execute the command with. For example, you can replace `<AMOUNT>`
with a number such as `42` or `100.42`. You can replace `<PUBKEY>` with the
base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type
`<ACCOUNT_ADDRESS>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of
text you can execute the command with. For example, you can replace `<AMOUNT>`
with a number such as `42` or `100.42`. You can replace `<ACCOUNT_ADDRESS>` with
the base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type
of wallet you chose. If you chose an fs wallet, that path might be
`~/my-solana-wallet/my-keypair.json`. If you chose a paper wallet, use the
keyword `ASK`, and the Solana CLI will prompt you for your seed phrase. If
you chose a hardware wallet, use your USB URL, such as `usb://ledger?key=0`.
you chose a hardware wallet, use your keypair URL, such as `usb://ledger?key=0`.
### Test-drive your Public Keys
@ -50,23 +50,24 @@ Try and *airdrop* yourself some play tokens on the developer testnet, called
Devnet:
```bash
solana airdrop 10 <RECIPIENT_PUBKEY> --url http://devnet.solana.com
solana airdrop 10 <RECIPIENT_ACCOUNT_ADDRESS> --url http://devnet.solana.com
```
where you replace the text `<RECIPIENT_PUBKEY>` with your base58 public key.
where you replace the text `<RECIPIENT_ACCOUNT_ADDRESS>` with your base58-encoded
public key.
Confirm the airdrop was successful by checking the account's balance.
It should output `10 SOL`:
```bash
solana balance <ACCOUNT_PUBKEY> --url http://devnet.solana.com
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
```
Next, prove that you own those tokens by transferring them. The Solana cluster
will only accept the transfer if you sign the transaction with the private
key corresponding to the sender's public key in the transaction.
First, we'll need a public key to receive our tokens. Create a second
First, we will need a public key to receive our tokens. Create a second
keypair and record its pubkey:
```bash
@ -74,7 +75,7 @@ solana-keygen new --no-passphrase --no-outfile
```
The output will contain the public key after the text `pubkey:`. Copy the
public key. We'll use it in the next step.
public key. We will use it in the next step.
```text
============================================================================
@ -83,19 +84,19 @@ pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
```
```bash
solana transfer --keypair=<SENDER_KEYPAIR> <RECIPIENT_PUBKEY> 5 --url http://devnet.solana.com
solana transfer --from=<SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --url http://devnet.solana.com
```
where you replace `<SENDER_KEYPAIR>` with the path to a keypair in your wallet,
and replace `<RECIPIENT_PUBKEY>` with the output of `solana-keygen new` above.
and replace `<RECIPIENT_ACCOUNT_ADDRESS>` with the output of `solana-keygen new` above.
Confirm the updated balances with `solana balance`:
```bash
solana balance <ACCOUNT_PUBKEY> --url http://devnet.solana.com
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
```
where `<PUBKEY>` is either the public key from your keypair or the
where `<ACCOUNT_ADDRESS>` is either the public key from your keypair or the
recipient's public key.
## Send Tokens
@ -106,11 +107,11 @@ tokens to transfer. Once you have that collected, you can transfer tokens
with the `solana transfer` command:
```bash
solana transfer --keypair=<SENDER_KEYPAIR> <RECIPIENT_PUBKEY> <AMOUNT>
solana transfer --from=<SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT>
```
Confirm the updated balances with `solana balance`:
```bash
solana balance <ACCOUNT_PUBKEY>
solana balance <ACCOUNT_ADDRESS>
```

View File

@ -18,8 +18,7 @@ At present, the following commands support offline signing:
* [`deactivate-stake`](../cli/usage.md#solana-deactivate-stake)
* [`delegate-stake`](../cli/usage.md#solana-delegate-stake)
* [`split-stake`](../cli/usage.md#solana-split-stake)
* [`stake-authorize-staker`](../cli/usage.md#solana-stake-authorize-staker)
* [`stake-authorize-withdrawer`](../cli/usage.md#solana-stake-authorize-withdrawer)
* [`stake-authorize`](../cli/usage.md#solana-stake-authorize)
* [`stake-set-lockup`](../cli/usage.md#solana-stake-set-lockup)
* [`transfer`](../cli/usage.md#solana-transfer)
* [`withdraw-stake`](../cli/usage.md#solana-withdraw-stake)

View File

@ -52,7 +52,7 @@ usb://ledger/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
## Manage Multiple Hardware Wallets
It is sometimes useful to sign a transaction with keys from multiple hardware
wallets. Signing with multiple wallets requires *fully qualified USB URLs*.
wallets. Signing with multiple wallets requires *fully qualified keypair URLs*.
When the URL is not fully qualified, the Solana CLI will prompt you with
the fully qualified URLs of all connected hardware wallets, and ask you to
choose which wallet to use for each signature.

View File

@ -72,81 +72,29 @@ To fix, check the following:
3. On your computer, run:
```text
solana address --keypair usb://ledger
solana-keygen pubkey usb://ledger
```
This confirms your Ledger device is connected properly and in the correct state
to interact with the Solana CLI. The command returns your Ledger's unique
*wallet key*. When you have multiple Nano S devices connected to the same
*wallet ID*. When you have multiple Nano S devices connected to the same
computer, you can use your wallet key to specify which Ledger hardware wallet
you want to use. Run the same command again, but this time, with its fully
qualified URL:
```text
solana address --keypair usb://ledger/<WALLET_KEY>
solana-keygen pubkey usb://ledger/<WALLET_ID>
```
Confirm it prints the same key as when you entered just `usb://ledger`.
where you replace `<WALLET_ID>` with the output of the first command.
Confirm it prints the same wallet ID as before.
To learn more about USB URLs, see
To learn more about keypair URLs, see
[Specify A Hardware Wallet Key](index.md#specify-a-hardware-wallet-key)
### Set CLI Configuration
Configure the `solana` CLI tool to connect to a particular cluster:
```bash
solana config set --url <CLUSTER_URL> # (i.e. http://devnet.solana.com)
```
If you want to set a Ledger key as the default signer for CLI commands, use the
[CLI configuration settings](../cli/usage.md#solana-config):
```text
solana config set --keypair <LEDGER_URL>
```
For example:
```text
solana config set --keypair usb://ledger?key=0
```
### Check Account Balance
```text
solana balance --keypair usb://ledger?key=12345
```
Or with the default signer:
```text
solana balance
```
### Send SOL via Ledger Device
```text
solana transfer <RECIPIENT> <AMOUNT> --from <LEDGER_URL>
```
Or with the default signer:
```text
solana transfer <RECIPIENT> <AMOUNT>
```
### Delegate Stake with Ledger Device
```text
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID> --keypair <LEDGER_URL>
```
Or with the default signer:
```text
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID>
```
Read more about [sending and receiving tokens](../transfer-tokens.md) and
[delegating stake](../delegate-stake.md). You can use your Ledger keypair URL
anywhere you see an option or argument that accepts a `<KEYPAIR>`.
## Support

View File

@ -35,14 +35,15 @@ enum LedgerOutputMethod {
Json,
}
fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod) {
fn output_slot(
blockstore: &Blockstore,
slot: Slot,
method: &LedgerOutputMethod,
) -> Result<(), String> {
println!("Slot Meta {:?}", blockstore.meta(slot));
let entries = blockstore
.get_slot_entries(slot, 0, None)
.unwrap_or_else(|err| {
eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
exit(1);
});
.map_err(|err| format!("Failed to load entries for slot {}: {}", slot, err))?;
for (entry_index, entry) in entries.iter().enumerate() {
match method {
@ -115,6 +116,7 @@ fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod)
}
}
}
Ok(())
}
fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutputMethod) {
@ -140,7 +142,9 @@ fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutp
}
}
output_slot(&blockstore, slot, &method);
if let Err(err) = output_slot(&blockstore, slot, &method) {
eprintln!("{}", err);
}
}
if method == LedgerOutputMethod::Json {
@ -616,7 +620,21 @@ fn main() {
.takes_value(true)
.multiple(true)
.required(true)
.help("List of slots to print"),
.help("Slots to print"),
)
)
.subcommand(
SubCommand::with_name("set-dead-slot")
.about("Mark one or more slots dead")
.arg(
Arg::with_name("slots")
.index(1)
.value_name("SLOTS")
.validator(is_slot)
.takes_value(true)
.multiple(true)
.required(true)
.help("Slots to mark dead"),
)
)
.subcommand(
@ -824,13 +842,12 @@ fn main() {
}
("slot", Some(arg_matches)) => {
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
let blockstore = open_blockstore(&ledger_path);
for slot in slots {
println!("Slot {}", slot);
output_slot(
&open_blockstore(&ledger_path),
slot,
&LedgerOutputMethod::Print,
);
if let Err(err) = output_slot(&blockstore, slot, &LedgerOutputMethod::Print) {
eprintln!("{}", err);
}
}
}
("json", Some(arg_matches)) => {
@ -841,6 +858,16 @@ fn main() {
LedgerOutputMethod::Json,
);
}
("set-dead-slot", Some(arg_matches)) => {
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
let blockstore = open_blockstore(&ledger_path);
for slot in slots {
match blockstore.set_dead_slot(slot) {
Ok(_) => println!("Slot {} dead", slot),
Err(err) => eprintln!("Failed to set slot {} dead slot: {}", slot, err),
}
}
}
("verify", Some(arg_matches)) => {
let process_options = ProcessOptions {
dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(),

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,
)?;
@ -568,6 +570,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>
@ -599,12 +602,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(), account_paths, &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

@ -298,7 +298,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

@ -17,13 +17,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};
@ -547,7 +548,7 @@ fn test_listener_startup() {
#[test]
#[serial]
fn test_softlaunch_operating_mode() {
fn test_stable_operating_mode() {
solana_logger::setup();
let config = ClusterConfig {
@ -566,7 +567,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(),
@ -586,7 +587,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(),
@ -606,6 +607,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.2"
fnv = "1.0.6"
fs_extra = "1.1.0"
itertools = "0.8.2"
lazy_static = "1.4.0"
libc = "0.2.66"
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

@ -59,15 +59,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);
@ -79,14 +73,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>,
local_paths: &[PathBuf],
append_vecs_path: P,
) -> std::result::Result<(), IOError> {
self.accounts_db
.accounts_from_stream(stream, local_paths, 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
@ -1393,10 +1421,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, &daccounts_paths, 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 {
@ -440,7 +453,7 @@ pub struct AccountsDB {
write_version: AtomicUsize,
/// Set of storage paths to pick from
paths: RwLock<Vec<PathBuf>>,
paths: Vec<PathBuf>,
/// Directory of paths this accounts_db needs to hold/remove
temp_paths: Option<Vec<TempDir>>,
@ -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,
@ -469,7 +485,7 @@ impl Default for AccountsDB {
storage: RwLock::new(AccountStorage(HashMap::new())),
next_id: AtomicUsize::new(0),
write_version: AtomicUsize::new(0),
paths: RwLock::new(vec![]),
paths: vec![],
temp_paths: None,
file_size: DEFAULT_FILE_SIZE,
thread_pool: rayon::ThreadPoolBuilder::new()
@ -478,6 +494,7 @@ impl Default for AccountsDB {
.unwrap(),
min_num_stores: num_threads,
bank_hashes: RwLock::new(bank_hashes),
frozen_accounts: HashMap::new(),
}
}
}
@ -486,23 +503,22 @@ impl AccountsDB {
pub fn new(paths: Vec<PathBuf>) -> Self {
let new = if !paths.is_empty() {
Self {
paths: RwLock::new(paths),
paths,
temp_paths: None,
..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 {
paths: RwLock::new(paths),
paths,
temp_paths: Some(temp_dirs),
..Self::default()
}
};
{
let paths = new.paths.read().unwrap();
for path in paths.iter() {
for path in new.paths.iter() {
std::fs::create_dir_all(path).expect("Create directory failed.");
}
}
@ -527,8 +543,7 @@ impl AccountsDB {
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
&self,
mut stream: &mut BufReader<R>,
local_account_paths: &[PathBuf],
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()))?;
@ -542,8 +557,8 @@ impl AccountsDB {
.map(|(slot_id, mut slot_storage)| {
let mut new_slot_storage = HashMap::new();
for (id, storage_entry) in slot_storage.drain() {
let path_index = thread_rng().gen_range(0, local_account_paths.len());
let local_dir = &local_account_paths[path_index];
let path_index = thread_rng().gen_range(0, self.paths.len());
let local_dir = &self.paths[path_index];
std::fs::create_dir_all(local_dir).expect("Create directory failed");
@ -551,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();
@ -602,7 +618,6 @@ impl AccountsDB {
self.bank_hashes.write().unwrap().insert(slot, bank_hash);
// Process deserialized data, set necessary fields in self
*self.paths.write().unwrap() = local_account_paths.to_vec();
let max_id: usize = *storage
.0
.values()
@ -938,22 +953,14 @@ impl AccountsDB {
}
fn create_and_insert_store(&self, slot_id: Slot, size: u64) -> Arc<AccountStorageEntry> {
let path_index = thread_rng().gen_range(0, self.paths.len());
let store =
Arc::new(self.new_storage_entry(slot_id, &Path::new(&self.paths[path_index]), size));
let store_for_index = store.clone();
let mut stores = self.storage.write().unwrap();
let slot_storage = stores.0.entry(slot_id).or_insert_with(HashMap::new);
self.create_store(slot_id, slot_storage, size)
}
fn create_store(
&self,
slot_id: Slot,
slot_storage: &mut SlotStores,
size: u64,
) -> Arc<AccountStorageEntry> {
let paths = self.paths.read().unwrap();
let path_index = thread_rng().gen_range(0, paths.len());
let store = Arc::new(self.new_storage_entry(slot_id, &Path::new(&paths[path_index]), size));
slot_storage.insert(store.id, store.clone());
slot_storage.insert(store.id, store_for_index);
store
}
@ -987,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,
@ -1398,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);
}
@ -2332,12 +2408,11 @@ pub mod tests {
let buf = writer.into_inner();
let mut reader = BufReader::new(&buf[..]);
let daccounts = AccountsDB::new(Vec::new());
let local_paths = daccounts.paths.read().unwrap().clone();
let copied_accounts = TempDir::new().unwrap();
// Simulate obtaining a copy of the AppendVecs from a tarball
copy_append_vecs(&accounts, copied_accounts.path()).unwrap();
daccounts
.accounts_from_stream(&mut reader, &local_paths, copied_accounts.path())
.accounts_from_stream(&mut reader, copied_accounts.path())
.unwrap();
print_count_and_status("daccounts", &daccounts);
@ -2724,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,31 +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>,
local_paths: &[PathBuf],
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, local_paths, append_vecs_path)?;
Ok(())
})
}
pub fn get_snapshot_storages(&self, slot: Slot) -> SnapshotStorages {
@ -359,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
{
@ -4764,14 +4772,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, &dbank_paths, 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

@ -109,7 +109,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,6 +1,7 @@
use bzip2::bufread::BzDecoder;
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 console::Emoji;
use indicatif::{ProgressBar, ProgressStyle};
@ -858,6 +859,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));
@ -932,6 +944,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()
};