1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4460,6 +4460,7 @@ dependencies = [
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -301,9 +301,7 @@ pub enum CliCommand {
|
||||
},
|
||||
StakeAuthorize {
|
||||
stake_account_pubkey: Pubkey,
|
||||
new_authorized_pubkey: Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
authority: SignerIndex,
|
||||
new_authorizations: Vec<(StakeAuthorize, Pubkey, SignerIndex)>,
|
||||
sign_only: bool,
|
||||
blockhash_query: BlockhashQuery,
|
||||
nonce_account: Option<Pubkey>,
|
||||
@ -644,18 +642,9 @@ pub fn parse_command(
|
||||
("split-stake", Some(matches)) => {
|
||||
parse_split_stake(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("stake-authorize-staker", Some(matches)) => parse_stake_authorize(
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
StakeAuthorize::Staker,
|
||||
),
|
||||
("stake-authorize-withdrawer", Some(matches)) => parse_stake_authorize(
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
StakeAuthorize::Withdrawer,
|
||||
),
|
||||
("stake-authorize", Some(matches)) => {
|
||||
parse_stake_authorize(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("stake-set-lockup", Some(matches)) => {
|
||||
parse_stake_set_lockup(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
@ -1825,9 +1814,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
}
|
||||
CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey,
|
||||
stake_authorize,
|
||||
authority,
|
||||
ref new_authorizations,
|
||||
sign_only,
|
||||
blockhash_query,
|
||||
nonce_account,
|
||||
@ -1837,9 +1824,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
&rpc_client,
|
||||
config,
|
||||
&stake_account_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
*stake_authorize,
|
||||
*authority,
|
||||
new_authorizations,
|
||||
*sign_only,
|
||||
blockhash_query,
|
||||
*nonce_account,
|
||||
@ -2264,10 +2249,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The pubkey of airdrop recipient"),
|
||||
.help("The account address of airdrop recipient"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2276,10 +2261,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The public key of the balance to check"),
|
||||
.help("The account address of the balance to check"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
@ -2294,11 +2279,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(1)
|
||||
.value_name("PROCESS_PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The process id of the transfer to cancel"),
|
||||
.help("The account address of the transfer to cancel"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2327,7 +2312,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("program_id")
|
||||
.index(2)
|
||||
.value_name("PROGRAM_PUBKEY")
|
||||
.value_name("PROGRAM_ID")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(
|
||||
@ -2363,11 +2348,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
@ -2432,19 +2417,19 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(2)
|
||||
.value_name("PROCESS_PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The process id of the transfer to authorize"),
|
||||
.help("The account address of the transfer to authorize"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2453,19 +2438,19 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(2)
|
||||
.value_name("PROCESS_PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The process id of the transfer to unlock"),
|
||||
.help("The account address of the transfer to unlock"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("datetime")
|
||||
@ -2481,11 +2466,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
@ -2516,7 +2501,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
|
@ -95,7 +95,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -156,7 +156,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -169,7 +169,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -184,7 +184,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -203,7 +203,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -212,7 +212,7 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("destination_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
|
506
cli/src/stake.rs
506
cli/src/stake.rs
@ -160,7 +160,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -169,7 +169,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("VOTE_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -182,53 +182,36 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(fee_payer_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-authorize-staker")
|
||||
.about("Authorize a new stake signing keypair for the given stake account")
|
||||
SubCommand::with_name("stake-authorize")
|
||||
.about("Authorize a new signing keypair for the given stake account")
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Stake account in which to set the authorized staker")
|
||||
.help("Stake account in which to set a new authority")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_pubkey")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
Arg::with_name("new_stake_authority")
|
||||
.long("new-stake-authority")
|
||||
.required_unless("new_withdraw_authority")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.value_name("PUBKEY")
|
||||
.validator(is_valid_pubkey)
|
||||
.help("New authorized staker")
|
||||
)
|
||||
.arg(stake_authority_arg())
|
||||
.offline_args()
|
||||
.arg(nonce_arg())
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(fee_payer_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-authorize-withdrawer")
|
||||
.about("Authorize a new withdraw signing keypair for the given stake account")
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
Arg::with_name("new_withdraw_authority")
|
||||
.long("new-withdraw-authority")
|
||||
.required_unless("new_stake_authority")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Stake account in which to set the authorized withdrawer")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_pubkey")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.value_name("PUBKEY")
|
||||
.validator(is_valid_pubkey)
|
||||
.help("New authorized withdrawer")
|
||||
)
|
||||
.arg(stake_authority_arg())
|
||||
.arg(withdraw_authority_arg())
|
||||
.offline_args()
|
||||
.arg(nonce_arg())
|
||||
@ -241,7 +224,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -259,7 +242,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -302,7 +285,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -311,7 +294,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("destination_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -338,7 +321,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -390,7 +373,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("stake_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -515,37 +498,75 @@ pub fn parse_stake_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
stake_authorize: StakeAuthorize,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authorized_pubkey =
|
||||
pubkey_of_signer(matches, "authorized_pubkey", wallet_manager)?.unwrap();
|
||||
let authority_flag = match stake_authorize {
|
||||
StakeAuthorize::Staker => STAKE_AUTHORITY_ARG.name,
|
||||
StakeAuthorize::Withdrawer => WITHDRAW_AUTHORITY_ARG.name,
|
||||
|
||||
let mut new_authorizations = Vec::new();
|
||||
let mut bulk_signers = Vec::new();
|
||||
if let Some(new_authority_pubkey) =
|
||||
pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?
|
||||
{
|
||||
let (authority, authority_pubkey) = {
|
||||
let (authority, authority_pubkey) =
|
||||
signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
// Withdraw authority may also change the staker
|
||||
if authority.is_none() {
|
||||
signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
|
||||
} else {
|
||||
(authority, authority_pubkey)
|
||||
}
|
||||
};
|
||||
new_authorizations.push((
|
||||
StakeAuthorize::Staker,
|
||||
new_authority_pubkey,
|
||||
authority_pubkey,
|
||||
));
|
||||
bulk_signers.push(authority);
|
||||
};
|
||||
if let Some(new_authority_pubkey) =
|
||||
pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?
|
||||
{
|
||||
let (authority, authority_pubkey) =
|
||||
signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
new_authorizations.push((
|
||||
StakeAuthorize::Withdrawer,
|
||||
new_authority_pubkey,
|
||||
authority_pubkey,
|
||||
));
|
||||
bulk_signers.push(authority);
|
||||
};
|
||||
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||
let (authority, authority_pubkey) = signer_of(matches, authority_flag, wallet_manager)?;
|
||||
let blockhash_query = BlockhashQuery::new_from_matches(matches);
|
||||
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||
|
||||
let mut bulk_signers = vec![authority, fee_payer];
|
||||
bulk_signers.push(fee_payer);
|
||||
if nonce_account.is_some() {
|
||||
bulk_signers.push(nonce_authority);
|
||||
}
|
||||
let signer_info =
|
||||
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
|
||||
|
||||
let new_authorizations = new_authorizations
|
||||
.into_iter()
|
||||
.map(
|
||||
|(stake_authorize, new_authority_pubkey, authority_pubkey)| {
|
||||
(
|
||||
stake_authorize,
|
||||
new_authority_pubkey,
|
||||
signer_info.index_of(authority_pubkey).unwrap(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey,
|
||||
stake_authorize,
|
||||
authority: signer_info.index_of(authority_pubkey).unwrap(),
|
||||
new_authorizations,
|
||||
sign_only,
|
||||
blockhash_query,
|
||||
nonce_account,
|
||||
@ -871,28 +892,30 @@ pub fn process_stake_authorize(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
stake_account_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
authority: SignerIndex,
|
||||
new_authorizations: &[(StakeAuthorize, Pubkey, SignerIndex)],
|
||||
sign_only: bool,
|
||||
blockhash_query: &BlockhashQuery,
|
||||
nonce_account: Option<Pubkey>,
|
||||
nonce_authority: SignerIndex,
|
||||
fee_payer: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
check_unique_pubkeys(
|
||||
(stake_account_pubkey, "stake_account_pubkey".to_string()),
|
||||
(authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let authority = config.signers[authority];
|
||||
let mut ixs = Vec::new();
|
||||
for (stake_authorize, authorized_pubkey, authority) in new_authorizations.iter() {
|
||||
check_unique_pubkeys(
|
||||
(stake_account_pubkey, "stake_account_pubkey".to_string()),
|
||||
(authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let authority = config.signers[*authority];
|
||||
ixs.push(stake_instruction::authorize(
|
||||
stake_account_pubkey, // stake account to update
|
||||
&authority.pubkey(), // currently authorized
|
||||
authorized_pubkey, // new stake signer
|
||||
*stake_authorize, // stake or withdraw
|
||||
));
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client)?;
|
||||
let ixs = vec![stake_instruction::authorize(
|
||||
stake_account_pubkey, // stake account to update
|
||||
&authority.pubkey(), // currently authorized
|
||||
authorized_pubkey, // new stake signer
|
||||
stake_authorize, // stake or withdraw
|
||||
)];
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
@ -1236,8 +1259,8 @@ pub fn process_stake_set_lockup(
|
||||
|
||||
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
|
||||
fn show_authorized(authorized: &Authorized) {
|
||||
println!("Authorized Staker: {}", authorized.staker);
|
||||
println!("Authorized Withdrawer: {}", authorized.withdrawer);
|
||||
println!("Stake Authority: {}", authorized.staker);
|
||||
println!("Withdraw Authority: {}", authorized.withdrawer);
|
||||
}
|
||||
fn show_lockup(lockup: &Lockup) {
|
||||
println!(
|
||||
@ -1266,7 +1289,10 @@ pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamp
|
||||
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
||||
);
|
||||
if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
println!("Delegated Voter Pubkey: {}", stake.delegation.voter_pubkey);
|
||||
println!(
|
||||
"Delegated Vote Account Address: {}",
|
||||
stake.delegation.voter_pubkey
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"Stake activates starting from epoch: {}",
|
||||
@ -1486,83 +1512,279 @@ mod tests {
|
||||
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
|
||||
}
|
||||
|
||||
fn parse_authorize_tests(
|
||||
test_commands: &App,
|
||||
stake_account_pubkey: Pubkey,
|
||||
authority_keypair_file: &str,
|
||||
stake_authorize: StakeAuthorize,
|
||||
) {
|
||||
#[test]
|
||||
fn test_parse_command() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_account_keypair = Keypair::new();
|
||||
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let stake_account_pubkey = stake_account_keypair.pubkey();
|
||||
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_authority_keypair = Keypair::new();
|
||||
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
// stake-authorize subcommand
|
||||
let stake_account_string = stake_account_pubkey.to_string();
|
||||
|
||||
let (subcommand, authority_flag) = match stake_authorize {
|
||||
StakeAuthorize::Staker => ("stake-authorize-staker", "--stake-authority"),
|
||||
StakeAuthorize::Withdrawer => ("stake-authorize-withdrawer", "--withdraw-authority"),
|
||||
};
|
||||
|
||||
// Test Staker Subcommand
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
let new_stake_authority = Pubkey::new(&[1u8; 32]);
|
||||
let new_stake_string = new_stake_authority.to_string();
|
||||
let new_withdraw_authority = Pubkey::new(&[2u8; 32]);
|
||||
let new_withdraw_string = new_withdraw_authority.to_string();
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
&stake_account_string,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
"--new-withdraw-authority",
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, new_stake_authority, 0,),
|
||||
(StakeAuthorize::Withdrawer, new_withdraw_authority, 0,),
|
||||
],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
);
|
||||
// Test Staker Subcommand w/ authority
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let withdraw_authority_keypair = Keypair::new();
|
||||
write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
&stake_account_string,
|
||||
&authority_flag,
|
||||
&authority_keypair_file,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
"--new-withdraw-authority",
|
||||
&new_withdraw_string,
|
||||
"--stake-authority",
|
||||
&stake_authority_keypair_file,
|
||||
"--withdraw-authority",
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 1,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, new_stake_authority, 1,),
|
||||
(StakeAuthorize::Withdrawer, new_withdraw_authority, 2,),
|
||||
],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&authority_keypair_file).unwrap().into()
|
||||
read_keypair_file(&stake_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
read_keypair_file(&withdraw_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
],
|
||||
}
|
||||
},
|
||||
);
|
||||
// Withdraw authority may set both new authorities
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
"--new-withdraw-authority",
|
||||
&new_withdraw_string,
|
||||
"--withdraw-authority",
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, new_stake_authority, 1,),
|
||||
(StakeAuthorize::Withdrawer, new_withdraw_authority, 1,),
|
||||
],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&withdraw_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
],
|
||||
},
|
||||
);
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 0,),],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
);
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
"--stake-authority",
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 1,),],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&stake_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
],
|
||||
},
|
||||
);
|
||||
// Withdraw authority may set new stake authority
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&new_stake_string,
|
||||
"--withdraw-authority",
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, new_stake_authority, 1,),],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&withdraw_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
],
|
||||
},
|
||||
);
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-withdraw-authority",
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(
|
||||
StakeAuthorize::Withdrawer,
|
||||
new_withdraw_authority,
|
||||
0,
|
||||
),],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
);
|
||||
let test_stake_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-withdraw-authority",
|
||||
&new_withdraw_string,
|
||||
"--withdraw-authority",
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(
|
||||
StakeAuthorize::Withdrawer,
|
||||
new_withdraw_authority,
|
||||
1,
|
||||
),],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&withdraw_authority_keypair_file)
|
||||
.unwrap()
|
||||
.into(),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
// Test Authorize Subcommand w/ sign-only
|
||||
let blockhash = Hash::default();
|
||||
let blockhash_string = format!("{}", blockhash);
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--blockhash",
|
||||
&blockhash_string,
|
||||
@ -1573,9 +1795,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
@ -1592,8 +1812,9 @@ mod tests {
|
||||
let signer = format!("{}={}", keypair.pubkey(), sig);
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--blockhash",
|
||||
&blockhash_string,
|
||||
@ -1607,9 +1828,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::Cluster,
|
||||
@ -1633,8 +1852,9 @@ mod tests {
|
||||
let nonce_account = Pubkey::new(&[1u8; 32]);
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--blockhash",
|
||||
&blockhash_string,
|
||||
@ -1654,9 +1874,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account),
|
||||
@ -1676,8 +1894,9 @@ mod tests {
|
||||
// Test Authorize Subcommand w/ blockhash
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--blockhash",
|
||||
&blockhash_string,
|
||||
@ -1687,9 +1906,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::Cluster,
|
||||
@ -1710,8 +1927,9 @@ mod tests {
|
||||
let nonce_account_string = nonce_account_pubkey.to_string();
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--blockhash",
|
||||
&blockhash_string,
|
||||
@ -1725,9 +1943,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account_pubkey),
|
||||
@ -1751,8 +1967,9 @@ mod tests {
|
||||
let fee_payer_string = fee_payer_pubkey.to_string();
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--fee-payer",
|
||||
&fee_payer_keypair_file,
|
||||
@ -1762,9 +1979,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@ -1782,8 +1997,9 @@ mod tests {
|
||||
let signer = format!("{}={}", fee_payer_string, sig);
|
||||
let test_authorize = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
&subcommand,
|
||||
"stake-authorize",
|
||||
&stake_account_string,
|
||||
"--new-stake-authority",
|
||||
&stake_account_string,
|
||||
"--fee-payer",
|
||||
&fee_payer_string,
|
||||
@ -1797,9 +2013,7 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: stake_account_pubkey,
|
||||
stake_authorize,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, stake_account_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::Cluster,
|
||||
@ -1815,34 +2029,6 @@ mod tests {
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_command() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_account_keypair = Keypair::new();
|
||||
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let stake_account_pubkey = stake_account_keypair.pubkey();
|
||||
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_authority_keypair = Keypair::new();
|
||||
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
parse_authorize_tests(
|
||||
&test_commands,
|
||||
stake_account_pubkey,
|
||||
&stake_authority_keypair_file,
|
||||
StakeAuthorize::Staker,
|
||||
);
|
||||
parse_authorize_tests(
|
||||
&test_commands,
|
||||
stake_account_pubkey,
|
||||
&stake_authority_keypair_file,
|
||||
StakeAuthorize::Withdrawer,
|
||||
);
|
||||
|
||||
// Test CreateStakeAccount SubCommand
|
||||
let custodian = Pubkey::new_rand();
|
||||
|
@ -66,7 +66,7 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("node_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NODE_PUBKEY")
|
||||
.value_name("NODE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -75,7 +75,7 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("storage_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STORAGE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -89,11 +89,11 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("storage_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("STORAGE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Storage account pubkey"),
|
||||
.help("Storage account address"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -108,7 +108,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -130,7 +130,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -170,7 +170,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -189,7 +189,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
@ -198,7 +198,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("destination_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
|
@ -626,9 +626,7 @@ fn test_stake_authorize() {
|
||||
config.signers.pop();
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
@ -644,13 +642,40 @@ fn test_stake_authorize() {
|
||||
};
|
||||
assert_eq!(current_authority, online_authority_pubkey);
|
||||
|
||||
// Assign new offline stake authority
|
||||
// Assign new online stake and withdraw authorities
|
||||
let online_authority2 = Keypair::new();
|
||||
let online_authority2_pubkey = online_authority2.pubkey();
|
||||
let withdraw_authority = Keypair::new();
|
||||
let withdraw_authority_pubkey = withdraw_authority.pubkey();
|
||||
config.signers.push(&online_authority);
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: offline_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, online_authority2_pubkey, 1),
|
||||
(StakeAuthorize::Withdrawer, withdraw_authority_pubkey, 0),
|
||||
],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let (current_staker, current_withdrawer) = match stake_state {
|
||||
StakeState::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
|
||||
_ => panic!("Unexpected stake state!"),
|
||||
};
|
||||
assert_eq!(current_staker, online_authority2_pubkey);
|
||||
assert_eq!(current_withdrawer, withdraw_authority_pubkey);
|
||||
|
||||
// Assign new offline stake authority
|
||||
config.signers.pop();
|
||||
config.signers.push(&online_authority2);
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
@ -672,9 +697,7 @@ fn test_stake_authorize() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: nonced_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
@ -688,9 +711,7 @@ fn test_stake_authorize() {
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: nonced_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
@ -732,9 +753,7 @@ fn test_stake_authorize() {
|
||||
config_offline.signers.push(&nonced_authority);
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
@ -750,9 +769,7 @@ fn test_stake_authorize() {
|
||||
config.signers = vec![&offline_presigner, &nonced_authority_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
@ -859,9 +876,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config.signers = vec![&default_signer, &payer_keypair];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: offline_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@ -879,9 +894,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: payer_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
@ -895,9 +908,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: payer_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
|
@ -78,6 +78,7 @@ pub struct ValidatorConfig {
|
||||
pub trusted_validators: Option<HashSet<Pubkey>>, // None = trust all
|
||||
pub halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
pub accounts_hash_fault_injection_slots: u64, // 0 = no fault injection
|
||||
pub frozen_accounts: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl Default for ValidatorConfig {
|
||||
@ -103,6 +104,7 @@ impl Default for ValidatorConfig {
|
||||
trusted_validators: None,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: false,
|
||||
accounts_hash_fault_injection_slots: 0,
|
||||
frozen_accounts: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -594,6 +596,7 @@ fn new_banks_from_blockstore(
|
||||
poh_verify,
|
||||
dev_halt_at_slot: config.dev_halt_at_slot,
|
||||
new_hard_forks: config.new_hard_forks.clone(),
|
||||
frozen_accounts: config.frozen_accounts.clone(),
|
||||
..blockstore_processor::ProcessOptions::default()
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,7 @@ mod tests {
|
||||
let bank0 = Bank::new_with_paths(
|
||||
&genesis_config_info.genesis_config,
|
||||
vec![accounts_dir.path().to_path_buf()],
|
||||
&[],
|
||||
);
|
||||
bank0.freeze();
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
@ -82,6 +83,7 @@ mod tests {
|
||||
|
||||
let deserialized_bank = snapshot_utils::bank_from_archive(
|
||||
&account_paths,
|
||||
&[],
|
||||
&old_bank_forks
|
||||
.snapshot_config
|
||||
.as_ref()
|
||||
|
@ -10,6 +10,7 @@
|
||||
* [Paper Wallet Usage](paper-wallet/usage.md)
|
||||
* [Generate Keys](cli/generate-keys.md)
|
||||
* [Send and Receive Tokens](cli/transfer-tokens.md)
|
||||
* [Delegate Stake](cli/delegate-stake.md)
|
||||
* [Offline Signing](offline-signing/README.md)
|
||||
* [Durable Transaction Nonces](offline-signing/durable-nonce.md)
|
||||
* [Command-line Reference](cli/usage.md)
|
||||
|
@ -8,7 +8,7 @@ The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-l
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana address
|
||||
$ solana-keygen pubkey
|
||||
|
||||
// Return
|
||||
<PUBKEY>
|
||||
|
@ -1,5 +1,9 @@
|
||||
# Command-line Guide
|
||||
|
||||
This section describes the command-line tools for interacting with Solana. One
|
||||
could use these tools to send payments, stake validators, and check account
|
||||
balances.
|
||||
In this section, we will describe how to create a Solana *wallet*, how to send
|
||||
and receive tokens, and how to participate in the cluster by delegating stake.
|
||||
To interact with a Solana cluster, we will use its command-line interface, also
|
||||
known as the CLI. We use the command-line because it is the first place the
|
||||
Solana core team deploys new functionality. The command-line interface is not
|
||||
necessarily the easiest to use, but it provides the most direct, flexible, and
|
||||
secure access to your Solana accounts.
|
||||
|
@ -3,7 +3,7 @@
|
||||
Keypairs are stored in *wallets* and wallets come in many forms. A wallet might
|
||||
be a directory in your computer's file system, a piece of paper, or a
|
||||
specialized device called a *hardware wallet*. Some wallets are easier to use
|
||||
than others. Some are more secure than others. In this section, we'll compare
|
||||
than others. Some are more secure than others. In this section, we will compare
|
||||
and contrast different types of wallets and help you choose the wallet that
|
||||
best fits your needs.
|
||||
|
||||
@ -16,8 +16,8 @@ file system. Each file in the directory holds a keypair.
|
||||
|
||||
An FS wallet is the most convenient and least secure form of wallet. It is
|
||||
convenient because the keypair is stored in a simple file. You can generate as
|
||||
many keys as you'd like and trivially back them up by copying the files. It is
|
||||
insecure because the keypair files are **unencrypted**. If you are the only
|
||||
many keys as you would like and trivially back them up by copying the files. It
|
||||
is insecure because the keypair files are **unencrypted**. If you are the only
|
||||
user of your computer and you are confident it is free of malware, an FS wallet
|
||||
is a fine solution for small amounts of cryptocurrency. If, however, your
|
||||
computer contains malware and is connected to the Internet, that malware may
|
||||
@ -72,8 +72,8 @@ To keep your hardware wallet tokens safe, we suggest:
|
||||
|
||||
## Which Wallet is Best?
|
||||
|
||||
Different people will have different needs, but if you are still unsure what's
|
||||
best for you after reading the comparisons above, go with a
|
||||
Different people will have different needs, but if you are still unsure what
|
||||
is best for you after reading the comparisons above, go with a
|
||||
[Ledger Nano S](https://shop.ledger.com/products/ledger-nano-s). The
|
||||
[Nano S is well-integrated into Solana's tool suite](../remote-wallet/ledger)
|
||||
and offers an outstanding blend of security and convenience.
|
||||
|
197
docs/src/cli/delegate-stake.md
Normal file
197
docs/src/cli/delegate-stake.md
Normal 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.
|
@ -1,7 +1,7 @@
|
||||
# Generate a Keypair and its Public Key
|
||||
|
||||
In this section, we'll generate a keypair, query it for its public key,
|
||||
and verify you control its private key. Before you begin, you'll need
|
||||
In this section, we will generate a keypair, query it for its public key,
|
||||
and verify you control its private key. Before you begin, you will need
|
||||
to:
|
||||
|
||||
* [Install the Solana Tool Suite](../install-solana.md)
|
||||
|
@ -6,7 +6,7 @@ To receive tokens, you will need an address for others to send tokens to. In
|
||||
Solana, an address is the public key of a keypair. There are a variety
|
||||
of techniques for generating keypairs. The method you choose will depend on how
|
||||
you choose to store keypairs. Keypairs are stored in wallets. Before receiving
|
||||
tokens, you'll need to [choose a wallet](choose-a-wallet.md) and
|
||||
tokens, you will need to [choose a wallet](choose-a-wallet.md) and
|
||||
[generate keys](generate-keys.md). Once completed, you should have a public key
|
||||
for each keypair you generated. The public key is a long string of base58
|
||||
characters. Its length varies from 32 to 44 characters.
|
||||
@ -32,14 +32,14 @@ where you replace the text `<COMMAND>` with the name of the command you want
|
||||
to learn more about.
|
||||
|
||||
The command's usage message will typically contain words such as `<AMOUNT>`,
|
||||
`<PUBKEY>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of text
|
||||
you can execute the command with. For example, you can replace `<AMOUNT>`
|
||||
with a number such as `42` or `100.42`. You can replace `<PUBKEY>` with the
|
||||
base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type
|
||||
`<ACCOUNT_ADDRESS>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of
|
||||
text you can execute the command with. For example, you can replace `<AMOUNT>`
|
||||
with a number such as `42` or `100.42`. You can replace `<ACCOUNT_ADDRESS>` with
|
||||
the base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type
|
||||
of wallet you chose. If you chose an fs wallet, that path might be
|
||||
`~/my-solana-wallet/my-keypair.json`. If you chose a paper wallet, use the
|
||||
keyword `ASK`, and the Solana CLI will prompt you for your seed phrase. If
|
||||
you chose a hardware wallet, use your USB URL, such as `usb://ledger?key=0`.
|
||||
you chose a hardware wallet, use your keypair URL, such as `usb://ledger?key=0`.
|
||||
|
||||
### Test-drive your Public Keys
|
||||
|
||||
@ -50,23 +50,24 @@ Try and *airdrop* yourself some play tokens on the developer testnet, called
|
||||
Devnet:
|
||||
|
||||
```bash
|
||||
solana airdrop 10 <RECIPIENT_PUBKEY> --url http://devnet.solana.com
|
||||
solana airdrop 10 <RECIPIENT_ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where you replace the text `<RECIPIENT_PUBKEY>` with your base58 public key.
|
||||
where you replace the text `<RECIPIENT_ACCOUNT_ADDRESS>` with your base58-encoded
|
||||
public key.
|
||||
|
||||
Confirm the airdrop was successful by checking the account's balance.
|
||||
It should output `10 SOL`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_PUBKEY> --url http://devnet.solana.com
|
||||
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
Next, prove that you own those tokens by transferring them. The Solana cluster
|
||||
will only accept the transfer if you sign the transaction with the private
|
||||
key corresponding to the sender's public key in the transaction.
|
||||
|
||||
First, we'll need a public key to receive our tokens. Create a second
|
||||
First, we will need a public key to receive our tokens. Create a second
|
||||
keypair and record its pubkey:
|
||||
|
||||
```bash
|
||||
@ -74,7 +75,7 @@ solana-keygen new --no-passphrase --no-outfile
|
||||
```
|
||||
|
||||
The output will contain the public key after the text `pubkey:`. Copy the
|
||||
public key. We'll use it in the next step.
|
||||
public key. We will use it in the next step.
|
||||
|
||||
```text
|
||||
============================================================================
|
||||
@ -83,19 +84,19 @@ pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
|
||||
```
|
||||
|
||||
```bash
|
||||
solana transfer --keypair=<SENDER_KEYPAIR> <RECIPIENT_PUBKEY> 5 --url http://devnet.solana.com
|
||||
solana transfer --from=<SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where you replace `<SENDER_KEYPAIR>` with the path to a keypair in your wallet,
|
||||
and replace `<RECIPIENT_PUBKEY>` with the output of `solana-keygen new` above.
|
||||
and replace `<RECIPIENT_ACCOUNT_ADDRESS>` with the output of `solana-keygen new` above.
|
||||
|
||||
Confirm the updated balances with `solana balance`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_PUBKEY> --url http://devnet.solana.com
|
||||
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where `<PUBKEY>` is either the public key from your keypair or the
|
||||
where `<ACCOUNT_ADDRESS>` is either the public key from your keypair or the
|
||||
recipient's public key.
|
||||
|
||||
## Send Tokens
|
||||
@ -106,11 +107,11 @@ tokens to transfer. Once you have that collected, you can transfer tokens
|
||||
with the `solana transfer` command:
|
||||
|
||||
```bash
|
||||
solana transfer --keypair=<SENDER_KEYPAIR> <RECIPIENT_PUBKEY> <AMOUNT>
|
||||
solana transfer --from=<SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT>
|
||||
```
|
||||
|
||||
Confirm the updated balances with `solana balance`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_PUBKEY>
|
||||
solana balance <ACCOUNT_ADDRESS>
|
||||
```
|
||||
|
@ -18,8 +18,7 @@ At present, the following commands support offline signing:
|
||||
* [`deactivate-stake`](../cli/usage.md#solana-deactivate-stake)
|
||||
* [`delegate-stake`](../cli/usage.md#solana-delegate-stake)
|
||||
* [`split-stake`](../cli/usage.md#solana-split-stake)
|
||||
* [`stake-authorize-staker`](../cli/usage.md#solana-stake-authorize-staker)
|
||||
* [`stake-authorize-withdrawer`](../cli/usage.md#solana-stake-authorize-withdrawer)
|
||||
* [`stake-authorize`](../cli/usage.md#solana-stake-authorize)
|
||||
* [`stake-set-lockup`](../cli/usage.md#solana-stake-set-lockup)
|
||||
* [`transfer`](../cli/usage.md#solana-transfer)
|
||||
* [`withdraw-stake`](../cli/usage.md#solana-withdraw-stake)
|
||||
|
@ -52,7 +52,7 @@ usb://ledger/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
|
||||
## Manage Multiple Hardware Wallets
|
||||
|
||||
It is sometimes useful to sign a transaction with keys from multiple hardware
|
||||
wallets. Signing with multiple wallets requires *fully qualified USB URLs*.
|
||||
wallets. Signing with multiple wallets requires *fully qualified keypair URLs*.
|
||||
When the URL is not fully qualified, the Solana CLI will prompt you with
|
||||
the fully qualified URLs of all connected hardware wallets, and ask you to
|
||||
choose which wallet to use for each signature.
|
||||
|
@ -72,81 +72,29 @@ To fix, check the following:
|
||||
3. On your computer, run:
|
||||
|
||||
```text
|
||||
solana address --keypair usb://ledger
|
||||
solana-keygen pubkey usb://ledger
|
||||
```
|
||||
|
||||
This confirms your Ledger device is connected properly and in the correct state
|
||||
to interact with the Solana CLI. The command returns your Ledger's unique
|
||||
*wallet key*. When you have multiple Nano S devices connected to the same
|
||||
*wallet ID*. When you have multiple Nano S devices connected to the same
|
||||
computer, you can use your wallet key to specify which Ledger hardware wallet
|
||||
you want to use. Run the same command again, but this time, with its fully
|
||||
qualified URL:
|
||||
|
||||
```text
|
||||
solana address --keypair usb://ledger/<WALLET_KEY>
|
||||
solana-keygen pubkey usb://ledger/<WALLET_ID>
|
||||
```
|
||||
|
||||
Confirm it prints the same key as when you entered just `usb://ledger`.
|
||||
where you replace `<WALLET_ID>` with the output of the first command.
|
||||
Confirm it prints the same wallet ID as before.
|
||||
|
||||
To learn more about USB URLs, see
|
||||
To learn more about keypair URLs, see
|
||||
[Specify A Hardware Wallet Key](index.md#specify-a-hardware-wallet-key)
|
||||
|
||||
### Set CLI Configuration
|
||||
|
||||
Configure the `solana` CLI tool to connect to a particular cluster:
|
||||
|
||||
```bash
|
||||
solana config set --url <CLUSTER_URL> # (i.e. http://devnet.solana.com)
|
||||
```
|
||||
|
||||
If you want to set a Ledger key as the default signer for CLI commands, use the
|
||||
[CLI configuration settings](../cli/usage.md#solana-config):
|
||||
|
||||
```text
|
||||
solana config set --keypair <LEDGER_URL>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
solana config set --keypair usb://ledger?key=0
|
||||
```
|
||||
|
||||
### Check Account Balance
|
||||
|
||||
```text
|
||||
solana balance --keypair usb://ledger?key=12345
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana balance
|
||||
```
|
||||
|
||||
### Send SOL via Ledger Device
|
||||
|
||||
```text
|
||||
solana transfer <RECIPIENT> <AMOUNT> --from <LEDGER_URL>
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana transfer <RECIPIENT> <AMOUNT>
|
||||
```
|
||||
|
||||
### Delegate Stake with Ledger Device
|
||||
|
||||
```text
|
||||
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID> --keypair <LEDGER_URL>
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID>
|
||||
```
|
||||
Read more about [sending and receiving tokens](../transfer-tokens.md) and
|
||||
[delegating stake](../delegate-stake.md). You can use your Ledger keypair URL
|
||||
anywhere you see an option or argument that accepts a `<KEYPAIR>`.
|
||||
|
||||
## Support
|
||||
|
||||
|
@ -35,14 +35,15 @@ enum LedgerOutputMethod {
|
||||
Json,
|
||||
}
|
||||
|
||||
fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod) {
|
||||
fn output_slot(
|
||||
blockstore: &Blockstore,
|
||||
slot: Slot,
|
||||
method: &LedgerOutputMethod,
|
||||
) -> Result<(), String> {
|
||||
println!("Slot Meta {:?}", blockstore.meta(slot));
|
||||
let entries = blockstore
|
||||
.get_slot_entries(slot, 0, None)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
|
||||
exit(1);
|
||||
});
|
||||
.map_err(|err| format!("Failed to load entries for slot {}: {}", slot, err))?;
|
||||
|
||||
for (entry_index, entry) in entries.iter().enumerate() {
|
||||
match method {
|
||||
@ -115,6 +116,7 @@ fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutputMethod) {
|
||||
@ -140,7 +142,9 @@ fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutp
|
||||
}
|
||||
}
|
||||
|
||||
output_slot(&blockstore, slot, &method);
|
||||
if let Err(err) = output_slot(&blockstore, slot, &method) {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if method == LedgerOutputMethod::Json {
|
||||
@ -616,7 +620,21 @@ fn main() {
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.help("List of slots to print"),
|
||||
.help("Slots to print"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set-dead-slot")
|
||||
.about("Mark one or more slots dead")
|
||||
.arg(
|
||||
Arg::with_name("slots")
|
||||
.index(1)
|
||||
.value_name("SLOTS")
|
||||
.validator(is_slot)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.help("Slots to mark dead"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
@ -824,13 +842,12 @@ fn main() {
|
||||
}
|
||||
("slot", Some(arg_matches)) => {
|
||||
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
|
||||
let blockstore = open_blockstore(&ledger_path);
|
||||
for slot in slots {
|
||||
println!("Slot {}", slot);
|
||||
output_slot(
|
||||
&open_blockstore(&ledger_path),
|
||||
slot,
|
||||
&LedgerOutputMethod::Print,
|
||||
);
|
||||
if let Err(err) = output_slot(&blockstore, slot, &LedgerOutputMethod::Print) {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
("json", Some(arg_matches)) => {
|
||||
@ -841,6 +858,16 @@ fn main() {
|
||||
LedgerOutputMethod::Json,
|
||||
);
|
||||
}
|
||||
("set-dead-slot", Some(arg_matches)) => {
|
||||
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
|
||||
let blockstore = open_blockstore(&ledger_path);
|
||||
for slot in slots {
|
||||
match blockstore.set_dead_slot(slot) {
|
||||
Ok(_) => println!("Slot {} dead", slot),
|
||||
Err(err) => eprintln!("Failed to set slot {} dead slot: {}", slot, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
("verify", Some(arg_matches)) => {
|
||||
let process_options = ProcessOptions {
|
||||
dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(),
|
||||
|
@ -66,6 +66,7 @@ pub fn load(
|
||||
|
||||
let deserialized_bank = snapshot_utils::bank_from_archive(
|
||||
&account_paths,
|
||||
&process_options.frozen_accounts,
|
||||
&snapshot_config.snapshot_path,
|
||||
&archive_filename,
|
||||
)
|
||||
|
@ -23,6 +23,7 @@ use solana_sdk::{
|
||||
clock::{Slot, MAX_RECENT_BLOCKHASHES},
|
||||
genesis_config::GenesisConfig,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Keypair,
|
||||
timing::duration_as_ms,
|
||||
transaction::{Result, Transaction, TransactionError},
|
||||
@ -271,6 +272,7 @@ pub struct ProcessOptions {
|
||||
pub entry_callback: Option<ProcessCallback>,
|
||||
pub override_num_threads: Option<usize>,
|
||||
pub new_hard_forks: Option<Vec<Slot>>,
|
||||
pub frozen_accounts: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
pub fn process_blockstore(
|
||||
@ -289,7 +291,11 @@ pub fn process_blockstore(
|
||||
}
|
||||
|
||||
// Setup bank for slot 0
|
||||
let bank0 = Arc::new(Bank::new_with_paths(&genesis_config, account_paths));
|
||||
let bank0 = Arc::new(Bank::new_with_paths(
|
||||
&genesis_config,
|
||||
account_paths,
|
||||
&opts.frozen_accounts,
|
||||
));
|
||||
info!("processing ledger for slot 0...");
|
||||
let recyclers = VerifyRecyclers::default();
|
||||
process_bank_0(&bank0, blockstore, &opts, &recyclers)?;
|
||||
@ -2611,7 +2617,7 @@ pub mod tests {
|
||||
genesis_config: &GenesisConfig,
|
||||
account_paths: Vec<PathBuf>,
|
||||
) -> EpochSchedule {
|
||||
let bank = Bank::new_with_paths(&genesis_config, account_paths);
|
||||
let bank = Bank::new_with_paths(&genesis_config, account_paths, &[]);
|
||||
bank.epoch_schedule().clone()
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ use solana_runtime::{
|
||||
MAX_SNAPSHOT_DATA_FILE_SIZE,
|
||||
},
|
||||
};
|
||||
use solana_sdk::{clock::Slot, hash::Hash};
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
env,
|
||||
@ -432,6 +432,7 @@ pub fn remove_snapshot<P: AsRef<Path>>(slot: Slot, snapshot_path: P) -> Result<(
|
||||
|
||||
pub fn bank_from_archive<P: AsRef<Path>>(
|
||||
account_paths: &[PathBuf],
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
snapshot_path: &PathBuf,
|
||||
snapshot_tar: P,
|
||||
) -> Result<Bank> {
|
||||
@ -450,6 +451,7 @@ pub fn bank_from_archive<P: AsRef<Path>>(
|
||||
let bank = rebuild_bank_from_snapshots(
|
||||
snapshot_version.trim(),
|
||||
account_paths,
|
||||
frozen_account_pubkeys,
|
||||
&unpacked_snapshots_dir,
|
||||
unpacked_accounts_dir,
|
||||
)?;
|
||||
@ -568,6 +570,7 @@ pub fn untar_snapshot_in<P: AsRef<Path>, Q: AsRef<Path>>(
|
||||
fn rebuild_bank_from_snapshots<P>(
|
||||
snapshot_version: &str,
|
||||
account_paths: &[PathBuf],
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
unpacked_snapshots_dir: &PathBuf,
|
||||
append_vecs_path: P,
|
||||
) -> Result<Bank>
|
||||
@ -599,12 +602,16 @@ where
|
||||
}
|
||||
};
|
||||
info!("Rebuilding accounts...");
|
||||
bank.set_bank_rc(
|
||||
bank::BankRc::new(account_paths.to_vec(), 0, bank.slot()),
|
||||
bank::StatusCacheRc::default(),
|
||||
);
|
||||
bank.rc
|
||||
.accounts_from_stream(stream.by_ref(), account_paths, &append_vecs_path)?;
|
||||
let rc = bank::BankRc::from_stream(
|
||||
account_paths,
|
||||
bank.slot(),
|
||||
&bank.ancestors,
|
||||
frozen_account_pubkeys,
|
||||
stream.by_ref(),
|
||||
&append_vecs_path,
|
||||
)?;
|
||||
|
||||
bank.set_bank_rc(rc, bank::StatusCacheRc::default());
|
||||
Ok(bank)
|
||||
},
|
||||
)?;
|
||||
|
@ -298,7 +298,7 @@ impl LocalCluster {
|
||||
self.exit();
|
||||
for (_, node) in self.validators.iter_mut() {
|
||||
if let Some(v) = node.validator.take() {
|
||||
v.join().unwrap();
|
||||
v.join().expect("Validator join failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,14 @@ use solana_local_cluster::{
|
||||
local_cluster::{ClusterConfig, LocalCluster},
|
||||
};
|
||||
use solana_sdk::{
|
||||
client::SyncClient,
|
||||
client::{AsyncClient, SyncClient},
|
||||
clock::{self, Slot},
|
||||
commitment_config::CommitmentConfig,
|
||||
epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
|
||||
genesis_config::OperatingMode,
|
||||
hash::Hash,
|
||||
poh_config::PohConfig,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@ -547,7 +548,7 @@ fn test_listener_startup() {
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_softlaunch_operating_mode() {
|
||||
fn test_stable_operating_mode() {
|
||||
solana_logger::setup();
|
||||
|
||||
let config = ClusterConfig {
|
||||
@ -566,7 +567,7 @@ fn test_softlaunch_operating_mode() {
|
||||
solana_core::cluster_info::VALIDATOR_PORT_RANGE,
|
||||
);
|
||||
|
||||
// Programs that are available at soft launch epoch 0
|
||||
// Programs that are available at epoch 0
|
||||
for program_id in [
|
||||
&solana_config_program::id(),
|
||||
&solana_sdk::system_program::id(),
|
||||
@ -586,7 +587,7 @@ fn test_softlaunch_operating_mode() {
|
||||
);
|
||||
}
|
||||
|
||||
// Programs that are not available at soft launch epoch 0
|
||||
// Programs that are not available at epoch 0
|
||||
for program_id in [
|
||||
&solana_sdk::bpf_loader::id(),
|
||||
&solana_storage_program::id(),
|
||||
@ -606,6 +607,110 @@ fn test_softlaunch_operating_mode() {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_frozen_account_panic(mut cluster: LocalCluster, frozen_account: Arc<Keypair>) {
|
||||
let client = cluster
|
||||
.get_validator_client(&frozen_account.pubkey())
|
||||
.unwrap();
|
||||
|
||||
// Check the validator is alive by poking it over RPC
|
||||
trace!(
|
||||
"validator slot: {}",
|
||||
client
|
||||
.get_slot_with_commitment(CommitmentConfig::recent())
|
||||
.expect("get slot")
|
||||
);
|
||||
|
||||
// Reset the frozen account panic signal
|
||||
solana_runtime::accounts_db::FROZEN_ACCOUNT_PANIC.store(false, Ordering::Relaxed);
|
||||
|
||||
// Wait for the frozen account panic signal
|
||||
let mut i = 0;
|
||||
while !solana_runtime::accounts_db::FROZEN_ACCOUNT_PANIC.load(Ordering::Relaxed) {
|
||||
// Transfer from frozen account
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap();
|
||||
client
|
||||
.async_transfer(1, &frozen_account, &Pubkey::new_rand(), blockhash)
|
||||
.unwrap();
|
||||
|
||||
sleep(Duration::from_secs(1));
|
||||
i += 1;
|
||||
if i > 10 {
|
||||
panic!("FROZEN_ACCOUNT_PANIC still false");
|
||||
}
|
||||
}
|
||||
|
||||
// The validator is now broken and won't shutdown properly. Avoid LocalCluster panic in Drop
|
||||
// with some manual cleanup:
|
||||
cluster.exit();
|
||||
cluster.validators = HashMap::default();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_frozen_account_from_genesis() {
|
||||
solana_logger::setup();
|
||||
let validator_identity =
|
||||
Arc::new(solana_sdk::signature::keypair_from_seed(&[0u8; 32]).unwrap());
|
||||
|
||||
let config = ClusterConfig {
|
||||
validator_keys: Some(vec![validator_identity.clone()]),
|
||||
node_stakes: vec![100; 1],
|
||||
cluster_lamports: 1_000,
|
||||
validator_configs: vec![
|
||||
ValidatorConfig {
|
||||
// Freeze the validator identity account
|
||||
frozen_accounts: vec![validator_identity.pubkey()],
|
||||
..ValidatorConfig::default()
|
||||
};
|
||||
1
|
||||
],
|
||||
..ClusterConfig::default()
|
||||
};
|
||||
generate_frozen_account_panic(LocalCluster::new(&config), validator_identity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_frozen_account_from_snapshot() {
|
||||
solana_logger::setup();
|
||||
let validator_identity =
|
||||
Arc::new(solana_sdk::signature::keypair_from_seed(&[0u8; 32]).unwrap());
|
||||
|
||||
let mut snapshot_test_config = setup_snapshot_validator_config(5, 1);
|
||||
// Freeze the validator identity account
|
||||
snapshot_test_config.validator_config.frozen_accounts = vec![validator_identity.pubkey()];
|
||||
|
||||
let config = ClusterConfig {
|
||||
validator_keys: Some(vec![validator_identity.clone()]),
|
||||
node_stakes: vec![100; 1],
|
||||
cluster_lamports: 1_000,
|
||||
validator_configs: vec![snapshot_test_config.validator_config.clone()],
|
||||
..ClusterConfig::default()
|
||||
};
|
||||
let mut cluster = LocalCluster::new(&config);
|
||||
|
||||
let snapshot_package_output_path = &snapshot_test_config
|
||||
.validator_config
|
||||
.snapshot_config
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.snapshot_package_output_path;
|
||||
|
||||
trace!("Waiting for snapshot at {:?}", snapshot_package_output_path);
|
||||
let (archive_filename, _archive_snapshot_hash) =
|
||||
wait_for_next_snapshot(&cluster, &snapshot_package_output_path);
|
||||
|
||||
trace!("Found snapshot: {:?}", archive_filename);
|
||||
|
||||
// Restart the validator from a snapshot
|
||||
let validator_info = cluster.exit_node(&validator_identity.pubkey());
|
||||
cluster.restart_node(&validator_identity.pubkey(), validator_info);
|
||||
|
||||
generate_frozen_account_panic(cluster, validator_identity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_consistency_halt() {
|
||||
|
@ -15,6 +15,7 @@ byteorder = "1.3.2"
|
||||
fnv = "1.0.6"
|
||||
fs_extra = "1.1.0"
|
||||
itertools = "0.8.2"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.66"
|
||||
libloading = "0.5.2"
|
||||
log = "0.4.8"
|
||||
|
@ -32,7 +32,7 @@ fn bench_has_duplicates(bencher: &mut Bencher) {
|
||||
#[bench]
|
||||
fn test_accounts_create(bencher: &mut Bencher) {
|
||||
let (genesis_config, _) = create_genesis_config(10_000);
|
||||
let bank0 = Bank::new_with_paths(&genesis_config, vec![PathBuf::from("bench_a0")]);
|
||||
let bank0 = Bank::new_with_paths(&genesis_config, vec![PathBuf::from("bench_a0")], &[]);
|
||||
bencher.iter(|| {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
deposit_many(&bank0, &mut pubkeys, 1000);
|
||||
@ -46,6 +46,7 @@ fn test_accounts_squash(bencher: &mut Bencher) {
|
||||
banks.push(Arc::new(Bank::new_with_paths(
|
||||
&genesis_config,
|
||||
vec![PathBuf::from("bench_a1")],
|
||||
&[],
|
||||
)));
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
deposit_many(&banks[0], &mut pubkeys, 250000);
|
||||
|
@ -59,15 +59,9 @@ pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, Trans
|
||||
|
||||
impl Accounts {
|
||||
pub fn new(paths: Vec<PathBuf>) -> Self {
|
||||
let accounts_db = Arc::new(AccountsDB::new(paths));
|
||||
|
||||
Accounts {
|
||||
slot: 0,
|
||||
accounts_db,
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
}
|
||||
Self::new_with_frozen_accounts(paths, &HashMap::default(), &[])
|
||||
}
|
||||
|
||||
pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
|
||||
let accounts_db = parent.accounts_db.clone();
|
||||
accounts_db.set_hash(slot, parent_slot);
|
||||
@ -79,14 +73,48 @@ impl Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
|
||||
&self,
|
||||
pub fn new_with_frozen_accounts(
|
||||
paths: Vec<PathBuf>,
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
) -> Self {
|
||||
let mut accounts = Accounts {
|
||||
slot: 0,
|
||||
accounts_db: Arc::new(AccountsDB::new(paths)),
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
};
|
||||
accounts.freeze_accounts(ancestors, frozen_account_pubkeys);
|
||||
accounts
|
||||
}
|
||||
|
||||
pub fn freeze_accounts(
|
||||
&mut self,
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
) {
|
||||
Arc::get_mut(&mut self.accounts_db)
|
||||
.unwrap()
|
||||
.freeze_accounts(ancestors, frozen_account_pubkeys);
|
||||
}
|
||||
|
||||
pub fn from_stream<R: Read, P: AsRef<Path>>(
|
||||
account_paths: &[PathBuf],
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
stream: &mut BufReader<R>,
|
||||
local_paths: &[PathBuf],
|
||||
append_vecs_path: P,
|
||||
) -> std::result::Result<(), IOError> {
|
||||
self.accounts_db
|
||||
.accounts_from_stream(stream, local_paths, append_vecs_path)
|
||||
stream_append_vecs_path: P,
|
||||
) -> std::result::Result<Self, IOError> {
|
||||
let mut accounts_db = AccountsDB::new(account_paths.to_vec());
|
||||
accounts_db.accounts_from_stream(stream, stream_append_vecs_path)?;
|
||||
accounts_db.freeze_accounts(ancestors, frozen_account_pubkeys);
|
||||
|
||||
Ok(Accounts {
|
||||
slot: 0,
|
||||
accounts_db: Arc::new(accounts_db),
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return true if the slice has any duplicate elements
|
||||
@ -1393,10 +1421,14 @@ mod tests {
|
||||
let buf = writer.into_inner();
|
||||
let mut reader = BufReader::new(&buf[..]);
|
||||
let (_accounts_dir, daccounts_paths) = get_temp_accounts_paths(2).unwrap();
|
||||
let daccounts = Accounts::new(daccounts_paths.clone());
|
||||
assert!(daccounts
|
||||
.accounts_from_stream(&mut reader, &daccounts_paths, copied_accounts.path())
|
||||
.is_ok());
|
||||
let daccounts = Accounts::from_stream(
|
||||
&daccounts_paths,
|
||||
&HashMap::default(),
|
||||
&[],
|
||||
&mut reader,
|
||||
copied_accounts.path(),
|
||||
)
|
||||
.unwrap();
|
||||
check_accounts(&daccounts, &pubkeys, 100);
|
||||
assert_eq!(accounts.bank_hash_at(0), daccounts.bank_hash_at(0));
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ use crate::{
|
||||
use bincode::{deserialize_from, serialize_into};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use fs_extra::dir::CopyOptions;
|
||||
use lazy_static::lazy_static;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::{prelude::*, ThreadPool};
|
||||
@ -56,6 +57,12 @@ pub const DEFAULT_FILE_SIZE: u64 = 4 * 1024 * 1024;
|
||||
pub const DEFAULT_NUM_THREADS: u32 = 8;
|
||||
pub const DEFAULT_NUM_DIRS: u32 = 4;
|
||||
|
||||
lazy_static! {
|
||||
// FROZEN_ACCOUNT_PANIC is used to signal local_cluster that an AccountsDB panic has occurred,
|
||||
// as |cargo test| cannot observe panics in other threads
|
||||
pub static ref FROZEN_ACCOUNT_PANIC: Arc<AtomicBool> = { Arc::new(AtomicBool::new(false)) };
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ErrorCounters {
|
||||
pub total: usize,
|
||||
@ -426,6 +433,12 @@ pub struct BankHashInfo {
|
||||
pub stats: BankHashStats,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FrozenAccountInfo {
|
||||
pub hash: Hash, // Hash generated by hash_frozen_account_data()
|
||||
pub lamports: u64, // Account balance cannot be lower than this amount
|
||||
}
|
||||
|
||||
// This structure handles the load/store of the accounts
|
||||
#[derive(Debug)]
|
||||
pub struct AccountsDB {
|
||||
@ -440,7 +453,7 @@ pub struct AccountsDB {
|
||||
write_version: AtomicUsize,
|
||||
|
||||
/// Set of storage paths to pick from
|
||||
paths: RwLock<Vec<PathBuf>>,
|
||||
paths: Vec<PathBuf>,
|
||||
|
||||
/// Directory of paths this accounts_db needs to hold/remove
|
||||
temp_paths: Option<Vec<TempDir>>,
|
||||
@ -448,6 +461,9 @@ pub struct AccountsDB {
|
||||
/// Starting file size of appendvecs
|
||||
file_size: u64,
|
||||
|
||||
/// Accounts that will cause a panic! if data modified or lamports decrease
|
||||
frozen_accounts: HashMap<Pubkey, FrozenAccountInfo>,
|
||||
|
||||
/// Thread pool used for par_iter
|
||||
pub thread_pool: ThreadPool,
|
||||
|
||||
@ -469,7 +485,7 @@ impl Default for AccountsDB {
|
||||
storage: RwLock::new(AccountStorage(HashMap::new())),
|
||||
next_id: AtomicUsize::new(0),
|
||||
write_version: AtomicUsize::new(0),
|
||||
paths: RwLock::new(vec![]),
|
||||
paths: vec![],
|
||||
temp_paths: None,
|
||||
file_size: DEFAULT_FILE_SIZE,
|
||||
thread_pool: rayon::ThreadPoolBuilder::new()
|
||||
@ -478,6 +494,7 @@ impl Default for AccountsDB {
|
||||
.unwrap(),
|
||||
min_num_stores: num_threads,
|
||||
bank_hashes: RwLock::new(bank_hashes),
|
||||
frozen_accounts: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,23 +503,22 @@ impl AccountsDB {
|
||||
pub fn new(paths: Vec<PathBuf>) -> Self {
|
||||
let new = if !paths.is_empty() {
|
||||
Self {
|
||||
paths: RwLock::new(paths),
|
||||
paths,
|
||||
temp_paths: None,
|
||||
..Self::default()
|
||||
}
|
||||
} else {
|
||||
// Create a temprorary set of accounts directories, used primarily
|
||||
// Create a temporary set of accounts directories, used primarily
|
||||
// for testing
|
||||
let (temp_dirs, paths) = get_temp_accounts_paths(DEFAULT_NUM_DIRS).unwrap();
|
||||
Self {
|
||||
paths: RwLock::new(paths),
|
||||
paths,
|
||||
temp_paths: Some(temp_dirs),
|
||||
..Self::default()
|
||||
}
|
||||
};
|
||||
{
|
||||
let paths = new.paths.read().unwrap();
|
||||
for path in paths.iter() {
|
||||
for path in new.paths.iter() {
|
||||
std::fs::create_dir_all(path).expect("Create directory failed.");
|
||||
}
|
||||
}
|
||||
@ -527,8 +543,7 @@ impl AccountsDB {
|
||||
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
|
||||
&self,
|
||||
mut stream: &mut BufReader<R>,
|
||||
local_account_paths: &[PathBuf],
|
||||
append_vecs_path: P,
|
||||
stream_append_vecs_path: P,
|
||||
) -> Result<(), IOError> {
|
||||
let _len: usize =
|
||||
deserialize_from(&mut stream).map_err(|e| AccountsDB::get_io_error(&e.to_string()))?;
|
||||
@ -542,8 +557,8 @@ impl AccountsDB {
|
||||
.map(|(slot_id, mut slot_storage)| {
|
||||
let mut new_slot_storage = HashMap::new();
|
||||
for (id, storage_entry) in slot_storage.drain() {
|
||||
let path_index = thread_rng().gen_range(0, local_account_paths.len());
|
||||
let local_dir = &local_account_paths[path_index];
|
||||
let path_index = thread_rng().gen_range(0, self.paths.len());
|
||||
let local_dir = &self.paths[path_index];
|
||||
|
||||
std::fs::create_dir_all(local_dir).expect("Create directory failed");
|
||||
|
||||
@ -551,8 +566,9 @@ impl AccountsDB {
|
||||
// at by `local_dir`
|
||||
let append_vec_relative_path =
|
||||
AppendVec::new_relative_path(slot_id, storage_entry.id);
|
||||
let append_vec_abs_path =
|
||||
append_vecs_path.as_ref().join(&append_vec_relative_path);
|
||||
let append_vec_abs_path = stream_append_vecs_path
|
||||
.as_ref()
|
||||
.join(&append_vec_relative_path);
|
||||
let target = local_dir.join(append_vec_abs_path.file_name().unwrap());
|
||||
if std::fs::rename(append_vec_abs_path.clone(), target).is_err() {
|
||||
let mut copy_options = CopyOptions::new();
|
||||
@ -602,7 +618,6 @@ impl AccountsDB {
|
||||
self.bank_hashes.write().unwrap().insert(slot, bank_hash);
|
||||
|
||||
// Process deserialized data, set necessary fields in self
|
||||
*self.paths.write().unwrap() = local_account_paths.to_vec();
|
||||
let max_id: usize = *storage
|
||||
.0
|
||||
.values()
|
||||
@ -938,22 +953,14 @@ impl AccountsDB {
|
||||
}
|
||||
|
||||
fn create_and_insert_store(&self, slot_id: Slot, size: u64) -> Arc<AccountStorageEntry> {
|
||||
let path_index = thread_rng().gen_range(0, self.paths.len());
|
||||
let store =
|
||||
Arc::new(self.new_storage_entry(slot_id, &Path::new(&self.paths[path_index]), size));
|
||||
let store_for_index = store.clone();
|
||||
|
||||
let mut stores = self.storage.write().unwrap();
|
||||
let slot_storage = stores.0.entry(slot_id).or_insert_with(HashMap::new);
|
||||
|
||||
self.create_store(slot_id, slot_storage, size)
|
||||
}
|
||||
|
||||
fn create_store(
|
||||
&self,
|
||||
slot_id: Slot,
|
||||
slot_storage: &mut SlotStores,
|
||||
size: u64,
|
||||
) -> Arc<AccountStorageEntry> {
|
||||
let paths = self.paths.read().unwrap();
|
||||
let path_index = thread_rng().gen_range(0, paths.len());
|
||||
let store = Arc::new(self.new_storage_entry(slot_id, &Path::new(&paths[path_index]), size));
|
||||
slot_storage.insert(store.id, store.clone());
|
||||
slot_storage.insert(store.id, store_for_index);
|
||||
store
|
||||
}
|
||||
|
||||
@ -987,6 +994,21 @@ impl AccountsDB {
|
||||
)
|
||||
}
|
||||
|
||||
fn hash_frozen_account_data(account: &Account) -> Hash {
|
||||
let mut hasher = Hasher::default();
|
||||
|
||||
hasher.hash(&account.data);
|
||||
hasher.hash(&account.owner.as_ref());
|
||||
|
||||
if account.executable {
|
||||
hasher.hash(&[1u8; 1]);
|
||||
} else {
|
||||
hasher.hash(&[0u8; 1]);
|
||||
}
|
||||
|
||||
hasher.result()
|
||||
}
|
||||
|
||||
pub fn hash_account_data(
|
||||
slot: Slot,
|
||||
lamports: u64,
|
||||
@ -1398,8 +1420,62 @@ impl AccountsDB {
|
||||
hashes
|
||||
}
|
||||
|
||||
pub fn freeze_accounts(
|
||||
&mut self,
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
account_pubkeys: &[Pubkey],
|
||||
) {
|
||||
for account_pubkey in account_pubkeys {
|
||||
if let Some((account, _slot)) = self.load_slow(ancestors, &account_pubkey) {
|
||||
let frozen_account_info = FrozenAccountInfo {
|
||||
hash: Self::hash_frozen_account_data(&account),
|
||||
lamports: account.lamports,
|
||||
};
|
||||
warn!(
|
||||
"Account {} is now frozen at lamports={}, hash={}",
|
||||
account_pubkey, frozen_account_info.lamports, frozen_account_info.hash
|
||||
);
|
||||
self.frozen_accounts
|
||||
.insert(*account_pubkey, frozen_account_info);
|
||||
} else {
|
||||
panic!(
|
||||
"Unable to freeze an account that does not exist: {}",
|
||||
account_pubkey
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cause a panic if frozen accounts would be affected by data in `accounts`
|
||||
fn assert_frozen_accounts(&self, accounts: &[(&Pubkey, &Account)]) {
|
||||
if self.frozen_accounts.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (account_pubkey, account) in accounts.iter() {
|
||||
if let Some(frozen_account_info) = self.frozen_accounts.get(*account_pubkey) {
|
||||
if account.lamports < frozen_account_info.lamports {
|
||||
FROZEN_ACCOUNT_PANIC.store(true, Ordering::Relaxed);
|
||||
panic!(
|
||||
"Frozen account {} modified. Lamports decreased from {} to {}",
|
||||
account_pubkey, frozen_account_info.lamports, account.lamports,
|
||||
)
|
||||
}
|
||||
|
||||
let hash = Self::hash_frozen_account_data(&account);
|
||||
if hash != frozen_account_info.hash {
|
||||
FROZEN_ACCOUNT_PANIC.store(true, Ordering::Relaxed);
|
||||
panic!(
|
||||
"Frozen account {} modified. Hash changed from {} to {}",
|
||||
account_pubkey, frozen_account_info.hash, hash,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Store the account update.
|
||||
pub fn store(&self, slot_id: Slot, accounts: &[(&Pubkey, &Account)]) {
|
||||
self.assert_frozen_accounts(accounts);
|
||||
let hashes = self.hash_accounts(slot_id, accounts);
|
||||
self.store_with_hashes(slot_id, accounts, &hashes);
|
||||
}
|
||||
@ -2332,12 +2408,11 @@ pub mod tests {
|
||||
let buf = writer.into_inner();
|
||||
let mut reader = BufReader::new(&buf[..]);
|
||||
let daccounts = AccountsDB::new(Vec::new());
|
||||
let local_paths = daccounts.paths.read().unwrap().clone();
|
||||
let copied_accounts = TempDir::new().unwrap();
|
||||
// Simulate obtaining a copy of the AppendVecs from a tarball
|
||||
copy_append_vecs(&accounts, copied_accounts.path()).unwrap();
|
||||
daccounts
|
||||
.accounts_from_stream(&mut reader, &local_paths, copied_accounts.path())
|
||||
.accounts_from_stream(&mut reader, copied_accounts.path())
|
||||
.unwrap();
|
||||
|
||||
print_count_and_status("daccounts", &daccounts);
|
||||
@ -2724,6 +2799,139 @@ pub mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_frozen_account_data() {
|
||||
let account = Account::new(1, 42, &Pubkey::default());
|
||||
|
||||
let hash = AccountsDB::hash_frozen_account_data(&account);
|
||||
assert_ne!(hash, Hash::default()); // Better not be the default Hash
|
||||
|
||||
// Lamports changes to not affect the hash
|
||||
let mut account_modified = account.clone();
|
||||
account_modified.lamports -= 1;
|
||||
assert_eq!(
|
||||
hash,
|
||||
AccountsDB::hash_frozen_account_data(&account_modified)
|
||||
);
|
||||
|
||||
// Rent epoch may changes to not affect the hash
|
||||
let mut account_modified = account.clone();
|
||||
account_modified.rent_epoch += 1;
|
||||
assert_eq!(
|
||||
hash,
|
||||
AccountsDB::hash_frozen_account_data(&account_modified)
|
||||
);
|
||||
|
||||
// Account data may not be modified
|
||||
let mut account_modified = account.clone();
|
||||
account_modified.data[0] = 42;
|
||||
assert_ne!(
|
||||
hash,
|
||||
AccountsDB::hash_frozen_account_data(&account_modified)
|
||||
);
|
||||
|
||||
// Owner may not be modified
|
||||
let mut account_modified = account.clone();
|
||||
account_modified.owner =
|
||||
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
assert_ne!(
|
||||
hash,
|
||||
AccountsDB::hash_frozen_account_data(&account_modified)
|
||||
);
|
||||
|
||||
// Executable may not be modified
|
||||
let mut account_modified = account.clone();
|
||||
account_modified.executable = true;
|
||||
assert_ne!(
|
||||
hash,
|
||||
AccountsDB::hash_frozen_account_data(&account_modified)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frozen_account_lamport_increase() {
|
||||
let frozen_pubkey =
|
||||
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
let mut db = AccountsDB::new(Vec::new());
|
||||
|
||||
let mut account = Account::new(1, 42, &frozen_pubkey);
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
|
||||
|
||||
// Store with no account changes is ok
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
// Store with an increase in lamports is ok
|
||||
account.lamports = 2;
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
// Store with an decrease that does not go below the frozen amount of lamports is tolerated
|
||||
account.lamports = 1;
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
// A store of any value over the frozen value of '1' across different slots is also ok
|
||||
account.lamports = 3;
|
||||
db.store(1, &[(&frozen_pubkey, &account)]);
|
||||
account.lamports = 2;
|
||||
db.store(2, &[(&frozen_pubkey, &account)]);
|
||||
account.lamports = 1;
|
||||
db.store(3, &[(&frozen_pubkey, &account)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Frozen account My11111111111111111111111111111111111111111 modified. Lamports decreased from 1 to 0"
|
||||
)]
|
||||
fn test_frozen_account_lamport_decrease() {
|
||||
let frozen_pubkey =
|
||||
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
let mut db = AccountsDB::new(Vec::new());
|
||||
|
||||
let mut account = Account::new(1, 42, &frozen_pubkey);
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
|
||||
|
||||
// Store with a decrease below the frozen amount of lamports is not ok
|
||||
account.lamports -= 1;
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Unable to freeze an account that does not exist: My11111111111111111111111111111111111111111"
|
||||
)]
|
||||
fn test_frozen_account_nonexistent() {
|
||||
let frozen_pubkey =
|
||||
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
let mut db = AccountsDB::new(Vec::new());
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Frozen account My11111111111111111111111111111111111111111 modified. Hash changed from 8wHcxDkjiwdrkPAsDnmNrF1UDGJFAtZzPQBSVweY3yRA to JdscGYB1uczVssmYuJusDD1Bfe6wpNeeho8XjcH8inN"
|
||||
)]
|
||||
fn test_frozen_account_data_modified() {
|
||||
let frozen_pubkey =
|
||||
Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
|
||||
let mut db = AccountsDB::new(Vec::new());
|
||||
|
||||
let mut account = Account::new(1, 42, &frozen_pubkey);
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
db.freeze_accounts(&ancestors, &[frozen_pubkey]);
|
||||
|
||||
account.data[0] = 42;
|
||||
db.store(0, &[(&frozen_pubkey, &account)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_stored_account() {
|
||||
// This test uses some UNSAFE trick to detect most of account's field
|
||||
|
@ -4,9 +4,7 @@
|
||||
//! already been signed and verified.
|
||||
use crate::{
|
||||
accounts::{Accounts, TransactionAccounts, TransactionLoadResult, TransactionLoaders},
|
||||
accounts_db::{
|
||||
AccountsDBSerialize, AppendVecId, ErrorCounters, SnapshotStorage, SnapshotStorages,
|
||||
},
|
||||
accounts_db::{AccountsDBSerialize, ErrorCounters, SnapshotStorage, SnapshotStorages},
|
||||
blockhash_queue::BlockhashQueue,
|
||||
message_processor::{MessageProcessor, ProcessInstruction},
|
||||
nonce_utils,
|
||||
@ -85,31 +83,30 @@ pub struct BankRc {
|
||||
}
|
||||
|
||||
impl BankRc {
|
||||
pub fn new(account_paths: Vec<PathBuf>, id: AppendVecId, slot: Slot) -> Self {
|
||||
let accounts = Accounts::new(account_paths);
|
||||
accounts
|
||||
.accounts_db
|
||||
.next_id
|
||||
.store(id as usize, Ordering::Relaxed);
|
||||
BankRc {
|
||||
pub fn from_stream<R: Read, P: AsRef<Path>>(
|
||||
account_paths: &[PathBuf],
|
||||
slot: Slot,
|
||||
ancestors: &HashMap<Slot, usize>,
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
mut stream: &mut BufReader<R>,
|
||||
stream_append_vecs_path: P,
|
||||
) -> std::result::Result<Self, IOError> {
|
||||
let _len: usize =
|
||||
deserialize_from(&mut stream).map_err(|e| BankRc::get_io_error(&e.to_string()))?;
|
||||
|
||||
let accounts = Accounts::from_stream(
|
||||
account_paths,
|
||||
ancestors,
|
||||
frozen_account_pubkeys,
|
||||
stream,
|
||||
stream_append_vecs_path,
|
||||
)?;
|
||||
|
||||
Ok(BankRc {
|
||||
accounts: Arc::new(accounts),
|
||||
parent: RwLock::new(None),
|
||||
slot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accounts_from_stream<R: Read, P: AsRef<Path>>(
|
||||
&self,
|
||||
mut stream: &mut BufReader<R>,
|
||||
local_paths: &[PathBuf],
|
||||
append_vecs_path: P,
|
||||
) -> std::result::Result<(), IOError> {
|
||||
let _len: usize =
|
||||
deserialize_from(&mut stream).map_err(|e| BankRc::get_io_error(&e.to_string()))?;
|
||||
self.accounts
|
||||
.accounts_from_stream(stream, local_paths, append_vecs_path)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_snapshot_storages(&self, slot: Slot) -> SnapshotStorages {
|
||||
@ -359,14 +356,25 @@ impl Default for BlockhashQueue {
|
||||
|
||||
impl Bank {
|
||||
pub fn new(genesis_config: &GenesisConfig) -> Self {
|
||||
Self::new_with_paths(&genesis_config, Vec::new())
|
||||
Self::new_with_paths(&genesis_config, Vec::new(), &[])
|
||||
}
|
||||
|
||||
pub fn new_with_paths(genesis_config: &GenesisConfig, paths: Vec<PathBuf>) -> Self {
|
||||
pub fn new_with_paths(
|
||||
genesis_config: &GenesisConfig,
|
||||
paths: Vec<PathBuf>,
|
||||
frozen_account_pubkeys: &[Pubkey],
|
||||
) -> Self {
|
||||
let mut bank = Self::default();
|
||||
bank.ancestors.insert(bank.slot(), 0);
|
||||
|
||||
bank.rc.accounts = Arc::new(Accounts::new(paths));
|
||||
bank.process_genesis_config(genesis_config);
|
||||
|
||||
// Freeze accounts after process_genesis_config creates the initial append vecs
|
||||
Arc::get_mut(&mut bank.rc.accounts)
|
||||
.unwrap()
|
||||
.freeze_accounts(&bank.ancestors, frozen_account_pubkeys);
|
||||
|
||||
// genesis needs stakes for all epochs up to the epoch implied by
|
||||
// slot = 0 and genesis configuration
|
||||
{
|
||||
@ -4764,14 +4772,21 @@ mod tests {
|
||||
let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
|
||||
let ref_sc = StatusCacheRc::default();
|
||||
ref_sc.status_cache.write().unwrap().add_root(2);
|
||||
dbank.set_bank_rc(BankRc::new(dbank_paths.clone(), 0, dbank.slot()), ref_sc);
|
||||
// Create a directory to simulate AppendVecs unpackaged from a snapshot tar
|
||||
let copied_accounts = TempDir::new().unwrap();
|
||||
copy_append_vecs(&bank2.rc.accounts.accounts_db, copied_accounts.path()).unwrap();
|
||||
dbank
|
||||
.rc
|
||||
.accounts_from_stream(&mut reader, &dbank_paths, copied_accounts.path())
|
||||
.unwrap();
|
||||
dbank.set_bank_rc(
|
||||
BankRc::from_stream(
|
||||
&dbank_paths,
|
||||
dbank.slot(),
|
||||
&dbank.ancestors,
|
||||
&[],
|
||||
&mut reader,
|
||||
copied_accounts.path(),
|
||||
)
|
||||
.unwrap(),
|
||||
ref_sc,
|
||||
);
|
||||
assert_eq!(dbank.get_balance(&key1.pubkey()), 0);
|
||||
assert_eq!(dbank.get_balance(&key2.pubkey()), 10);
|
||||
assert_eq!(dbank.get_balance(&key3.pubkey()), 0);
|
||||
|
@ -109,7 +109,7 @@ impl PreAccount {
|
||||
return Err(InstructionError::ExecutableModified);
|
||||
}
|
||||
|
||||
// No one modifies r ent_epoch (yet).
|
||||
// No one modifies rent_epoch (yet).
|
||||
if self.rent_epoch != post.rent_epoch {
|
||||
return Err(InstructionError::RentEpochModified);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use bzip2::bufread::BzDecoder;
|
||||
use clap::{
|
||||
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, Arg, ArgMatches,
|
||||
crate_description, crate_name, value_t, value_t_or_exit, values_t, values_t_or_exit, App, Arg,
|
||||
ArgMatches,
|
||||
};
|
||||
use console::Emoji;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
@ -858,6 +859,17 @@ pub fn main() {
|
||||
.takes_value(false)
|
||||
.help("Abort the validator if a bank hash mismatch is detected within trusted validator set"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("frozen_accounts")
|
||||
.long("frozen-account")
|
||||
.validator(is_pubkey)
|
||||
.value_name("PUBKEY")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.help("Freeze the specified account. This will cause the validator to \
|
||||
intentionally crash should any transaction modify the frozen account in any way \
|
||||
other than increasing the account balance"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let identity_keypair = Arc::new(keypair_of(&matches, "identity").unwrap_or_else(Keypair::new));
|
||||
@ -932,6 +944,7 @@ pub fn main() {
|
||||
voting_disabled: matches.is_present("no_voting"),
|
||||
wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(),
|
||||
trusted_validators,
|
||||
frozen_accounts: values_t!(matches, "frozen_accounts", Pubkey).unwrap_or_default(),
|
||||
..ValidatorConfig::default()
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user