Add CliCommand::StakeSetLockup (#8248)

automerge
This commit is contained in:
Tyera Eulberg 2020-02-12 16:36:29 -07:00 committed by GitHub
parent 58727463e1
commit 6309c97697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 417 additions and 8 deletions

View File

@ -334,6 +334,17 @@ pub enum CliCommand {
nonce_authority: Option<SigningAuthority>,
fee_payer: Option<SigningAuthority>,
},
StakeSetLockup {
stake_account_pubkey: Pubkey,
lockup: Lockup,
custodian: Option<SigningAuthority>,
sign_only: bool,
signers: Option<Vec<(Pubkey, Signature)>>,
blockhash_query: BlockhashQuery,
nonce_account: Option<Pubkey>,
nonce_authority: Option<SigningAuthority>,
fee_payer: Option<SigningAuthority>,
},
WithdrawStake {
stake_account_pubkey: Pubkey,
destination_account_pubkey: Pubkey,
@ -565,6 +576,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
("stake-authorize-withdrawer", Some(matches)) => {
parse_stake_authorize(matches, StakeAuthorize::Withdrawer)
}
("stake-set-lockup", Some(matches)) => parse_stake_set_lockup(matches),
("stake-account", Some(matches)) => parse_show_stake_account(matches),
("stake-history", Some(matches)) => parse_show_stake_history(matches),
// Storage Commands
@ -1665,7 +1677,29 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
nonce_authority.as_ref(),
fee_payer.as_ref(),
),
CliCommand::StakeSetLockup {
stake_account_pubkey,
mut lockup,
ref custodian,
sign_only,
ref signers,
blockhash_query,
nonce_account,
ref nonce_authority,
ref fee_payer,
} => process_stake_set_lockup(
&rpc_client,
config,
&stake_account_pubkey,
&mut lockup,
custodian.as_ref(),
*sign_only,
signers,
blockhash_query,
*nonce_account,
nonce_authority.as_ref(),
fee_payer.as_ref(),
),
CliCommand::WithdrawStake {
stake_account_pubkey,
destination_account_pubkey,

View File

@ -47,7 +47,7 @@ fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(STAKE_AUTHORITY_ARG.name)
.long(STAKE_AUTHORITY_ARG.long)
.takes_value(true)
.value_name("KEYPAIR of PUBKEY")
.value_name("KEYPAIR or PUBKEY")
.validator(is_pubkey_or_keypair_or_ask_keyword)
.help(STAKE_AUTHORITY_ARG.help)
}
@ -99,7 +99,7 @@ impl StakeSubCommands for App<'_, '_> {
.arg(
Arg::with_name("custodian")
.long("custodian")
.value_name("PUBKEY")
.value_name("KEYPAIR or PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Identity of the custodian (can withdraw before lockup expires)")
@ -337,7 +337,55 @@ impl StakeSubCommands for App<'_, '_> {
.help("Specify unit to use for request")
)
.arg(withdraw_authority_arg())
)
)
.subcommand(
SubCommand::with_name("stake-set-lockup")
.about("Set Lockup for the stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account for which to set Lockup")
)
.arg(
Arg::with_name("lockup_epoch")
.long("lockup-epoch")
.value_name("EPOCH")
.takes_value(true)
.help("The epoch height at which this account will be available for withdrawal")
)
.arg(
Arg::with_name("lockup_date")
.long("lockup-date")
.value_name("RFC3339 DATE TIME")
.validator(is_rfc3339_datetime)
.takes_value(true)
.help("The date and time at which this account will be available for withdrawal")
)
.arg(
Arg::with_name("new_custodian")
.long("new-custodian")
.value_name("KEYPAIR or PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Identity of the new lockup custodian (can withdraw before lockup expires)")
)
.arg(
Arg::with_name("custodian")
.long("custodian")
.takes_value(true)
.value_name("KEYPAIR or PUBKEY")
.validator(is_pubkey_or_keypair_or_ask_keyword)
.help("Public key of signing custodian (defaults to cli config pubkey)")
)
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.arg(fee_payer_arg())
)
.subcommand(
SubCommand::with_name("stake-account")
.about("Show the contents of a stake account")
@ -553,6 +601,44 @@ pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result<CliCommand
})
}
pub fn parse_stake_set_lockup(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let epoch = value_of(&matches, "lockup_epoch").unwrap_or(0);
let unix_timestamp = unix_timestamp_from_rfc3339_datetime(&matches, "lockup_date").unwrap_or(0);
let new_custodian = pubkey_of(matches, "new_custodian").unwrap_or_default();
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let signers = pubkeys_sigs_of(&matches, SIGNER_ARG.name);
let blockhash_query = BlockhashQuery::new_from_matches(matches);
let require_keypair = signers.is_none();
let nonce_account = pubkey_of(&matches, NONCE_ARG.name);
let custodian = SigningAuthority::new_from_matches(&matches, "custodian", signers.as_deref())?;
let nonce_authority =
SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, signers.as_deref())?;
let fee_payer =
SigningAuthority::new_from_matches(&matches, FEE_PAYER_ARG.name, signers.as_deref())?;
Ok(CliCommandInfo {
command: CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup: Lockup {
custodian: new_custodian,
epoch,
unix_timestamp,
},
custodian,
sign_only,
signers,
blockhash_query,
nonce_account,
nonce_authority,
fee_payer,
},
require_keypair,
})
}
pub fn parse_show_stake_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let use_lamports_unit = matches.is_present("lamports");
@ -984,6 +1070,74 @@ pub fn process_split_stake(
}
}
#[allow(clippy::too_many_arguments)]
pub fn process_stake_set_lockup(
rpc_client: &RpcClient,
config: &CliConfig,
stake_account_pubkey: &Pubkey,
lockup: &mut Lockup,
custodian: Option<&SigningAuthority>,
sign_only: bool,
signers: &Option<Vec<(Pubkey, Signature)>>,
blockhash_query: &BlockhashQuery,
nonce_account: Option<Pubkey>,
nonce_authority: Option<&SigningAuthority>,
fee_payer: Option<&SigningAuthority>,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) =
blockhash_query.get_blockhash_fee_calculator(rpc_client)?;
let custodian = custodian.map(|a| a.keypair()).unwrap_or(&config.keypair);
// If new custodian is not explicitly set, default to current custodian
if lockup.custodian == Pubkey::default() {
lockup.custodian = custodian.pubkey();
}
let ixs = vec![stake_instruction::set_lockup(
stake_account_pubkey,
lockup,
&custodian.pubkey(),
)];
let (nonce_authority, nonce_authority_pubkey) = nonce_authority
.map(|a| (a.keypair(), a.pubkey()))
.unwrap_or((&config.keypair, config.keypair.pubkey()));
let fee_payer = fee_payer.map(|f| f.keypair()).unwrap_or(&config.keypair);
let mut tx = if let Some(nonce_account) = &nonce_account {
Transaction::new_signed_with_nonce(
ixs,
Some(&fee_payer.pubkey()),
&[fee_payer, nonce_authority, custodian],
nonce_account,
&nonce_authority.pubkey(),
recent_blockhash,
)
} else {
Transaction::new_signed_with_payer(
ixs,
Some(&fee_payer.pubkey()),
&[fee_payer, custodian],
recent_blockhash,
)
};
if let Some(signers) = signers {
replace_signatures(&mut tx, &signers)?;
}
if sign_only {
return_signers(&tx)
} else {
if let Some(nonce_account) = &nonce_account {
let nonce_account = rpc_client.get_account(nonce_account)?;
check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?;
}
check_account_for_fee(
rpc_client,
&tx.message.account_keys[0],
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
}
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
fn show_authorized(authorized: &Authorized) {
println!("Authorized Staker: {}", authorized.staker);

View File

@ -860,6 +860,7 @@ fn test_stake_split() {
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_offline = CliConfig::default();
config_offline.json_rpc_url = String::default();
let offline_pubkey = config_offline.keypair.pubkey();
let (_offline_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap();
@ -878,7 +879,6 @@ fn test_stake_split() {
let minimum_stake_balance = rpc_client
.get_minimum_balance_for_rent_exemption(std::mem::size_of::<StakeState>())
.unwrap();
println!("stake min: {}", minimum_stake_balance);
let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
let stake_account_pubkey = stake_keypair.pubkey();
let (stake_keypair_file, mut tmp_file) = make_tmp_file();
@ -902,7 +902,6 @@ fn test_stake_split() {
let minimum_nonce_balance = rpc_client
.get_minimum_balance_for_rent_exemption(NonceState::size())
.unwrap();
println!("nonce min: {}", minimum_nonce_balance);
let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap();
let nonce_account_pubkey = nonce_account.pubkey();
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
@ -935,7 +934,7 @@ fn test_stake_split() {
sign_only: true,
signers: None,
blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()),
nonce_account: Some(nonce_account_pubkey.into()),
nonce_account: Some(nonce_account_pubkey),
nonce_authority: None,
split_stake_account: read_keypair_file(&split_keypair_file).unwrap().into(),
seed: None,
@ -950,7 +949,7 @@ fn test_stake_split() {
sign_only: false,
signers: Some(signers),
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
nonce_account: Some(nonce_account_pubkey.into()),
nonce_account: Some(nonce_account_pubkey),
nonce_authority: Some(offline_pubkey.into()),
split_stake_account: read_keypair_file(&split_keypair_file).unwrap().into(),
seed: None,
@ -972,3 +971,225 @@ fn test_stake_split() {
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}
#[test]
fn test_stake_set_lockup() {
solana_logger::setup();
let (server, leader_data, alice, ledger_path, _voter) = new_validator_for_tests_ex(1, 42_000);
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let mut config = CliConfig::default();
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_offline = CliConfig::default();
config_offline.json_rpc_url = String::default();
let offline_pubkey = config_offline.keypair.pubkey();
let (_offline_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&config_offline.keypair, tmp_file.as_file_mut()).unwrap();
// Verify we're offline
config_offline.command = CliCommand::ClusterVersion;
process_command(&config_offline).unwrap_err();
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 500_000)
.unwrap();
check_balance(500_000, &rpc_client, &config.keypair.pubkey());
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
check_balance(100_000, &rpc_client, &offline_pubkey);
// Create stake account, identity is authority
let minimum_stake_balance = rpc_client
.get_minimum_balance_for_rent_exemption(std::mem::size_of::<StakeState>())
.unwrap();
let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
let stake_account_pubkey = stake_keypair.pubkey();
let (stake_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap();
let mut lockup = Lockup::default();
lockup.custodian = config.keypair.pubkey();
config.command = CliCommand::CreateStakeAccount {
stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(),
seed: None,
staker: Some(offline_pubkey),
withdrawer: Some(offline_pubkey),
lockup,
lamports: 10 * minimum_stake_balance,
};
process_command(&config).unwrap();
check_balance(
10 * minimum_stake_balance,
&rpc_client,
&stake_account_pubkey,
);
// Online set lockup
let mut lockup = Lockup {
unix_timestamp: 1581534570,
epoch: 200,
custodian: Pubkey::default(),
};
config.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: None,
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
fee_payer: None,
};
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_lockup = match stake_state {
StakeState::Initialized(meta) => meta.lockup,
_ => panic!("Unexpected stake state!"),
};
lockup.custodian = config.keypair.pubkey(); // Default new_custodian is config.keypair
assert_eq!(current_lockup, lockup);
// Set custodian to another pubkey
let online_custodian = Keypair::new();
let online_custodian_pubkey = online_custodian.pubkey();
let (online_custodian_file, mut tmp_file) = make_tmp_file();
write_keypair(&online_custodian, tmp_file.as_file_mut()).unwrap();
let lockup = Lockup {
unix_timestamp: 1581534571,
epoch: 201,
custodian: online_custodian_pubkey,
};
config.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: None,
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
fee_payer: None,
};
process_command(&config).unwrap();
let mut lockup = Lockup {
unix_timestamp: 1581534572,
epoch: 202,
custodian: Pubkey::default(),
};
config.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: Some(read_keypair_file(&online_custodian_file).unwrap().into()),
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
fee_payer: None,
};
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_lockup = match stake_state {
StakeState::Initialized(meta) => meta.lockup,
_ => panic!("Unexpected stake state!"),
};
lockup.custodian = online_custodian_pubkey; // Default new_custodian is designated custodian
assert_eq!(current_lockup, lockup);
// Set custodian to offline pubkey
let lockup = Lockup {
unix_timestamp: 1581534573,
epoch: 203,
custodian: offline_pubkey,
};
config.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: Some(online_custodian.into()),
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
fee_payer: None,
};
process_command(&config).unwrap();
// Create nonce account
let minimum_nonce_balance = rpc_client
.get_minimum_balance_for_rent_exemption(NonceState::size())
.unwrap();
let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap();
let nonce_account_pubkey = nonce_account.pubkey();
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap();
config.command = CliCommand::CreateNonceAccount {
nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(),
seed: None,
nonce_authority: Some(offline_pubkey),
lamports: minimum_nonce_balance,
};
process_command(&config).unwrap();
check_balance(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
// Fetch nonce hash
let account = rpc_client.get_account(&nonce_account_pubkey).unwrap();
let nonce_state: NonceState = account.state().unwrap();
let nonce_hash = match nonce_state {
NonceState::Initialized(_meta, hash) => hash,
_ => panic!("Nonce is not initialized"),
};
// Nonced offline set lockup
let lockup = Lockup {
unix_timestamp: 1581534576,
epoch: 222,
custodian: offline_pubkey,
};
config_offline.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: None,
sign_only: true,
signers: None,
blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()),
nonce_account: Some(nonce_account_pubkey),
nonce_authority: None,
fee_payer: None,
};
let sig_response = process_command(&config_offline).unwrap();
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
config.command = CliCommand::StakeSetLockup {
stake_account_pubkey,
lockup,
custodian: Some(offline_pubkey.into()),
sign_only: false,
signers: Some(signers),
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
nonce_account: Some(nonce_account_pubkey),
nonce_authority: Some(offline_pubkey.into()),
fee_payer: Some(offline_pubkey.into()),
};
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_lockup = match stake_state {
StakeState::Initialized(meta) => meta.lockup,
_ => panic!("Unexpected stake state!"),
};
assert_eq!(current_lockup, lockup);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}