wallet: Refuse to delegate stake to a vote account with a stale root slot (#5282)
* Refuse to delegate stake to a vote account with a stale root slot * Remove sdk-c from the virtual manifest temporarily For an unknown reason |cargo clippy| is getting stuck in CI intermittently when trying to build this crate.
This commit is contained in:
parent
911dee24c5
commit
0f5acb86d3
|
@ -359,22 +359,6 @@ name = "c_linked_list"
|
|||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.38"
|
||||
|
@ -3672,20 +3656,6 @@ dependencies = [
|
|||
"untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-sdk-c"
|
||||
version = "0.18.0-pre0"
|
||||
dependencies = [
|
||||
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bs58 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-ed25519-dalek 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"solana-sdk 0.18.0-pre0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-stake-api"
|
||||
version = "0.18.0-pre0"
|
||||
|
@ -5173,7 +5143,6 @@ dependencies = [
|
|||
"checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f"
|
||||
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
|
||||
"checksum c_linked_list 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
|
||||
"checksum cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7e19db9a3892c88c74cbbdcd218196068a928f1b60e736c448b13a1e81f277"
|
||||
"checksum cc 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "ce400c638d48ee0e9ab75aef7997609ec57367ccfe1463f21bf53c3eca67bf46"
|
||||
"checksum cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7fa24eb00d5ffab90eaeaf1092ac85c04c64aaf358ea6f84505b8116d24c6af"
|
||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
|
|
|
@ -3,7 +3,6 @@ members = [
|
|||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"sdk-c",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
|
|
|
@ -28,6 +28,7 @@ OPTIONS:
|
|||
multiple validators in the same workspace
|
||||
--no-airdrop - Do not attempt to airdrop the stake
|
||||
--keypair FILE - Keypair to fund the stake from
|
||||
--force - Override delegate-stake sanity checks
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
|
@ -36,6 +37,7 @@ EOF
|
|||
common_args=()
|
||||
label=
|
||||
airdrops_enabled=1
|
||||
maybe_force=
|
||||
|
||||
positional_args=()
|
||||
while [[ -n $1 ]]; do
|
||||
|
@ -46,6 +48,9 @@ while [[ -n $1 ]]; do
|
|||
elif [[ $1 = --keypair || $1 = -k ]]; then
|
||||
common_args+=("$1" "$2")
|
||||
shift 2
|
||||
elif [[ $1 = --force ]]; then
|
||||
maybe_force=--force
|
||||
shift 2
|
||||
elif [[ $1 = --url || $1 = -u ]]; then
|
||||
url=$2
|
||||
shift 2
|
||||
|
@ -100,6 +105,6 @@ stake_pubkey=$($solana_keygen pubkey "$stake_keypair_path")
|
|||
|
||||
set -x
|
||||
$solana_wallet "${common_args[@]}" \
|
||||
delegate-stake "$stake_keypair_path" "$vote_pubkey" "$stake_lamports"
|
||||
delegate-stake $maybe_force "$stake_keypair_path" "$vote_pubkey" "$stake_lamports"
|
||||
$solana_wallet "${common_args[@]}" show-stake-account "$stake_pubkey"
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ local|tar|skip)
|
|||
waitForNodeToInit
|
||||
|
||||
if [[ $skipSetup != true && $nodeType != blockstreamer ]]; then
|
||||
./multinode-demo/delegate-stake.sh $stake
|
||||
./multinode-demo/delegate-stake.sh --force $stake
|
||||
fi
|
||||
;;
|
||||
replicator)
|
||||
|
|
|
@ -29,6 +29,7 @@ use solana_sdk::transaction::{Transaction, TransactionError};
|
|||
use solana_stake_api::stake_instruction;
|
||||
use solana_storage_api::storage_instruction;
|
||||
use solana_vote_api::vote_instruction;
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
@ -50,7 +51,7 @@ pub enum WalletCommand {
|
|||
AuthorizeVoter(Pubkey, Keypair, Pubkey),
|
||||
CreateVoteAccount(Pubkey, Pubkey, u8, u64),
|
||||
ShowVoteAccount(Pubkey),
|
||||
DelegateStake(Keypair, Pubkey, u64),
|
||||
DelegateStake(Keypair, Pubkey, u64, bool),
|
||||
WithdrawStake(Keypair, Pubkey, u64),
|
||||
DeactivateStake(Keypair),
|
||||
RedeemVoteCredits(Pubkey, Pubkey),
|
||||
|
@ -233,10 +234,12 @@ pub fn parse_command(
|
|||
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
|
||||
let vote_account_pubkey = value_of(matches, "vote_account_pubkey").unwrap();
|
||||
let lamports_to_stake = matches.value_of("lamports_to_stake").unwrap().parse()?;
|
||||
let force = matches.is_present("force");
|
||||
Ok(WalletCommand::DelegateStake(
|
||||
stake_account_keypair,
|
||||
vote_account_pubkey,
|
||||
lamports_to_stake,
|
||||
force,
|
||||
))
|
||||
}
|
||||
("withdraw-stake", Some(matches)) => {
|
||||
|
@ -502,7 +505,6 @@ fn process_show_vote_account(
|
|||
) -> ProcessResult {
|
||||
let vote_account = rpc_client.get_account(vote_account_pubkey)?;
|
||||
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
if vote_account.owner != solana_vote_api::id() {
|
||||
Err(WalletError::RpcRequestError(
|
||||
format!("{:?} is not a vote account", vote_account_pubkey).to_string(),
|
||||
|
@ -587,6 +589,7 @@ fn process_delegate_stake(
|
|||
stake_account_keypair: &Keypair,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
force: bool,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
|
@ -597,6 +600,39 @@ fn process_delegate_stake(
|
|||
lamports,
|
||||
);
|
||||
|
||||
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
||||
// voted at the tip of the ledger
|
||||
let vote_account_data = rpc_client.get_account_data(vote_account_pubkey)?;
|
||||
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
|
||||
WalletError::RpcRequestError(
|
||||
"Account data could not be deserialized to vote state".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let sanity_check_result = match vote_state.root_slot {
|
||||
None => Err(WalletError::DynamicProgramError(
|
||||
"Vote account has no root slot".to_string(),
|
||||
)),
|
||||
Some(root_slot) => {
|
||||
let slot = rpc_client.get_slot()?;
|
||||
if root_slot + solana_sdk::timing::DEFAULT_SLOTS_PER_TURN < slot {
|
||||
Err(WalletError::DynamicProgramError(
|
||||
"Vote account root slot is too old".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if sanity_check_result.is_err() {
|
||||
if !force {
|
||||
sanity_check_result?;
|
||||
} else {
|
||||
println!("--force supplied, ignoring: {:?}", sanity_check_result);
|
||||
}
|
||||
}
|
||||
|
||||
let mut tx = Transaction::new_signed_with_payer(
|
||||
ixs,
|
||||
Some(&config.keypair.pubkey()),
|
||||
|
@ -1056,15 +1092,19 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
|||
process_show_vote_account(&rpc_client, config, &vote_account_pubkey)
|
||||
}
|
||||
|
||||
WalletCommand::DelegateStake(stake_account_keypair, vote_account_pubkey, lamports) => {
|
||||
process_delegate_stake(
|
||||
&rpc_client,
|
||||
config,
|
||||
&stake_account_keypair,
|
||||
&vote_account_pubkey,
|
||||
*lamports,
|
||||
)
|
||||
}
|
||||
WalletCommand::DelegateStake(
|
||||
stake_account_keypair,
|
||||
vote_account_pubkey,
|
||||
lamports,
|
||||
force,
|
||||
) => process_delegate_stake(
|
||||
&rpc_client,
|
||||
config,
|
||||
&stake_account_keypair,
|
||||
&vote_account_pubkey,
|
||||
*lamports,
|
||||
*force,
|
||||
),
|
||||
|
||||
WalletCommand::WithdrawStake(
|
||||
stake_account_keypair,
|
||||
|
@ -1403,6 +1443,13 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
.subcommand(
|
||||
SubCommand::with_name("delegate-stake")
|
||||
.about("Delegate stake to a vote account")
|
||||
.arg(
|
||||
Arg::with_name("force")
|
||||
.long("force")
|
||||
.takes_value(false)
|
||||
.hidden(true) // Don't document this argument to discourage its use
|
||||
.help("Override vote account sanity checks (use carefully!)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("stake_account_keypair_file")
|
||||
.index(1)
|
||||
|
@ -1883,7 +1930,21 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_delegate_stake).unwrap(),
|
||||
WalletCommand::DelegateStake(keypair, pubkey, 42)
|
||||
WalletCommand::DelegateStake(keypair, pubkey, 42, false)
|
||||
);
|
||||
|
||||
let keypair = read_keypair(&keypair_file).unwrap();
|
||||
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"delegate-stake",
|
||||
"--force",
|
||||
&keypair_file,
|
||||
&pubkey_string,
|
||||
"42",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&pubkey, &test_delegate_stake).unwrap(),
|
||||
WalletCommand::DelegateStake(keypair, pubkey, 42, true)
|
||||
);
|
||||
|
||||
// Test WithdrawStake Subcommand
|
||||
|
@ -2072,11 +2133,15 @@ mod tests {
|
|||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
|
||||
// TODO: Need to add mock GetAccountInfo to mock_rpc_client_request.rs to re-enable the
|
||||
// DeactivateStake test.
|
||||
/*
|
||||
let bob_keypair = Keypair::new();
|
||||
let node_pubkey = Pubkey::new_rand();
|
||||
config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_pubkey, 100);
|
||||
config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_pubkey, 100, true);
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
*/
|
||||
|
||||
let bob_keypair = Keypair::new();
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
|
|
Loading…
Reference in New Issue