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:
parent
81fa69d347
commit
ba8f49366d
|
@ -2779,6 +2779,7 @@ dependencies = [
|
||||||
"solana-logger 0.15.0",
|
"solana-logger 0.15.0",
|
||||||
"solana-netutil 0.15.0",
|
"solana-netutil 0.15.0",
|
||||||
"solana-sdk 0.15.0",
|
"solana-sdk 0.15.0",
|
||||||
|
"solana-stake-api 0.15.0",
|
||||||
"solana-vote-api 0.15.0",
|
"solana-vote-api 0.15.0",
|
||||||
"solana-vote-signer 0.15.0",
|
"solana-vote-signer 0.15.0",
|
||||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -72,32 +72,51 @@ rsync_url() { # adds the 'rsync://` prefix to URLs that need it
|
||||||
echo "rsync://$url"
|
echo "rsync://$url"
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_vote_account() {
|
setup_vote_and_stake_accounts() {
|
||||||
declare entrypoint_ip=$1
|
declare entrypoint_ip=$1
|
||||||
declare node_keypair_path=$2
|
declare node_keypair_path=$2
|
||||||
declare vote_keypair_path=$3
|
declare vote_keypair_path=$3
|
||||||
declare stake=$4
|
declare stake_keypair_path=$4
|
||||||
|
declare stake=$5
|
||||||
|
|
||||||
declare node_keypair
|
declare node_pubkey
|
||||||
node_keypair=$($solana_wallet --keypair "$node_keypair_path" address)
|
node_pubkey=$($solana_keygen pubkey "$node_keypair_path")
|
||||||
|
|
||||||
declare vote_keypair
|
declare vote_pubkey
|
||||||
vote_keypair=$($solana_wallet --keypair "$vote_keypair_path" address)
|
vote_pubkey=$($solana_keygen pubkey "$vote_keypair_path")
|
||||||
|
|
||||||
if [[ -f "$vote_keypair_path".configured ]]; then
|
declare stake_pubkey
|
||||||
echo "Vote account has already been configured"
|
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
|
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" \
|
$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
|
fi
|
||||||
|
|
||||||
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \
|
$solana_wallet --url "http://$entrypoint_ip:8899" \
|
||||||
show-vote-account "$vote_keypair"
|
show-vote-account "$vote_pubkey"
|
||||||
|
|
||||||
|
$solana_wallet --url "http://$entrypoint_ip:8899" \
|
||||||
|
show-stake-account "$stake_pubkey"
|
||||||
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,8 +242,8 @@ elif [[ $node_type = replicator ]]; then
|
||||||
[[ -r "$replicator_keypair_path" ]] || $solana_keygen -o "$replicator_keypair_path"
|
[[ -r "$replicator_keypair_path" ]] || $solana_keygen -o "$replicator_keypair_path"
|
||||||
[[ -r "$replicator_storage_keypair_path" ]] || $solana_keygen -o "$replicator_storage_keypair_path"
|
[[ -r "$replicator_storage_keypair_path" ]] || $solana_keygen -o "$replicator_storage_keypair_path"
|
||||||
|
|
||||||
replicator_keypair=$($solana_keygen pubkey "$replicator_keypair_path")
|
replicator_pubkey=$($solana_keygen pubkey "$replicator_keypair_path")
|
||||||
replicator_storage_keypair=$($solana_keygen pubkey "$replicator_storage_keypair_path")
|
replicator_storage_pubkey=$($solana_keygen pubkey "$replicator_storage_keypair_path")
|
||||||
|
|
||||||
default_arg --entrypoint "$entrypoint_address"
|
default_arg --entrypoint "$entrypoint_address"
|
||||||
default_arg --identity "$replicator_keypair_path"
|
default_arg --identity "$replicator_keypair_path"
|
||||||
|
@ -241,12 +260,14 @@ else
|
||||||
|
|
||||||
: "${fullnode_keypair_path:=$SOLANA_CONFIG_DIR/fullnode-keypair$label.json}"
|
: "${fullnode_keypair_path:=$SOLANA_CONFIG_DIR/fullnode-keypair$label.json}"
|
||||||
fullnode_vote_keypair_path=$SOLANA_CONFIG_DIR/fullnode-vote-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
|
ledger_config_dir=$SOLANA_CONFIG_DIR/fullnode-ledger$label
|
||||||
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts$label
|
accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts$label
|
||||||
|
|
||||||
mkdir -p "$SOLANA_CONFIG_DIR"
|
mkdir -p "$SOLANA_CONFIG_DIR"
|
||||||
[[ -r "$fullnode_keypair_path" ]] || $solana_keygen -o "$fullnode_keypair_path"
|
[[ -r "$fullnode_keypair_path" ]] || $solana_keygen -o "$fullnode_keypair_path"
|
||||||
[[ -r "$fullnode_vote_keypair_path" ]] || $solana_keygen -o "$fullnode_vote_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 --entrypoint "$entrypoint_address"
|
||||||
default_arg --rpc-drone-address "${entrypoint_address%:*}:9900"
|
default_arg --rpc-drone-address "${entrypoint_address%:*}:9900"
|
||||||
|
@ -258,8 +279,8 @@ fi
|
||||||
if [[ $node_type = replicator ]]; then
|
if [[ $node_type = replicator ]]; then
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
======================[ Replicator configuration ]======================
|
======================[ Replicator configuration ]======================
|
||||||
replicator pubkey: $replicator_keypair
|
replicator pubkey: $replicator_pubkey
|
||||||
storage pubkey: $replicator_storage_keypair
|
storage pubkey: $replicator_storage_pubkey
|
||||||
ledger: $ledger_config_dir
|
ledger: $ledger_config_dir
|
||||||
======================================================================
|
======================================================================
|
||||||
EOF
|
EOF
|
||||||
|
@ -267,13 +288,13 @@ EOF
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
fullnode_keypair=$($solana_keygen pubkey "$fullnode_keypair_path")
|
fullnode_pubkey=$($solana_keygen pubkey "$fullnode_keypair_path")
|
||||||
fullnode_vote_keypair=$($solana_keygen pubkey "$fullnode_vote_keypair_path")
|
fullnode_vote_pubkey=$($solana_keygen pubkey "$fullnode_vote_keypair_path")
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
======================[ Fullnode configuration ]======================
|
======================[ Fullnode configuration ]======================
|
||||||
node pubkey: $fullnode_keypair
|
node pubkey: $fullnode_pubkey
|
||||||
vote pubkey: $fullnode_vote_keypair
|
vote pubkey: $fullnode_vote_pubkey
|
||||||
ledger: $ledger_config_dir
|
ledger: $ledger_config_dir
|
||||||
accounts: $accounts_config_dir
|
accounts: $accounts_config_dir
|
||||||
======================================================================
|
======================================================================
|
||||||
|
@ -281,7 +302,7 @@ EOF
|
||||||
|
|
||||||
default_arg --identity "$fullnode_keypair_path"
|
default_arg --identity "$fullnode_keypair_path"
|
||||||
default_arg --voting-keypair "$fullnode_vote_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 --ledger "$ledger_config_dir"
|
||||||
default_arg --accounts "$accounts_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
|
trap '[[ -n $pid ]] && kill "$pid" >/dev/null 2>&1 && wait "$pid"' INT TERM ERR
|
||||||
|
|
||||||
if [[ $node_type = validator ]] && ((stake)); then
|
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
|
elif [[ $node_type = replicator ]] && ((stake)); then
|
||||||
setup_replicator_account "${entrypoint_address%:*}" "$replicator_keypair_path" "$stake"
|
setup_replicator_account "${entrypoint_address%:*}" "$replicator_keypair_path" "$stake"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -23,6 +23,7 @@ solana-drone = { path = "../drone", version = "0.15.0" }
|
||||||
solana-logger = { path = "../logger", version = "0.15.0" }
|
solana-logger = { path = "../logger", version = "0.15.0" }
|
||||||
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
||||||
solana-sdk = { path = "../sdk", 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-api = { path = "../programs/vote_api", version = "0.15.0" }
|
||||||
solana-vote-signer = { path = "../vote-signer", version = "0.15.0" }
|
solana-vote-signer = { path = "../vote-signer", version = "0.15.0" }
|
||||||
url = "1.7.2"
|
url = "1.7.2"
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use clap::{
|
use clap::{crate_description, crate_name, crate_version, Arg, ArgMatches};
|
||||||
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
|
|
||||||
};
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
|
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;
|
use std::error;
|
||||||
|
|
||||||
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn error::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>> {
|
fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
solana_logger::setup();
|
solana_logger::setup();
|
||||||
|
|
||||||
let default = WalletConfig::default();
|
let default = WalletConfig::default();
|
||||||
let default_drone_port = format!("{}", default.drone_port);
|
let default_drone_port = format!("{}", default.drone_port);
|
||||||
|
|
||||||
let matches = App::new(crate_name!())
|
let matches = app(crate_name!(), crate_description!(), crate_version!())
|
||||||
.about(crate_description!())
|
|
||||||
.version(crate_version!())
|
|
||||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("json_rpc_url")
|
Arg::with_name("json_rpc_url")
|
||||||
.short("u")
|
.short("u")
|
||||||
|
@ -127,235 +113,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("/path/to/id.json"),
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
let config = parse_args(&matches)?;
|
let config = parse_args(&matches)?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use bs58;
|
use bs58;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use clap::ArgMatches;
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
use log::*;
|
use log::*;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -18,14 +18,15 @@ use solana_drone::drone_mock::request_airdrop_transaction;
|
||||||
use solana_sdk::bpf_loader;
|
use solana_sdk::bpf_loader;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::instruction::InstructionError;
|
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::loader_instruction;
|
||||||
use solana_sdk::message::Message;
|
use solana_sdk::message::Message;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
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_instruction::SystemError;
|
||||||
use solana_sdk::system_transaction;
|
use solana_sdk::system_transaction;
|
||||||
use solana_sdk::transaction::{Transaction, TransactionError};
|
use solana_sdk::transaction::{Transaction, TransactionError};
|
||||||
|
use solana_stake_api::stake_instruction;
|
||||||
use solana_vote_api::vote_instruction;
|
use solana_vote_api::vote_instruction;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
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
|
const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum WalletCommand {
|
pub enum WalletCommand {
|
||||||
Address,
|
Address,
|
||||||
Airdrop(u64),
|
Airdrop(u64),
|
||||||
Balance(Pubkey),
|
Balance(Pubkey),
|
||||||
Cancel(Pubkey),
|
Cancel(Pubkey),
|
||||||
Confirm(Signature),
|
Confirm(Signature),
|
||||||
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
|
|
||||||
AuthorizeVoter(Pubkey),
|
AuthorizeVoter(Pubkey),
|
||||||
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
|
CreateVoteAccount(Pubkey, Pubkey, u32, u64),
|
||||||
ShowVoteAccount(Pubkey),
|
ShowVoteAccount(Pubkey),
|
||||||
|
CreateStakeAccount(Pubkey, u64),
|
||||||
|
DelegateStake(Keypair, Pubkey),
|
||||||
|
ShowStakeAccount(Pubkey),
|
||||||
Deploy(String),
|
Deploy(String),
|
||||||
GetTransactionCount,
|
GetTransactionCount,
|
||||||
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
|
// 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())
|
.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(
|
pub fn parse_command(
|
||||||
pubkey: &Pubkey,
|
pubkey: &Pubkey,
|
||||||
matches: &ArgMatches<'_>,
|
matches: &ArgMatches<'_>,
|
||||||
|
@ -172,10 +181,6 @@ pub fn parse_command(
|
||||||
Err(WalletError::BadParameter("Invalid signature".to_string()))
|
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)) => {
|
("create-vote-account", Some(matches)) => {
|
||||||
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
|
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
|
||||||
let node_id = pubkey_of(matches, "node_id").unwrap();
|
let node_id = pubkey_of(matches, "node_id").unwrap();
|
||||||
|
@ -192,10 +197,35 @@ pub fn parse_command(
|
||||||
lamports,
|
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)) => {
|
("show-vote-account", Some(matches)) => {
|
||||||
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
|
let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap();
|
||||||
Ok(WalletCommand::ShowVoteAccount(voting_account_id))
|
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", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
|
||||||
deploy_matches
|
deploy_matches
|
||||||
.value_of("program_location")
|
.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()) {
|
match rpc_client.get_signature_status(&signature.to_string()) {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
if let Some(result) = status {
|
if let Some(result) = status {
|
||||||
|
@ -343,23 +373,7 @@ fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_authorize_voter(
|
fn process_create_vote_account(
|
||||||
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(
|
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
voting_account_id: &Pubkey,
|
voting_account_id: &Pubkey,
|
||||||
|
@ -367,7 +381,6 @@ fn process_create_staking(
|
||||||
commission: u32,
|
commission: u32,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
|
||||||
let ixs = vote_instruction::create_account(
|
let ixs = vote_instruction::create_account(
|
||||||
&config.keypair.pubkey(),
|
&config.keypair.pubkey(),
|
||||||
voting_account_id,
|
voting_account_id,
|
||||||
|
@ -375,12 +388,29 @@ fn process_create_staking(
|
||||||
commission,
|
commission,
|
||||||
lamports,
|
lamports,
|
||||||
);
|
);
|
||||||
|
let recent_blockhash = rpc_client.get_recent_blockhash()?;
|
||||||
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, 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])?;
|
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?;
|
||||||
Ok(signature_str.to_string())
|
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,
|
rpc_client: &RpcClient,
|
||||||
_config: &WalletConfig,
|
_config: &WalletConfig,
|
||||||
voting_account_id: &Pubkey,
|
voting_account_id: &Pubkey,
|
||||||
|
@ -388,7 +418,11 @@ fn process_show_staking(
|
||||||
use solana_vote_api::vote_state::VoteState;
|
use solana_vote_api::vote_state::VoteState;
|
||||||
let vote_account_lamports = rpc_client.retry_get_balance(voting_account_id, 5)?;
|
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_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!("account lamports: {}", vote_account_lamports.unwrap());
|
||||||
println!("node id: {}", vote_state.node_id);
|
println!("node id: {}", vote_state.node_id);
|
||||||
|
@ -417,6 +451,68 @@ fn process_show_staking(
|
||||||
Ok("".to_string())
|
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(
|
fn process_deploy(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
|
@ -656,13 +752,13 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
config.rpc_client.as_ref().unwrap()
|
config.rpc_client.as_ref().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
match config.command {
|
match &config.command {
|
||||||
// Get address of this client
|
// Get address of this client
|
||||||
WalletCommand::Address => unreachable!(),
|
WalletCommand::Address => unreachable!(),
|
||||||
|
|
||||||
// Request an airdrop from Solana Drone;
|
// Request an airdrop from Solana Drone;
|
||||||
WalletCommand::Airdrop(lamports) => {
|
WalletCommand::Airdrop(lamports) => {
|
||||||
process_airdrop(&rpc_client, config, drone_addr, lamports)
|
process_airdrop(&rpc_client, config, drone_addr, *lamports)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check client balance
|
// Check client balance
|
||||||
|
@ -674,25 +770,43 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
// Confirm the last client transaction by signature
|
// Confirm the last client transaction by signature
|
||||||
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
||||||
|
|
||||||
// Configure staking account already created
|
// Create vote account
|
||||||
WalletCommand::AuthorizeVoter(authorized_voter_id) => {
|
|
||||||
process_authorize_voter(&rpc_client, config, authorized_voter_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create staking account
|
|
||||||
WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => {
|
WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => {
|
||||||
process_create_staking(
|
process_create_vote_account(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
&voting_account_id,
|
&voting_account_id,
|
||||||
&node_id,
|
&node_id,
|
||||||
commission,
|
*commission,
|
||||||
lamports,
|
*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) => {
|
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
|
// Deploy a custom program to the chain
|
||||||
|
@ -713,17 +827,17 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
||||||
) => process_pay(
|
) => process_pay(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
lamports,
|
*lamports,
|
||||||
&to,
|
&to,
|
||||||
timestamp,
|
*timestamp,
|
||||||
timestamp_pubkey,
|
*timestamp_pubkey,
|
||||||
witnesses,
|
witnesses,
|
||||||
cancelable,
|
*cancelable,
|
||||||
),
|
),
|
||||||
|
|
||||||
// Apply time elapsed to contract
|
// Apply time elapsed to contract
|
||||||
WalletCommand::TimeElapsed(to, pubkey, dt) => {
|
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
|
// Apply witness signature to contract
|
||||||
|
@ -823,39 +937,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
// Return an error if a pubkey cannot be parsed.
|
||||||
mod tests {
|
fn is_pubkey(string: String) -> Result<(), String> {
|
||||||
use super::*;
|
match string.parse::<Pubkey>() {
|
||||||
use clap::{App, Arg, SubCommand};
|
Ok(_) => Ok(()),
|
||||||
use serde_json::Value;
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
use solana_client::mock_rpc_client_request::SIGNATURE;
|
|
||||||
use solana_sdk::transaction::TransactionError;
|
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wallet_config_drone_addr() {
|
|
||||||
let mut config = WalletConfig::default();
|
|
||||||
config.json_rpc_url = "http://127.0.0.1:8899".to_string();
|
|
||||||
let rpc_host = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
|
||||||
assert_eq!(
|
|
||||||
config.drone_addr(),
|
|
||||||
SocketAddr::new(rpc_host, config.drone_port)
|
|
||||||
);
|
|
||||||
|
|
||||||
config.drone_port = 1234;
|
|
||||||
assert_eq!(config.drone_addr(), SocketAddr::new(rpc_host, 1234));
|
|
||||||
|
|
||||||
config.drone_host = Some(rpc_host);
|
|
||||||
assert_eq!(
|
|
||||||
config.drone_addr(),
|
|
||||||
SocketAddr::new(config.drone_host.unwrap(), 1234)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> {
|
||||||
fn test_wallet_parse_command() {
|
App::new(name)
|
||||||
let test_commands = App::new("test")
|
.about(about)
|
||||||
|
.version(version)
|
||||||
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||||
.subcommand(SubCommand::with_name("address").about("Get your public key"))
|
.subcommand(SubCommand::with_name("address").about("Get your public key"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("airdrop")
|
SubCommand::with_name("airdrop")
|
||||||
|
@ -869,7 +963,18 @@ mod tests {
|
||||||
.help("The number of lamports to request"),
|
.help("The number of lamports to request"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(SubCommand::with_name("balance").about("Get your balance"))
|
.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(
|
||||||
SubCommand::with_name("cancel")
|
SubCommand::with_name("cancel")
|
||||||
.about("Cancel a transfer")
|
.about("Cancel a transfer")
|
||||||
|
@ -879,6 +984,7 @@ mod tests {
|
||||||
.value_name("PROCESS_ID")
|
.value_name("PROCESS_ID")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("The process id of the transfer to cancel"),
|
.help("The process id of the transfer to cancel"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -896,26 +1002,28 @@ mod tests {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("authorize-voter")
|
SubCommand::with_name("authorize-voter")
|
||||||
.about("Configure staking account for node")
|
.about("Authorize a different voter for this vote account")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("authorized_voter_id")
|
Arg::with_name("authorized_voter_id")
|
||||||
.index(1)
|
.index(1)
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Address to delegate this vote account to"),
|
.validator(is_pubkey)
|
||||||
|
.help("Vote signer to authorize"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("create-vote-account")
|
SubCommand::with_name("create-vote-account")
|
||||||
.about("Create staking account for node")
|
.about("Create vote account for a node")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("voting_account_id")
|
Arg::with_name("voting_account_id")
|
||||||
.index(1)
|
.index(1)
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Staking account address to fund"),
|
.validator(is_pubkey)
|
||||||
|
.help("Vote account address to fund"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("node_id")
|
Arg::with_name("node_id")
|
||||||
|
@ -923,6 +1031,7 @@ mod tests {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("Node that will vote in this account"),
|
.help("Node that will vote in this account"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -931,16 +1040,84 @@ mod tests {
|
||||||
.value_name("NUM")
|
.value_name("NUM")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("The number of lamports to send to staking account"),
|
.help("The number of lamports to send to the vote account"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("commission")
|
Arg::with_name("commission")
|
||||||
.long("commission")
|
.long("commission")
|
||||||
.value_name("NUM")
|
.value_name("NUM")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("The commission taken on reward redemption"),
|
.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(
|
||||||
SubCommand::with_name("deploy")
|
SubCommand::with_name("deploy")
|
||||||
.about("Deploy a program")
|
.about("Deploy a program")
|
||||||
|
@ -966,6 +1143,7 @@ mod tests {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("The pubkey of recipient"),
|
.help("The pubkey of recipient"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -989,6 +1167,7 @@ mod tests {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.requires("timestamp")
|
.requires("timestamp")
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("Require timestamp from this third party"),
|
.help("Require timestamp from this third party"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -998,6 +1177,7 @@ mod tests {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.use_delimiter(true)
|
.use_delimiter(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("Any third party signatures required to unlock the lamports"),
|
.help("Any third party signatures required to unlock the lamports"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -1015,6 +1195,7 @@ mod tests {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("The pubkey of recipient"),
|
.help("The pubkey of recipient"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -1035,6 +1216,7 @@ mod tests {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.validator(is_pubkey)
|
||||||
.help("The pubkey of recipient"),
|
.help("The pubkey of recipient"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -1052,7 +1234,43 @@ mod tests {
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Optional arbitrary timestamp to apply"),
|
.help("Optional arbitrary timestamp to apply"),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_config_drone_addr() {
|
||||||
|
let mut config = WalletConfig::default();
|
||||||
|
config.json_rpc_url = "http://127.0.0.1:8899".to_string();
|
||||||
|
let rpc_host = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
|
assert_eq!(
|
||||||
|
config.drone_addr(),
|
||||||
|
SocketAddr::new(rpc_host, config.drone_port)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
config.drone_port = 1234;
|
||||||
|
assert_eq!(config.drone_addr(), SocketAddr::new(rpc_host, 1234));
|
||||||
|
|
||||||
|
config.drone_host = Some(rpc_host);
|
||||||
|
assert_eq!(
|
||||||
|
config.drone_addr(),
|
||||||
|
SocketAddr::new(config.drone_host.unwrap(), 1234)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_parse_command() {
|
||||||
|
let test_commands = app("test", "desc", "version");
|
||||||
|
|
||||||
let pubkey = Pubkey::new_rand();
|
let pubkey = Pubkey::new_rand();
|
||||||
let pubkey_string = format!("{}", pubkey);
|
let pubkey_string = format!("{}", pubkey);
|
||||||
let witness0 = Pubkey::new_rand();
|
let witness0 = Pubkey::new_rand();
|
||||||
|
@ -1138,6 +1356,47 @@ mod tests {
|
||||||
WalletCommand::CreateVoteAccount(pubkey, node_id, 0, 50)
|
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
|
// Test Deploy Subcommand
|
||||||
let test_deploy =
|
let test_deploy =
|
||||||
test_commands
|
test_commands
|
||||||
|
@ -1287,12 +1546,22 @@ mod tests {
|
||||||
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
||||||
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
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);
|
config.command = WalletCommand::AuthorizeVoter(bob_pubkey);
|
||||||
let signature = process_command(&config);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
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();
|
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);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||||
|
|
||||||
|
@ -1397,10 +1666,10 @@ mod tests {
|
||||||
config.command = WalletCommand::Balance(config.keypair.pubkey());
|
config.command = WalletCommand::Balance(config.keypair.pubkey());
|
||||||
assert!(process_command(&config).is_err());
|
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());
|
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());
|
assert!(process_command(&config).is_err());
|
||||||
|
|
||||||
config.command = WalletCommand::GetTransactionCount;
|
config.command = WalletCommand::GetTransactionCount;
|
||||||
|
|
Loading…
Reference in New Issue