passive staking 4 (#4240)

* support passive staking with wallet, use it

* fixups

* clippy

* cleanup app generation in wallet, finish fullnode.sh staking

* _id and _keypair => pubkey
use keygen, not wallet to get pubkey

* found 'em
This commit is contained in:
Rob Walker 2019-05-09 19:31:42 -07:00 committed by GitHub
parent 81fa69d347
commit ba8f49366d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 568 additions and 519 deletions

1
Cargo.lock generated
View File

@ -2779,6 +2779,7 @@ dependencies = [
"solana-logger 0.15.0",
"solana-netutil 0.15.0",
"solana-sdk 0.15.0",
"solana-stake-api 0.15.0",
"solana-vote-api 0.15.0",
"solana-vote-signer 0.15.0",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -72,32 +72,51 @@ rsync_url() { # adds the 'rsync://` prefix to URLs that need it
echo "rsync://$url"
}
setup_vote_account() {
setup_vote_and_stake_accounts() {
declare entrypoint_ip=$1
declare node_keypair_path=$2
declare vote_keypair_path=$3
declare stake=$4
declare stake_keypair_path=$4
declare stake=$5
declare node_keypair
node_keypair=$($solana_wallet --keypair "$node_keypair_path" address)
declare node_pubkey
node_pubkey=$($solana_keygen pubkey "$node_keypair_path")
declare vote_keypair
vote_keypair=$($solana_wallet --keypair "$vote_keypair_path" address)
declare vote_pubkey
vote_pubkey=$($solana_keygen pubkey "$vote_keypair_path")
if [[ -f "$vote_keypair_path".configured ]]; then
echo "Vote account has already been configured"
declare stake_pubkey
stake_pubkey=$($solana_keygen pubkey "$stake_keypair_path")
if [[ -f "$node_keypair_path".configured ]]; then
echo "Vote and stake accounts have already been configured"
else
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" airdrop "$stake" || return $?
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" airdrop $((stake*2+1)) || return $?
# Fund the vote account from the node, with the node as the node_keypair
# Fund the vote account from the node, with the node as the node_pubkey
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
create-vote-account "$vote_keypair" "$node_keypair" $((stake - 1)) || return $?
create-vote-account "$vote_pubkey" "$node_pubkey" "$stake" || return $?
touch "$vote_keypair_path".configured
# Fund the stake account from the node, with the node as the node_pubkey
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
create-stake-account "$stake_pubkey" "$stake" || return $?
# Delegate the stake. The transaction fee is paid by the node but the
# transaction must be signed by the stake_keypair
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
delegate-stake "$stake_keypair_path" "$vote_pubkey" || return $?
touch "$node_keypair_path".configured
fi
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
show-vote-account "$vote_keypair"
$solana_wallet --url "http://$entrypoint_ip:8899" \
show-vote-account "$vote_pubkey"
$solana_wallet --url "http://$entrypoint_ip:8899" \
show-stake-account "$stake_pubkey"
return 0
}
@ -223,8 +242,8 @@ elif [[ $node_type = replicator ]]; then
[[ -r "$replicator_keypair_path" ]] || $solana_keygen -o "$replicator_keypair_path"
[[ -r "$replicator_storage_keypair_path" ]] || $solana_keygen -o "$replicator_storage_keypair_path"
replicator_keypair=$($solana_keygen pubkey "$replicator_keypair_path")
replicator_storage_keypair=$($solana_keygen pubkey "$replicator_storage_keypair_path")
replicator_pubkey=$($solana_keygen pubkey "$replicator_keypair_path")
replicator_storage_pubkey=$($solana_keygen pubkey "$replicator_storage_keypair_path")
default_arg --entrypoint "$entrypoint_address"
default_arg --identity "$replicator_keypair_path"
@ -241,12 +260,14 @@ else
: "${fullnode_keypair_path:=$SOLANA_CONFIG_DIR/fullnode-keypair$label.json}"
fullnode_vote_keypair_path=$SOLANA_CONFIG_DIR/fullnode-vote-keypair$label.json
fullnode_stake_keypair_path=$SOLANA_CONFIG_DIR/fullnode-stake-keypair$label.json
ledger_config_dir=$SOLANA_CONFIG_DIR/fullnode-ledger$label
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts$label
mkdir -p "$SOLANA_CONFIG_DIR"
[[ -r "$fullnode_keypair_path" ]] || $solana_keygen -o "$fullnode_keypair_path"
[[ -r "$fullnode_vote_keypair_path" ]] || $solana_keygen -o "$fullnode_vote_keypair_path"
[[ -r "$fullnode_stake_keypair_path" ]] || $solana_keygen -o "$fullnode_stake_keypair_path"
default_arg --entrypoint "$entrypoint_address"
default_arg --rpc-drone-address "${entrypoint_address%:*}:9900"
@ -258,8 +279,8 @@ fi
if [[ $node_type = replicator ]]; then
cat <<EOF
======================[ Replicator configuration ]======================
replicator pubkey: $replicator_keypair
storage pubkey: $replicator_storage_keypair
replicator pubkey: $replicator_pubkey
storage pubkey: $replicator_storage_pubkey
ledger: $ledger_config_dir
======================================================================
EOF
@ -267,13 +288,13 @@ EOF
else
fullnode_keypair=$($solana_keygen pubkey "$fullnode_keypair_path")
fullnode_vote_keypair=$($solana_keygen pubkey "$fullnode_vote_keypair_path")
fullnode_pubkey=$($solana_keygen pubkey "$fullnode_keypair_path")
fullnode_vote_pubkey=$($solana_keygen pubkey "$fullnode_vote_keypair_path")
cat <<EOF
======================[ Fullnode configuration ]======================
node pubkey: $fullnode_keypair
vote pubkey: $fullnode_vote_keypair
node pubkey: $fullnode_pubkey
vote pubkey: $fullnode_vote_pubkey
ledger: $ledger_config_dir
accounts: $accounts_config_dir
======================================================================
@ -281,7 +302,7 @@ EOF
default_arg --identity "$fullnode_keypair_path"
default_arg --voting-keypair "$fullnode_vote_keypair_path"
default_arg --vote-account "$fullnode_vote_keypair"
default_arg --vote-account "$fullnode_vote_pubkey"
default_arg --ledger "$ledger_config_dir"
default_arg --accounts "$accounts_config_dir"
@ -330,7 +351,7 @@ while true; do
trap '[[ -n $pid ]] && kill "$pid" >/dev/null 2>&1 && wait "$pid"' INT TERM ERR
if [[ $node_type = validator ]] && ((stake)); then
setup_vote_account "${entrypoint_address%:*}" "$fullnode_keypair_path" "$fullnode_vote_keypair_path" "$stake"
setup_vote_and_stake_accounts "${entrypoint_address%:*}" "$fullnode_keypair_path" "$fullnode_vote_keypair_path" "$fullnode_stake_keypair_path" "$stake"
elif [[ $node_type = replicator ]] && ((stake)); then
setup_replicator_account "${entrypoint_address%:*}" "$replicator_keypair_path" "$stake"
fi

View File

@ -23,6 +23,7 @@ solana-drone = { path = "../drone", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
solana-stake-api = { path = "../programs/stake_api", version = "0.15.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.15.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.15.0" }
url = "1.7.2"

View File

@ -1,9 +1,6 @@
use clap::{
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
};
use solana_sdk::pubkey::Pubkey;
use clap::{crate_description, crate_name, crate_version, Arg, ArgMatches};
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
use solana_wallet::wallet::{parse_command, process_command, WalletConfig, WalletError};
use solana_wallet::wallet::{app, parse_command, process_command, WalletConfig, WalletError};
use std::error;
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn error::Error>> {
@ -76,24 +73,13 @@ fn is_url(string: String) -> Result<(), String> {
}
}
// Return an error if a pubkey cannot be parsed.
fn is_pubkey(string: String) -> Result<(), String> {
match string.parse::<Pubkey>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}
}
fn main() -> Result<(), Box<dyn error::Error>> {
solana_logger::setup();
let default = WalletConfig::default();
let default_drone_port = format!("{}", default.drone_port);
let matches = App::new(crate_name!())
.about(crate_description!())
.version(crate_version!())
.setting(AppSettings::SubcommandRequiredElseHelp)
let matches = app(crate_name!(), crate_description!(), crate_version!())
.arg(
Arg::with_name("json_rpc_url")
.short("u")
@ -127,235 +113,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.takes_value(true)
.help("/path/to/id.json"),
)
.subcommand(SubCommand::with_name("address").about("Get your public key"))
.subcommand(
SubCommand::with_name("airdrop")
.about("Request a batch of lamports")
.arg(
Arg::with_name("lamports")
.index(1)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to request"),
),
)
.subcommand(
SubCommand::with_name("balance")
.about("Get your balance")
.arg(
Arg::with_name("pubkey")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey)
.help("The public key of the balance to check"),
),
)
.subcommand(
SubCommand::with_name("cancel")
.about("Cancel a transfer")
.arg(
Arg::with_name("process_id")
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The process id of the transfer to cancel"),
),
)
.subcommand(
SubCommand::with_name("confirm")
.about("Confirm transaction by signature")
.arg(
Arg::with_name("signature")
.index(1)
.value_name("SIGNATURE")
.takes_value(true)
.required(true)
.help("The transaction signature to confirm"),
),
)
.subcommand(
SubCommand::with_name("authorize-voter")
.about("Authorize a different voter for this account")
.arg(
Arg::with_name("authorized-voter-id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote signer to authorize"),
),
)
.subcommand(
SubCommand::with_name("create-vote-account")
.about("Create staking account for node")
.arg(
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("node_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
.index(3)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
)
.arg(
Arg::with_name("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission on rewards this vote account should take, defaults to zero")
),
)
.subcommand(
SubCommand::with_name("show-vote-account")
.about("Show the contents of a vote account")
.arg(
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote account pubkey"),
)
)
.subcommand(
SubCommand::with_name("deploy")
.about("Deploy a program")
.arg(
Arg::with_name("program_location")
.index(1)
.value_name("PATH")
.takes_value(true)
.required(true)
.help("/path/to/program.o"),
), // TODO: Add "loader" argument; current default is bpf_loader
)
.subcommand(
SubCommand::with_name("get-transaction-count").about("Get current transaction count"),
)
.subcommand(
SubCommand::with_name("pay")
.about("Send a payment")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("lamports")
.index(2)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send"),
)
.arg(
Arg::with_name("timestamp")
.long("after")
.value_name("DATETIME")
.takes_value(true)
.help("A timestamp after which transaction will execute"),
)
.arg(
Arg::with_name("timestamp_pubkey")
.long("require-timestamp-from")
.value_name("PUBKEY")
.takes_value(true)
.requires("timestamp")
.validator(is_pubkey)
.help("Require timestamp from this third party"),
)
.arg(
Arg::with_name("witness")
.long("require-signature-from")
.value_name("PUBKEY")
.takes_value(true)
.multiple(true)
.use_delimiter(true)
.validator(is_pubkey)
.help("Any third party signatures required to unlock the lamports"),
)
.arg(
Arg::with_name("cancelable")
.long("cancelable")
.takes_value(false),
),
)
.subcommand(
SubCommand::with_name("send-signature")
.about("Send a signature to authorize a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to authorize"),
),
)
.subcommand(
SubCommand::with_name("send-timestamp")
.about("Send a timestamp to unlock a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to unlock"),
)
.arg(
Arg::with_name("datetime")
.long("date")
.value_name("DATETIME")
.takes_value(true)
.help("Optional arbitrary timestamp to apply"),
),
)
.get_matches();
let config = parse_args(&matches)?;

View File

@ -1,6 +1,6 @@
use bs58;
use chrono::prelude::*;
use clap::ArgMatches;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use log::*;
use num_traits::FromPrimitive;
use serde_json;
@ -18,14 +18,15 @@ use solana_drone::drone_mock::request_airdrop_transaction;
use solana_sdk::bpf_loader;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::InstructionError;
use solana_sdk::instruction_processor_utils::DecodeError;
use solana_sdk::instruction_processor_utils::{DecodeError, State};
use solana_sdk::loader_instruction;
use solana_sdk::message::Message;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signature};
use solana_sdk::system_instruction::SystemError;
use solana_sdk::system_transaction;
use solana_sdk::transaction::{Transaction, TransactionError};
use solana_stake_api::stake_instruction;
use solana_vote_api::vote_instruction;
use std::fs::File;
use std::io::Read;
@ -37,16 +38,19 @@ use std::{error, fmt, mem};
const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE
#[derive(Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum WalletCommand {
Address,
Airdrop(u64),
Balance(Pubkey),
Cancel(Pubkey),
Confirm(Signature),
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
AuthorizeVoter(Pubkey),
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
ShowVoteAccount(Pubkey),
CreateStakeAccount(Pubkey, u64),
DelegateStake(Keypair, Pubkey),
ShowStakeAccount(Pubkey),
Deploy(String),
GetTransactionCount,
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
@ -141,6 +145,11 @@ fn pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Pubkey>> {
.map(|xs| xs.map(|x| x.parse::<Pubkey>().unwrap()).collect())
}
// Return the keypair for an argument with filename `name` or None if not present.
fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
matches.value_of(name).map(|x| read_keypair(x).unwrap())
}
pub fn parse_command(
pubkey: &Pubkey,
matches: &ArgMatches<'_>,
@ -172,10 +181,6 @@ pub fn parse_command(
Err(WalletError::BadParameter("Invalid signature".to_string()))
}
}
("authorize-voter", Some(matches)) => {
let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap();
Ok(WalletCommand::AuthorizeVoter(authorized_voter_id))
}
("create-vote-account", Some(matches)) => {
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
let node_id = pubkey_of(matches, "node_id").unwrap();
@ -192,10 +197,35 @@ pub fn parse_command(
lamports,
))
}
("authorize-voter", Some(matches)) => {
let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap();
Ok(WalletCommand::AuthorizeVoter(authorized_voter_id))
}
("show-vote-account", Some(matches)) => {
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
Ok(WalletCommand::ShowVoteAccount(voting_account_id))
}
("create-stake-account", Some(matches)) => {
let staking_account_id = pubkey_of(matches, "staking_account_id").unwrap();
let lamports = matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateStakeAccount(
staking_account_id,
lamports,
))
}
("delegate-stake", Some(matches)) => {
let staking_account_keypair =
keypair_of(matches, "staking_account_keypair_file").unwrap();
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
Ok(WalletCommand::DelegateStake(
staking_account_keypair,
voting_account_id,
))
}
("show-stake-account", Some(matches)) => {
let staking_account_id = pubkey_of(matches, "staking_account_id").unwrap();
Ok(WalletCommand::ShowStakeAccount(staking_account_id))
}
("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
deploy_matches
.value_of("program_location")
@ -324,7 +354,7 @@ fn process_balance(pubkey: &Pubkey, rpc_client: &RpcClient) -> ProcessResult {
}
}
fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResult {
fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResult {
match rpc_client.get_signature_status(&signature.to_string()) {
Ok(status) => {
if let Some(result) = status {
@ -343,23 +373,7 @@ fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResul
}
}
fn process_authorize_voter(
rpc_client: &RpcClient,
config: &WalletConfig,
authorized_voter_id: Pubkey,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::authorize_voter(
&config.keypair.pubkey(),
&authorized_voter_id,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
Ok(signature_str.to_string())
}
fn process_create_staking(
fn process_create_vote_account(
rpc_client: &RpcClient,
config: &WalletConfig,
voting_account_id: &Pubkey,
@ -367,7 +381,6 @@ fn process_create_staking(
commission: u32,
lamports: u64,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs = vote_instruction::create_account(
&config.keypair.pubkey(),
voting_account_id,
@ -375,12 +388,29 @@ fn process_create_staking(
commission,
lamports,
);
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
Ok(signature_str.to_string())
}
fn process_show_staking(
fn process_authorize_voter(
rpc_client: &RpcClient,
config: &WalletConfig,
authorized_voter_id: &Pubkey,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::authorize_voter(
&config.keypair.pubkey(),
authorized_voter_id,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
Ok(signature_str.to_string())
}
fn process_show_vote_account(
rpc_client: &RpcClient,
_config: &WalletConfig,
voting_account_id: &Pubkey,
@ -388,7 +418,11 @@ fn process_show_staking(
use solana_vote_api::vote_state::VoteState;
let vote_account_lamports = rpc_client.retry_get_balance(voting_account_id, 5)?;
let vote_account_data = rpc_client.get_account_data(voting_account_id)?;
let vote_state = VoteState::deserialize(&vote_account_data).unwrap();
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
WalletError::RpcRequestError(
"Account data could not be deserialized to vote state".to_string(),
)
})?;
println!("account lamports: {}", vote_account_lamports.unwrap());
println!("node id: {}", vote_state.node_id);
@ -417,6 +451,68 @@ fn process_show_staking(
Ok("".to_string())
}
fn process_create_stake_account(
rpc_client: &RpcClient,
config: &WalletConfig,
staking_account_id: &Pubkey,
lamports: u64,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::create_account(
&config.keypair.pubkey(),
staking_account_id,
lamports,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
Ok(signature_str.to_string())
}
fn process_delegate_stake(
rpc_client: &RpcClient,
config: &WalletConfig,
staking_account_keypair: &Keypair,
voting_account_id: &Pubkey,
) -> ProcessResult {
let recent_blockhash = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::delegate_stake(
&config.keypair.pubkey(),
&staking_account_keypair.pubkey(),
voting_account_id,
)];
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, &staking_account_keypair],
ixs,
recent_blockhash,
);
let signature_str = rpc_client
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &staking_account_keypair])?;
Ok(signature_str.to_string())
}
fn process_show_stake_account(
rpc_client: &RpcClient,
_config: &WalletConfig,
staking_account_id: &Pubkey,
) -> ProcessResult {
use solana_stake_api::stake_state::StakeState;
let stake_account = rpc_client.get_account(staking_account_id)?;
match stake_account.state() {
Ok(StakeState::Delegate {
voter_id,
credits_observed,
}) => {
println!("account lamports: {}", stake_account.lamports);
println!("voter id: {}", voter_id);
println!("credits observed: {}", credits_observed);
Ok("".to_string())
}
_ => Err(WalletError::RpcRequestError(
"Account data could not be deserialized to stake state".to_string(),
))?,
}
}
fn process_deploy(
rpc_client: &RpcClient,
config: &WalletConfig,
@ -656,13 +752,13 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
config.rpc_client.as_ref().unwrap()
};
match config.command {
match &config.command {
// Get address of this client
WalletCommand::Address => unreachable!(),
// Request an airdrop from Solana Drone;
WalletCommand::Airdrop(lamports) => {
process_airdrop(&rpc_client, config, drone_addr, lamports)
process_airdrop(&rpc_client, config, drone_addr, *lamports)
}
// Check client balance
@ -674,25 +770,43 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
// Confirm the last client transaction by signature
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
// Configure staking account already created
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
process_authorize_voter(&rpc_client, config, authorized_voter_id)
}
// Create staking account
// Create vote account
WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => {
process_create_staking(
process_create_vote_account(
&rpc_client,
config,
&voting_account_id,
&node_id,
commission,
lamports,
*commission,
*lamports,
)
}
// Configure staking account already created
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
process_authorize_voter(&rpc_client, config, &authorized_voter_id)
}
// Show a vote account
WalletCommand::ShowVoteAccount(voting_account_id) => {
process_show_staking(&rpc_client, config, &voting_account_id)
process_show_vote_account(&rpc_client, config, &voting_account_id)
}
// Create stake account
WalletCommand::CreateStakeAccount(staking_account_id, lamports) => {
process_create_stake_account(&rpc_client, config, &staking_account_id, *lamports)
}
// Create stake account
WalletCommand::DelegateStake(staking_account_keypair, voting_account_id) => {
process_delegate_stake(
&rpc_client,
config,
&staking_account_keypair,
&voting_account_id,
)
}
// Show a vote account
WalletCommand::ShowStakeAccount(staking_account_id) => {
process_show_stake_account(&rpc_client, config, &staking_account_id)
}
// Deploy a custom program to the chain
@ -713,17 +827,17 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
) => process_pay(
&rpc_client,
config,
lamports,
*lamports,
&to,
timestamp,
timestamp_pubkey,
*timestamp,
*timestamp_pubkey,
witnesses,
cancelable,
*cancelable,
),
// Apply time elapsed to contract
WalletCommand::TimeElapsed(to, pubkey, dt) => {
process_time_elapsed(&rpc_client, config, drone_addr, &to, &pubkey, dt)
process_time_elapsed(&rpc_client, config, drone_addr, &to, &pubkey, *dt)
}
// Apply witness signature to contract
@ -823,12 +937,312 @@ where
}
}
// Return an error if a pubkey cannot be parsed.
fn is_pubkey(string: String) -> Result<(), String> {
match string.parse::<Pubkey>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}
}
pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> {
App::new(name)
.about(about)
.version(version)
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(SubCommand::with_name("address").about("Get your public key"))
.subcommand(
SubCommand::with_name("airdrop")
.about("Request a batch of lamports")
.arg(
Arg::with_name("lamports")
.index(1)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to request"),
),
)
.subcommand(
SubCommand::with_name("balance")
.about("Get your balance")
.arg(
Arg::with_name("pubkey")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey)
.help("The public key of the balance to check"),
),
)
.subcommand(
SubCommand::with_name("cancel")
.about("Cancel a transfer")
.arg(
Arg::with_name("process_id")
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The process id of the transfer to cancel"),
),
)
.subcommand(
SubCommand::with_name("confirm")
.about("Confirm transaction by signature")
.arg(
Arg::with_name("signature")
.index(1)
.value_name("SIGNATURE")
.takes_value(true)
.required(true)
.help("The transaction signature to confirm"),
),
)
.subcommand(
SubCommand::with_name("authorize-voter")
.about("Authorize a different voter for this vote account")
.arg(
Arg::with_name("authorized_voter_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote signer to authorize"),
),
)
.subcommand(
SubCommand::with_name("create-vote-account")
.about("Create vote account for a node")
.arg(
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote account address to fund"),
)
.arg(
Arg::with_name("node_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Node that will vote in this account"),
)
.arg(
Arg::with_name("lamports")
.index(3)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to the vote account"),
)
.arg(
Arg::with_name("commission")
.long("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission taken on reward redemption, default: 0"),
),
)
.subcommand(
SubCommand::with_name("show-vote-account")
.about("Show the contents of a vote account")
.arg(
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Vote account pubkey"),
)
)
.subcommand(
SubCommand::with_name("create-stake-account")
.about("Create staking account")
.arg(
Arg::with_name("staking_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
.index(2)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
),
)
.subcommand(
SubCommand::with_name("delegate-stake")
.about("Delegate the stake to some vote account")
.arg(
Arg::with_name("staking_account_keypair_file")
.index(1)
.value_name("KEYPAIR_FILE")
.takes_value(true)
.required(true)
.help("Keypair file for the staking account, for signing the delegate transaction."),
)
.arg(
Arg::with_name("voting_account_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The voting account to which to delegate the stake."),
),
)
.subcommand(
SubCommand::with_name("show-stake-account")
.about("Show the contents of a stake account")
.arg(
Arg::with_name("staking_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("Stake account pubkey"),
)
)
.subcommand(
SubCommand::with_name("deploy")
.about("Deploy a program")
.arg(
Arg::with_name("program_location")
.index(1)
.value_name("PATH")
.takes_value(true)
.required(true)
.help("/path/to/program.o"),
), // TODO: Add "loader" argument; current default is bpf_loader
)
.subcommand(
SubCommand::with_name("get-transaction-count")
.about("Get current transaction count"),
)
.subcommand(
SubCommand::with_name("pay")
.about("Send a payment")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("lamports")
.index(2)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send"),
)
.arg(
Arg::with_name("timestamp")
.long("after")
.value_name("DATETIME")
.takes_value(true)
.help("A timestamp after which transaction will execute"),
)
.arg(
Arg::with_name("timestamp_pubkey")
.long("require-timestamp-from")
.value_name("PUBKEY")
.takes_value(true)
.requires("timestamp")
.validator(is_pubkey)
.help("Require timestamp from this third party"),
)
.arg(
Arg::with_name("witness")
.long("require-signature-from")
.value_name("PUBKEY")
.takes_value(true)
.multiple(true)
.use_delimiter(true)
.validator(is_pubkey)
.help("Any third party signatures required to unlock the lamports"),
)
.arg(
Arg::with_name("cancelable")
.long("cancelable")
.takes_value(false),
),
)
.subcommand(
SubCommand::with_name("send-signature")
.about("Send a signature to authorize a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to authorize"),
),
)
.subcommand(
SubCommand::with_name("send-timestamp")
.about("Send a timestamp to unlock a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to unlock"),
)
.arg(
Arg::with_name("datetime")
.long("date")
.value_name("DATETIME")
.takes_value(true)
.help("Optional arbitrary timestamp to apply"),
),
)
}
#[cfg(test)]
mod tests {
use super::*;
use clap::{App, Arg, SubCommand};
use serde_json::Value;
use solana_client::mock_rpc_client_request::SIGNATURE;
use solana_sdk::signature::gen_keypair_file;
use solana_sdk::transaction::TransactionError;
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
@ -855,204 +1269,8 @@ mod tests {
#[test]
fn test_wallet_parse_command() {
let test_commands = App::new("test")
.subcommand(SubCommand::with_name("address").about("Get your public key"))
.subcommand(
SubCommand::with_name("airdrop")
.about("Request a batch of lamports")
.arg(
Arg::with_name("lamports")
.index(1)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to request"),
),
)
.subcommand(SubCommand::with_name("balance").about("Get your balance"))
.subcommand(
SubCommand::with_name("cancel")
.about("Cancel a transfer")
.arg(
Arg::with_name("process_id")
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to cancel"),
),
)
.subcommand(
SubCommand::with_name("confirm")
.about("Confirm transaction by signature")
.arg(
Arg::with_name("signature")
.index(1)
.value_name("SIGNATURE")
.takes_value(true)
.required(true)
.help("The transaction signature to confirm"),
),
)
.subcommand(
SubCommand::with_name("authorize-voter")
.about("Configure staking account for node")
.arg(
Arg::with_name("authorized_voter_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Address to delegate this vote account to"),
),
)
.subcommand(
SubCommand::with_name("create-vote-account")
.about("Create staking account for node")
.arg(
Arg::with_name("voting_account_id")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("node_id")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Node that will vote in this account"),
)
.arg(
Arg::with_name("lamports")
.index(3)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
)
.arg(
Arg::with_name("commission")
.long("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission taken on reward redemption"),
),
)
.subcommand(
SubCommand::with_name("deploy")
.about("Deploy a program")
.arg(
Arg::with_name("program_location")
.index(1)
.value_name("PATH")
.takes_value(true)
.required(true)
.help("/path/to/program.o"),
), // TODO: Add "loader" argument; current default is bpf_loader
)
.subcommand(
SubCommand::with_name("get-transaction-count")
.about("Get current transaction count"),
)
.subcommand(
SubCommand::with_name("pay")
.about("Send a payment")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("lamports")
.index(2)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send"),
)
.arg(
Arg::with_name("timestamp")
.long("after")
.value_name("DATETIME")
.takes_value(true)
.help("A timestamp after which transaction will execute"),
)
.arg(
Arg::with_name("timestamp_pubkey")
.long("require-timestamp-from")
.value_name("PUBKEY")
.takes_value(true)
.requires("timestamp")
.help("Require timestamp from this third party"),
)
.arg(
Arg::with_name("witness")
.long("require-signature-from")
.value_name("PUBKEY")
.takes_value(true)
.multiple(true)
.use_delimiter(true)
.help("Any third party signatures required to unlock the lamports"),
)
.arg(
Arg::with_name("cancelable")
.long("cancelable")
.takes_value(false),
),
)
.subcommand(
SubCommand::with_name("send-signature")
.about("Send a signature to authorize a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to authorize"),
),
)
.subcommand(
SubCommand::with_name("send-timestamp")
.about("Send a timestamp to unlock a transfer")
.arg(
Arg::with_name("to")
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
)
.arg(
Arg::with_name("process_id")
.index(2)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to unlock"),
)
.arg(
Arg::with_name("datetime")
.long("date")
.value_name("DATETIME")
.takes_value(true)
.help("Optional arbitrary timestamp to apply"),
),
);
let test_commands = app("test", "desc", "version");
let pubkey = Pubkey::new_rand();
let pubkey_string = format!("{}", pubkey);
let witness0 = Pubkey::new_rand();
@ -1138,6 +1356,47 @@ mod tests {
WalletCommand::CreateVoteAccount(pubkey, node_id, 0, 50)
);
// Test Create Stake Account
let test_create_stake_account = test_commands.clone().get_matches_from(vec![
"test",
"create-stake-account",
&pubkey_string,
"50",
]);
assert_eq!(
parse_command(&pubkey, &test_create_stake_account).unwrap(),
WalletCommand::CreateStakeAccount(pubkey, 50)
);
fn make_tmp_path(name: &str) -> String {
let out_dir = std::env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let keypair = Keypair::new();
let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
// whack any possible collision
let _ignored = std::fs::remove_dir_all(&path);
// whack any possible collision
let _ignored = std::fs::remove_file(&path);
path
}
let keypair_file = make_tmp_path("keypair_file");
gen_keypair_file(&keypair_file).unwrap();
let keypair = read_keypair(&keypair_file).unwrap();
// Test Delegate Stake Subcommand
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
&keypair_file,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(keypair, pubkey)
);
// Test Deploy Subcommand
let test_deploy =
test_commands
@ -1287,12 +1546,22 @@ mod tests {
assert_eq!(process_command(&config).unwrap(), "Confirmed");
let bob_pubkey = Pubkey::new_rand();
let node_id = Pubkey::new_rand();
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::CreateStakeAccount(bob_pubkey, 10);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let bob_keypair = Keypair::new();
let node_id = Pubkey::new_rand();
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_id);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -1397,10 +1666,10 @@ mod tests {
config.command = WalletCommand::Balance(config.keypair.pubkey());
assert!(process_command(&config).is_err());
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
assert!(process_command(&config).is_err());
config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10);
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
assert!(process_command(&config).is_err());
config.command = WalletCommand::GetTransactionCount;