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"
|
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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue