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:
Michael Vines 2019-08-01 21:08:24 -07:00 committed by GitHub
parent 911dee24c5
commit 0f5acb86d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 47 deletions

31
Cargo.lock generated
View File

@ -359,22 +359,6 @@ name = "c_linked_list"
version = "1.1.1" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "cc" name = "cc"
version = "1.0.38" version = "1.0.38"
@ -3672,20 +3656,6 @@ dependencies = [
"untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "solana-stake-api" name = "solana-stake-api"
version = "0.18.0-pre0" 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 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 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 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 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 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" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"

View File

@ -3,7 +3,6 @@ members = [
"bench-exchange", "bench-exchange",
"bench-streamer", "bench-streamer",
"bench-tps", "bench-tps",
"sdk-c",
"chacha-sys", "chacha-sys",
"client", "client",
"core", "core",

View File

@ -28,6 +28,7 @@ OPTIONS:
multiple validators in the same workspace multiple validators in the same workspace
--no-airdrop - Do not attempt to airdrop the stake --no-airdrop - Do not attempt to airdrop the stake
--keypair FILE - Keypair to fund the stake from --keypair FILE - Keypair to fund the stake from
--force - Override delegate-stake sanity checks
EOF EOF
exit 1 exit 1
@ -36,6 +37,7 @@ EOF
common_args=() common_args=()
label= label=
airdrops_enabled=1 airdrops_enabled=1
maybe_force=
positional_args=() positional_args=()
while [[ -n $1 ]]; do while [[ -n $1 ]]; do
@ -46,6 +48,9 @@ while [[ -n $1 ]]; do
elif [[ $1 = --keypair || $1 = -k ]]; then elif [[ $1 = --keypair || $1 = -k ]]; then
common_args+=("$1" "$2") common_args+=("$1" "$2")
shift 2 shift 2
elif [[ $1 = --force ]]; then
maybe_force=--force
shift 2
elif [[ $1 = --url || $1 = -u ]]; then elif [[ $1 = --url || $1 = -u ]]; then
url=$2 url=$2
shift 2 shift 2
@ -100,6 +105,6 @@ stake_pubkey=$($solana_keygen pubkey "$stake_keypair_path")
set -x set -x
$solana_wallet "${common_args[@]}" \ $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" $solana_wallet "${common_args[@]}" show-stake-account "$stake_pubkey"

View File

@ -261,7 +261,7 @@ local|tar|skip)
waitForNodeToInit waitForNodeToInit
if [[ $skipSetup != true && $nodeType != blockstreamer ]]; then if [[ $skipSetup != true && $nodeType != blockstreamer ]]; then
./multinode-demo/delegate-stake.sh $stake ./multinode-demo/delegate-stake.sh --force $stake
fi fi
;; ;;
replicator) replicator)

View File

@ -29,6 +29,7 @@ use solana_sdk::transaction::{Transaction, TransactionError};
use solana_stake_api::stake_instruction; use solana_stake_api::stake_instruction;
use solana_storage_api::storage_instruction; use solana_storage_api::storage_instruction;
use solana_vote_api::vote_instruction; use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::VoteState;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
@ -50,7 +51,7 @@ pub enum WalletCommand {
AuthorizeVoter(Pubkey, Keypair, Pubkey), AuthorizeVoter(Pubkey, Keypair, Pubkey),
CreateVoteAccount(Pubkey, Pubkey, u8, u64), CreateVoteAccount(Pubkey, Pubkey, u8, u64),
ShowVoteAccount(Pubkey), ShowVoteAccount(Pubkey),
DelegateStake(Keypair, Pubkey, u64), DelegateStake(Keypair, Pubkey, u64, bool),
WithdrawStake(Keypair, Pubkey, u64), WithdrawStake(Keypair, Pubkey, u64),
DeactivateStake(Keypair), DeactivateStake(Keypair),
RedeemVoteCredits(Pubkey, Pubkey), RedeemVoteCredits(Pubkey, Pubkey),
@ -233,10 +234,12 @@ pub fn parse_command(
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap(); let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
let vote_account_pubkey = value_of(matches, "vote_account_pubkey").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 lamports_to_stake = matches.value_of("lamports_to_stake").unwrap().parse()?;
let force = matches.is_present("force");
Ok(WalletCommand::DelegateStake( Ok(WalletCommand::DelegateStake(
stake_account_keypair, stake_account_keypair,
vote_account_pubkey, vote_account_pubkey,
lamports_to_stake, lamports_to_stake,
force,
)) ))
} }
("withdraw-stake", Some(matches)) => { ("withdraw-stake", Some(matches)) => {
@ -502,7 +505,6 @@ fn process_show_vote_account(
) -> ProcessResult { ) -> ProcessResult {
let vote_account = rpc_client.get_account(vote_account_pubkey)?; 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() { if vote_account.owner != solana_vote_api::id() {
Err(WalletError::RpcRequestError( Err(WalletError::RpcRequestError(
format!("{:?} is not a vote account", vote_account_pubkey).to_string(), format!("{:?} is not a vote account", vote_account_pubkey).to_string(),
@ -587,6 +589,7 @@ fn process_delegate_stake(
stake_account_keypair: &Keypair, stake_account_keypair: &Keypair,
vote_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey,
lamports: u64, lamports: u64,
force: bool,
) -> ProcessResult { ) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
@ -597,6 +600,39 @@ fn process_delegate_stake(
lamports, 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( let mut tx = Transaction::new_signed_with_payer(
ixs, ixs,
Some(&config.keypair.pubkey()), 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) process_show_vote_account(&rpc_client, config, &vote_account_pubkey)
} }
WalletCommand::DelegateStake(stake_account_keypair, vote_account_pubkey, lamports) => { WalletCommand::DelegateStake(
process_delegate_stake( stake_account_keypair,
vote_account_pubkey,
lamports,
force,
) => process_delegate_stake(
&rpc_client, &rpc_client,
config, config,
&stake_account_keypair, &stake_account_keypair,
&vote_account_pubkey, &vote_account_pubkey,
*lamports, *lamports,
) *force,
} ),
WalletCommand::WithdrawStake( WalletCommand::WithdrawStake(
stake_account_keypair, stake_account_keypair,
@ -1403,6 +1443,13 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.subcommand( .subcommand(
SubCommand::with_name("delegate-stake") SubCommand::with_name("delegate-stake")
.about("Delegate stake to a vote account") .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(
Arg::with_name("stake_account_keypair_file") Arg::with_name("stake_account_keypair_file")
.index(1) .index(1)
@ -1883,7 +1930,21 @@ mod tests {
]); ]);
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(), 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 // Test WithdrawStake Subcommand
@ -2072,11 +2133,15 @@ mod tests {
let signature = process_command(&config); let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); 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 bob_keypair = Keypair::new();
let node_pubkey = Pubkey::new_rand(); 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); let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); assert_eq!(signature.unwrap(), SIGNATURE.to_string());
*/
let bob_keypair = Keypair::new(); let bob_keypair = Keypair::new();
let to_pubkey = Pubkey::new_rand(); let to_pubkey = Pubkey::new_rand();