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