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)", "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)", "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)", "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)", "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)", "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)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -626,9 +626,7 @@ fn test_stake_authorize() {
config.signers.pop(); config.signers.pop();
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: false, sign_only: false,
blockhash_query: BlockhashQuery::default(), blockhash_query: BlockhashQuery::default(),
nonce_account: None, nonce_account: None,
@ -644,13 +642,40 @@ fn test_stake_authorize() {
}; };
assert_eq!(current_authority, online_authority_pubkey); 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.signers.push(&online_authority);
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: offline_authority_pubkey, new_authorizations: vec![
stake_authorize: StakeAuthorize::Staker, (StakeAuthorize::Staker, online_authority2_pubkey, 1),
authority: 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, sign_only: false,
blockhash_query: BlockhashQuery::default(), blockhash_query: BlockhashQuery::default(),
nonce_account: None, nonce_account: None,
@ -672,9 +697,7 @@ fn test_stake_authorize() {
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::StakeAuthorize { config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: nonced_authority_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: true, sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -688,9 +711,7 @@ fn test_stake_authorize() {
config.signers = vec![&offline_presigner]; config.signers = vec![&offline_presigner];
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: nonced_authority_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: false, sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None, nonce_account: None,
@ -732,9 +753,7 @@ fn test_stake_authorize() {
config_offline.signers.push(&nonced_authority); config_offline.signers.push(&nonced_authority);
config_offline.command = CliCommand::StakeAuthorize { config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
stake_authorize: StakeAuthorize::Staker,
authority: 1,
sign_only: true, sign_only: true,
blockhash_query: BlockhashQuery::None(nonce_hash), blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_account.pubkey()), nonce_account: Some(nonce_account.pubkey()),
@ -750,9 +769,7 @@ fn test_stake_authorize() {
config.signers = vec![&offline_presigner, &nonced_authority_presigner]; config.signers = vec![&offline_presigner, &nonced_authority_presigner];
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: online_authority_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
stake_authorize: StakeAuthorize::Staker,
authority: 1,
sign_only: false, sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()), 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.signers = vec![&default_signer, &payer_keypair];
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: offline_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: false, sign_only: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -879,9 +894,7 @@ fn test_stake_authorize_with_fee_payer() {
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::StakeAuthorize { config_offline.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: payer_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: true, sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -895,9 +908,7 @@ fn test_stake_authorize_with_fee_payer() {
config.signers = vec![&offline_presigner]; config.signers = vec![&offline_presigner];
config.command = CliCommand::StakeAuthorize { config.command = CliCommand::StakeAuthorize {
stake_account_pubkey, stake_account_pubkey,
new_authorized_pubkey: payer_pubkey, new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
stake_authorize: StakeAuthorize::Staker,
authority: 0,
sign_only: false, sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None, nonce_account: None,

View File

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

View File

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

View File

@ -10,6 +10,7 @@
* [Paper Wallet Usage](paper-wallet/usage.md) * [Paper Wallet Usage](paper-wallet/usage.md)
* [Generate Keys](cli/generate-keys.md) * [Generate Keys](cli/generate-keys.md)
* [Send and Receive Tokens](cli/transfer-tokens.md) * [Send and Receive Tokens](cli/transfer-tokens.md)
* [Delegate Stake](cli/delegate-stake.md)
* [Offline Signing](offline-signing/README.md) * [Offline Signing](offline-signing/README.md)
* [Durable Transaction Nonces](offline-signing/durable-nonce.md) * [Durable Transaction Nonces](offline-signing/durable-nonce.md)
* [Command-line Reference](cli/usage.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 ```bash
// Command // Command
$ solana address $ solana-keygen pubkey
// Return // Return
<PUBKEY> <PUBKEY>

View File

@ -1,5 +1,9 @@
# Command-line Guide # Command-line Guide
This section describes the command-line tools for interacting with Solana. One In this section, we will describe how to create a Solana *wallet*, how to send
could use these tools to send payments, stake validators, and check account and receive tokens, and how to participate in the cluster by delegating stake.
balances. 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 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 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 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 and contrast different types of wallets and help you choose the wallet that
best fits your needs. 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 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 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 many keys as you would like and trivially back them up by copying the files. It
insecure because the keypair files are **unencrypted**. If you are the only 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 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 is a fine solution for small amounts of cryptocurrency. If, however, your
computer contains malware and is connected to the Internet, that malware may 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? ## Which Wallet is Best?
Different people will have different needs, but if you are still unsure what's Different people will have different needs, but if you are still unsure what
best for you after reading the comparisons above, go with a is best for you after reading the comparisons above, go with a
[Ledger Nano S](https://shop.ledger.com/products/ledger-nano-s). The [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) [Nano S is well-integrated into Solana's tool suite](../remote-wallet/ledger)
and offers an outstanding blend of security and convenience. 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 # Generate a Keypair and its Public Key
In this section, we'll generate a keypair, query it for its public key, 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'll need and verify you control its private key. Before you begin, you will need
to: to:
* [Install the Solana Tool Suite](../install-solana.md) * [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 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 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 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 [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 for each keypair you generated. The public key is a long string of base58
characters. Its length varies from 32 to 44 characters. 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. to learn more about.
The command's usage message will typically contain words such as `<AMOUNT>`, 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 `<ACCOUNT_ADDRESS>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of
you can execute the command with. For example, you can replace `<AMOUNT>` 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 with a number such as `42` or `100.42`. You can replace `<ACCOUNT_ADDRESS>` with
base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type 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 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 `~/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 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 ### Test-drive your Public Keys
@ -50,23 +50,24 @@ Try and *airdrop* yourself some play tokens on the developer testnet, called
Devnet: Devnet:
```bash ```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. Confirm the airdrop was successful by checking the account's balance.
It should output `10 SOL`: It should output `10 SOL`:
```bash ```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 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 will only accept the transfer if you sign the transaction with the private
key corresponding to the sender's public key in the transaction. 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: keypair and record its pubkey:
```bash ```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 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 ```text
============================================================================ ============================================================================
@ -83,19 +84,19 @@ pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
``` ```
```bash ```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, 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`: Confirm the updated balances with `solana balance`:
```bash ```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. recipient's public key.
## Send Tokens ## Send Tokens
@ -106,11 +107,11 @@ tokens to transfer. Once you have that collected, you can transfer tokens
with the `solana transfer` command: with the `solana transfer` command:
```bash ```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`: Confirm the updated balances with `solana balance`:
```bash ```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) * [`deactivate-stake`](../cli/usage.md#solana-deactivate-stake)
* [`delegate-stake`](../cli/usage.md#solana-delegate-stake) * [`delegate-stake`](../cli/usage.md#solana-delegate-stake)
* [`split-stake`](../cli/usage.md#solana-split-stake) * [`split-stake`](../cli/usage.md#solana-split-stake)
* [`stake-authorize-staker`](../cli/usage.md#solana-stake-authorize-staker) * [`stake-authorize`](../cli/usage.md#solana-stake-authorize)
* [`stake-authorize-withdrawer`](../cli/usage.md#solana-stake-authorize-withdrawer)
* [`stake-set-lockup`](../cli/usage.md#solana-stake-set-lockup) * [`stake-set-lockup`](../cli/usage.md#solana-stake-set-lockup)
* [`transfer`](../cli/usage.md#solana-transfer) * [`transfer`](../cli/usage.md#solana-transfer)
* [`withdraw-stake`](../cli/usage.md#solana-withdraw-stake) * [`withdraw-stake`](../cli/usage.md#solana-withdraw-stake)

View File

@ -52,7 +52,7 @@ usb://ledger/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
## Manage Multiple Hardware Wallets ## Manage Multiple Hardware Wallets
It is sometimes useful to sign a transaction with keys from multiple hardware 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 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 the fully qualified URLs of all connected hardware wallets, and ask you to
choose which wallet to use for each signature. choose which wallet to use for each signature.

View File

@ -72,81 +72,29 @@ To fix, check the following:
3. On your computer, run: 3. On your computer, run:
```text ```text
solana address --keypair usb://ledger solana-keygen pubkey usb://ledger
``` ```
This confirms your Ledger device is connected properly and in the correct state 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 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 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 you want to use. Run the same command again, but this time, with its fully
qualified URL: qualified URL:
```text ```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) [Specify A Hardware Wallet Key](index.md#specify-a-hardware-wallet-key)
### Set CLI Configuration Read more about [sending and receiving tokens](../transfer-tokens.md) and
[delegating stake](../delegate-stake.md). You can use your Ledger keypair URL
Configure the `solana` CLI tool to connect to a particular cluster: anywhere you see an option or argument that accepts a `<KEYPAIR>`.
```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>
```
## Support ## Support

View File

@ -35,14 +35,15 @@ enum LedgerOutputMethod {
Json, 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)); println!("Slot Meta {:?}", blockstore.meta(slot));
let entries = blockstore let entries = blockstore
.get_slot_entries(slot, 0, None) .get_slot_entries(slot, 0, None)
.unwrap_or_else(|err| { .map_err(|err| format!("Failed to load entries for slot {}: {}", slot, err))?;
eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
exit(1);
});
for (entry_index, entry) in entries.iter().enumerate() { for (entry_index, entry) in entries.iter().enumerate() {
match method { 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) { 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 { if method == LedgerOutputMethod::Json {
@ -616,7 +620,21 @@ fn main() {
.takes_value(true) .takes_value(true)
.multiple(true) .multiple(true)
.required(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( .subcommand(
@ -824,13 +842,12 @@ fn main() {
} }
("slot", Some(arg_matches)) => { ("slot", Some(arg_matches)) => {
let slots = values_t_or_exit!(arg_matches, "slots", Slot); let slots = values_t_or_exit!(arg_matches, "slots", Slot);
let blockstore = open_blockstore(&ledger_path);
for slot in slots { for slot in slots {
println!("Slot {}", slot); println!("Slot {}", slot);
output_slot( if let Err(err) = output_slot(&blockstore, slot, &LedgerOutputMethod::Print) {
&open_blockstore(&ledger_path), eprintln!("{}", err);
slot, }
&LedgerOutputMethod::Print,
);
} }
} }
("json", Some(arg_matches)) => { ("json", Some(arg_matches)) => {
@ -841,6 +858,16 @@ fn main() {
LedgerOutputMethod::Json, 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)) => { ("verify", Some(arg_matches)) => {
let process_options = ProcessOptions { let process_options = ProcessOptions {
dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(), 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( let deserialized_bank = snapshot_utils::bank_from_archive(
&account_paths, &account_paths,
&process_options.frozen_accounts,
&snapshot_config.snapshot_path, &snapshot_config.snapshot_path,
&archive_filename, &archive_filename,
) )

View File

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

View File

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

View File

@ -298,7 +298,7 @@ impl LocalCluster {
self.exit(); self.exit();
for (_, node) in self.validators.iter_mut() { for (_, node) in self.validators.iter_mut() {
if let Some(v) = node.validator.take() { 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}, local_cluster::{ClusterConfig, LocalCluster},
}; };
use solana_sdk::{ use solana_sdk::{
client::SyncClient, client::{AsyncClient, SyncClient},
clock::{self, Slot}, clock::{self, Slot},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH}, epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
genesis_config::OperatingMode, genesis_config::OperatingMode,
hash::Hash, hash::Hash,
poh_config::PohConfig, poh_config::PohConfig,
pubkey::Pubkey,
signature::{Keypair, Signer}, signature::{Keypair, Signer},
}; };
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -547,7 +548,7 @@ fn test_listener_startup() {
#[test] #[test]
#[serial] #[serial]
fn test_softlaunch_operating_mode() { fn test_stable_operating_mode() {
solana_logger::setup(); solana_logger::setup();
let config = ClusterConfig { let config = ClusterConfig {
@ -566,7 +567,7 @@ fn test_softlaunch_operating_mode() {
solana_core::cluster_info::VALIDATOR_PORT_RANGE, 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 [ for program_id in [
&solana_config_program::id(), &solana_config_program::id(),
&solana_sdk::system_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 [ for program_id in [
&solana_sdk::bpf_loader::id(), &solana_sdk::bpf_loader::id(),
&solana_storage_program::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] #[test]
#[serial] #[serial]
fn test_consistency_halt() { fn test_consistency_halt() {

View File

@ -15,6 +15,7 @@ byteorder = "1.3.2"
fnv = "1.0.6" fnv = "1.0.6"
fs_extra = "1.1.0" fs_extra = "1.1.0"
itertools = "0.8.2" itertools = "0.8.2"
lazy_static = "1.4.0"
libc = "0.2.66" libc = "0.2.66"
libloading = "0.5.2" libloading = "0.5.2"
log = "0.4.8" log = "0.4.8"

View File

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

View File

@ -59,15 +59,9 @@ pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, Trans
impl Accounts { impl Accounts {
pub fn new(paths: Vec<PathBuf>) -> Self { 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 { pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
let accounts_db = parent.accounts_db.clone(); let accounts_db = parent.accounts_db.clone();
accounts_db.set_hash(slot, parent_slot); accounts_db.set_hash(slot, parent_slot);
@ -79,14 +73,48 @@ impl Accounts {
} }
} }
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>( pub fn new_with_frozen_accounts(
&self, 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>, stream: &mut BufReader<R>,
local_paths: &[PathBuf], stream_append_vecs_path: P,
append_vecs_path: P, ) -> std::result::Result<Self, IOError> {
) -> std::result::Result<(), IOError> { let mut accounts_db = AccountsDB::new(account_paths.to_vec());
self.accounts_db accounts_db.accounts_from_stream(stream, stream_append_vecs_path)?;
.accounts_from_stream(stream, local_paths, 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 /// Return true if the slice has any duplicate elements
@ -1393,10 +1421,14 @@ mod tests {
let buf = writer.into_inner(); let buf = writer.into_inner();
let mut reader = BufReader::new(&buf[..]); let mut reader = BufReader::new(&buf[..]);
let (_accounts_dir, daccounts_paths) = get_temp_accounts_paths(2).unwrap(); let (_accounts_dir, daccounts_paths) = get_temp_accounts_paths(2).unwrap();
let daccounts = Accounts::new(daccounts_paths.clone()); let daccounts = Accounts::from_stream(
assert!(daccounts &daccounts_paths,
.accounts_from_stream(&mut reader, &daccounts_paths, copied_accounts.path()) &HashMap::default(),
.is_ok()); &[],
&mut reader,
copied_accounts.path(),
)
.unwrap();
check_accounts(&daccounts, &pubkeys, 100); check_accounts(&daccounts, &pubkeys, 100);
assert_eq!(accounts.bank_hash_at(0), daccounts.bank_hash_at(0)); 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 bincode::{deserialize_from, serialize_into};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use fs_extra::dir::CopyOptions; use fs_extra::dir::CopyOptions;
use lazy_static::lazy_static;
use log::*; use log::*;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use rayon::{prelude::*, ThreadPool}; 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_THREADS: u32 = 8;
pub const DEFAULT_NUM_DIRS: u32 = 4; 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)] #[derive(Debug, Default)]
pub struct ErrorCounters { pub struct ErrorCounters {
pub total: usize, pub total: usize,
@ -426,6 +433,12 @@ pub struct BankHashInfo {
pub stats: BankHashStats, 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 // This structure handles the load/store of the accounts
#[derive(Debug)] #[derive(Debug)]
pub struct AccountsDB { pub struct AccountsDB {
@ -440,7 +453,7 @@ pub struct AccountsDB {
write_version: AtomicUsize, write_version: AtomicUsize,
/// Set of storage paths to pick from /// Set of storage paths to pick from
paths: RwLock<Vec<PathBuf>>, paths: Vec<PathBuf>,
/// Directory of paths this accounts_db needs to hold/remove /// Directory of paths this accounts_db needs to hold/remove
temp_paths: Option<Vec<TempDir>>, temp_paths: Option<Vec<TempDir>>,
@ -448,6 +461,9 @@ pub struct AccountsDB {
/// Starting file size of appendvecs /// Starting file size of appendvecs
file_size: u64, 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 /// Thread pool used for par_iter
pub thread_pool: ThreadPool, pub thread_pool: ThreadPool,
@ -469,7 +485,7 @@ impl Default for AccountsDB {
storage: RwLock::new(AccountStorage(HashMap::new())), storage: RwLock::new(AccountStorage(HashMap::new())),
next_id: AtomicUsize::new(0), next_id: AtomicUsize::new(0),
write_version: AtomicUsize::new(0), write_version: AtomicUsize::new(0),
paths: RwLock::new(vec![]), paths: vec![],
temp_paths: None, temp_paths: None,
file_size: DEFAULT_FILE_SIZE, file_size: DEFAULT_FILE_SIZE,
thread_pool: rayon::ThreadPoolBuilder::new() thread_pool: rayon::ThreadPoolBuilder::new()
@ -478,6 +494,7 @@ impl Default for AccountsDB {
.unwrap(), .unwrap(),
min_num_stores: num_threads, min_num_stores: num_threads,
bank_hashes: RwLock::new(bank_hashes), bank_hashes: RwLock::new(bank_hashes),
frozen_accounts: HashMap::new(),
} }
} }
} }
@ -486,23 +503,22 @@ impl AccountsDB {
pub fn new(paths: Vec<PathBuf>) -> Self { pub fn new(paths: Vec<PathBuf>) -> Self {
let new = if !paths.is_empty() { let new = if !paths.is_empty() {
Self { Self {
paths: RwLock::new(paths), paths,
temp_paths: None, temp_paths: None,
..Self::default() ..Self::default()
} }
} else { } else {
// Create a temprorary set of accounts directories, used primarily // Create a temporary set of accounts directories, used primarily
// for testing // for testing
let (temp_dirs, paths) = get_temp_accounts_paths(DEFAULT_NUM_DIRS).unwrap(); let (temp_dirs, paths) = get_temp_accounts_paths(DEFAULT_NUM_DIRS).unwrap();
Self { Self {
paths: RwLock::new(paths), paths,
temp_paths: Some(temp_dirs), temp_paths: Some(temp_dirs),
..Self::default() ..Self::default()
} }
}; };
{ {
let paths = new.paths.read().unwrap(); for path in new.paths.iter() {
for path in paths.iter() {
std::fs::create_dir_all(path).expect("Create directory failed."); 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>>( pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
&self, &self,
mut stream: &mut BufReader<R>, mut stream: &mut BufReader<R>,
local_account_paths: &[PathBuf], stream_append_vecs_path: P,
append_vecs_path: P,
) -> Result<(), IOError> { ) -> Result<(), IOError> {
let _len: usize = let _len: usize =
deserialize_from(&mut stream).map_err(|e| AccountsDB::get_io_error(&e.to_string()))?; 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)| { .map(|(slot_id, mut slot_storage)| {
let mut new_slot_storage = HashMap::new(); let mut new_slot_storage = HashMap::new();
for (id, storage_entry) in slot_storage.drain() { for (id, storage_entry) in slot_storage.drain() {
let path_index = thread_rng().gen_range(0, local_account_paths.len()); let path_index = thread_rng().gen_range(0, self.paths.len());
let local_dir = &local_account_paths[path_index]; let local_dir = &self.paths[path_index];
std::fs::create_dir_all(local_dir).expect("Create directory failed"); std::fs::create_dir_all(local_dir).expect("Create directory failed");
@ -551,8 +566,9 @@ impl AccountsDB {
// at by `local_dir` // at by `local_dir`
let append_vec_relative_path = let append_vec_relative_path =
AppendVec::new_relative_path(slot_id, storage_entry.id); AppendVec::new_relative_path(slot_id, storage_entry.id);
let append_vec_abs_path = let append_vec_abs_path = stream_append_vecs_path
append_vecs_path.as_ref().join(&append_vec_relative_path); .as_ref()
.join(&append_vec_relative_path);
let target = local_dir.join(append_vec_abs_path.file_name().unwrap()); let target = local_dir.join(append_vec_abs_path.file_name().unwrap());
if std::fs::rename(append_vec_abs_path.clone(), target).is_err() { if std::fs::rename(append_vec_abs_path.clone(), target).is_err() {
let mut copy_options = CopyOptions::new(); let mut copy_options = CopyOptions::new();
@ -602,7 +618,6 @@ impl AccountsDB {
self.bank_hashes.write().unwrap().insert(slot, bank_hash); self.bank_hashes.write().unwrap().insert(slot, bank_hash);
// Process deserialized data, set necessary fields in self // Process deserialized data, set necessary fields in self
*self.paths.write().unwrap() = local_account_paths.to_vec();
let max_id: usize = *storage let max_id: usize = *storage
.0 .0
.values() .values()
@ -938,22 +953,14 @@ impl AccountsDB {
} }
fn create_and_insert_store(&self, slot_id: Slot, size: u64) -> Arc<AccountStorageEntry> { 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 mut stores = self.storage.write().unwrap();
let slot_storage = stores.0.entry(slot_id).or_insert_with(HashMap::new); let slot_storage = stores.0.entry(slot_id).or_insert_with(HashMap::new);
slot_storage.insert(store.id, store_for_index);
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());
store 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( pub fn hash_account_data(
slot: Slot, slot: Slot,
lamports: u64, lamports: u64,
@ -1398,8 +1420,62 @@ impl AccountsDB {
hashes 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. /// Store the account update.
pub fn store(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) { pub fn store(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) {
self.assert_frozen_accounts(accounts);
let hashes = self.hash_accounts(slot_id, accounts); let hashes = self.hash_accounts(slot_id, accounts);
self.store_with_hashes(slot_id, accounts, &hashes); self.store_with_hashes(slot_id, accounts, &hashes);
} }
@ -2332,12 +2408,11 @@ pub mod tests {
let buf = writer.into_inner(); let buf = writer.into_inner();
let mut reader = BufReader::new(&buf[..]); let mut reader = BufReader::new(&buf[..]);
let daccounts = AccountsDB::new(Vec::new()); let daccounts = AccountsDB::new(Vec::new());
let local_paths = daccounts.paths.read().unwrap().clone();
let copied_accounts = TempDir::new().unwrap(); let copied_accounts = TempDir::new().unwrap();
// Simulate obtaining a copy of the AppendVecs from a tarball // Simulate obtaining a copy of the AppendVecs from a tarball
copy_append_vecs(&accounts, copied_accounts.path()).unwrap(); copy_append_vecs(&accounts, copied_accounts.path()).unwrap();
daccounts daccounts
.accounts_from_stream(&mut reader, &local_paths, copied_accounts.path()) .accounts_from_stream(&mut reader, copied_accounts.path())
.unwrap(); .unwrap();
print_count_and_status("daccounts", &daccounts); print_count_and_status("daccounts", &daccounts);
@ -2724,6 +2799,139 @@ pub mod tests {
Ok(()) 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] #[test]
fn test_hash_stored_account() { fn test_hash_stored_account() {
// This test uses some UNSAFE trick to detect most of account's field // This test uses some UNSAFE trick to detect most of account's field

View File

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

View File

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

View File

@ -1,6 +1,7 @@
use bzip2::bufread::BzDecoder; use bzip2::bufread::BzDecoder;
use clap::{ 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 console::Emoji;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
@ -858,6 +859,17 @@ pub fn main() {
.takes_value(false) .takes_value(false)
.help("Abort the validator if a bank hash mismatch is detected within trusted validator set"), .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(); .get_matches();
let identity_keypair = Arc::new(keypair_of(&matches, "identity").unwrap_or_else(Keypair::new)); 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"), voting_disabled: matches.is_present("no_voting"),
wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(), wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(),
trusted_validators, trusted_validators,
frozen_accounts: values_t!(matches, "frozen_accounts", Pubkey).unwrap_or_default(),
..ValidatorConfig::default() ..ValidatorConfig::default()
}; };