Compare commits
7 Commits
document-r
...
v0.21.0
Author | SHA1 | Date | |
---|---|---|---|
de6cf6b7e3 | |||
32cf04c77d | |||
96df4c772f | |||
640c2f88bd | |||
82f78a5610 | |||
cf8f8afbc6 | |||
e6bc92f6c9 |
@ -18,6 +18,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
|||||||
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
|
||||||
* [getBalance](jsonrpc-api.md#getbalance)
|
* [getBalance](jsonrpc-api.md#getbalance)
|
||||||
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
|
||||||
|
* [getBlockTime](jsonrpc-api.md#getblocktime)
|
||||||
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
|
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
|
||||||
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
|
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
|
||||||
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
|
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
|
||||||
@ -209,6 +210,31 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
|||||||
{"jsonrpc":"2.0","result":[{"commitment":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,32]},42],"id":1}
|
{"jsonrpc":"2.0","result":[{"commitment":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,32]},42],"id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### getBlockTime
|
||||||
|
|
||||||
|
Returns the estimated production time of a block. Validators report their UTC
|
||||||
|
time to the ledger on a regular interval. A block's time is calculated as an
|
||||||
|
offset from the median value of the most recent validator time report.
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
* `u64` - block, identified by Slot
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
* `null` - block has not yet been produced
|
||||||
|
* `i64` - estimated production time, as Unix timestamp (seconds since the Unix epoch)
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockTime","params":[5]}' http://localhost:8899
|
||||||
|
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":1574721591,"id":1}
|
||||||
|
```
|
||||||
|
|
||||||
### getClusterNodes
|
### getClusterNodes
|
||||||
|
|
||||||
Returns information about all the nodes participating in the cluster
|
Returns information about all the nodes participating in the cluster
|
||||||
|
@ -246,7 +246,7 @@ sanity() {
|
|||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
NO_INSTALL_CHECK=1 \
|
NO_INSTALL_CHECK=1 \
|
||||||
ci/testnet-sanity.sh beta-testnet-solana-com gce us-west1-b
|
ci/testnet-sanity.sh beta-testnet-solana-com gce -P us-west1-b
|
||||||
maybe_deploy_software --deploy-if-newer
|
maybe_deploy_software --deploy-if-newer
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
@ -260,7 +260,7 @@ sanity() {
|
|||||||
testnet)
|
testnet)
|
||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
ci/testnet-sanity.sh testnet-solana-com gce us-west1-b
|
ci/testnet-sanity.sh testnet-solana-com gce -P us-west1-b
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
testnet-perf)
|
testnet-perf)
|
||||||
|
@ -3,8 +3,9 @@ use clap::ArgMatches;
|
|||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
native_token::sol_to_lamports,
|
native_token::sol_to_lamports,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{read_keypair_file, Keypair, KeypairUtil},
|
signature::{read_keypair_file, Keypair, KeypairUtil, Signature},
|
||||||
};
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
// Return parsed values from matches at `name`
|
// Return parsed values from matches at `name`
|
||||||
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
|
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
|
||||||
@ -50,6 +51,20 @@ pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
|
|||||||
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
|
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return pubkey/signature pairs for a string of the form pubkey=signature
|
||||||
|
pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubkey, Signature)>> {
|
||||||
|
matches.values_of(name).map(|values| {
|
||||||
|
values
|
||||||
|
.map(|pubkey_signer_string| {
|
||||||
|
let mut signer = pubkey_signer_string.split('=');
|
||||||
|
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||||
|
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||||
|
(key, sig)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64> {
|
pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64> {
|
||||||
if matches.value_of(unit) == Some("lamports") {
|
if matches.value_of(unit) == Some("lamports") {
|
||||||
value_of(matches, name)
|
value_of(matches, name)
|
||||||
@ -172,4 +187,25 @@ mod tests {
|
|||||||
|
|
||||||
fs::remove_file(&outfile).unwrap();
|
fs::remove_file(&outfile).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pubkeys_sigs_of() {
|
||||||
|
let key1 = Pubkey::new_rand();
|
||||||
|
let key2 = Pubkey::new_rand();
|
||||||
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let sig2 = Keypair::new().sign_message(&[1u8]);
|
||||||
|
let signer1 = format!("{}={}", key1, sig1);
|
||||||
|
let signer2 = format!("{}={}", key2, sig2);
|
||||||
|
let matches = app().clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"--multiple",
|
||||||
|
&signer1,
|
||||||
|
"--multiple",
|
||||||
|
&signer2,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
pubkeys_sigs_of(&matches, "multiple"),
|
||||||
|
Some(vec![(key1, sig1), (key2, sig2)])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::keypair::ASK_KEYWORD;
|
use crate::keypair::ASK_KEYWORD;
|
||||||
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::read_keypair_file;
|
use solana_sdk::signature::{read_keypair_file, Signature};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
// Return an error if a pubkey cannot be parsed.
|
// Return an error if a pubkey cannot be parsed.
|
||||||
pub fn is_pubkey(string: String) -> Result<(), String> {
|
pub fn is_pubkey(string: String) -> Result<(), String> {
|
||||||
@ -10,6 +12,14 @@ pub fn is_pubkey(string: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return an error if a hash cannot be parsed.
|
||||||
|
pub fn is_hash(string: String) -> Result<(), String> {
|
||||||
|
match string.parse::<Hash>() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return an error if a keypair file cannot be parsed.
|
// Return an error if a keypair file cannot be parsed.
|
||||||
pub fn is_keypair(string: String) -> Result<(), String> {
|
pub fn is_keypair(string: String) -> Result<(), String> {
|
||||||
read_keypair_file(&string)
|
read_keypair_file(&string)
|
||||||
@ -32,6 +42,28 @@ pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
|
|||||||
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
|
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return an error if string cannot be parsed as pubkey=signature string
|
||||||
|
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
|
||||||
|
let mut signer = string.split('=');
|
||||||
|
match Pubkey::from_str(
|
||||||
|
signer
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "Malformed signer string".to_string())?,
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
match Signature::from_str(
|
||||||
|
signer
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "Malformed signer string".to_string())?,
|
||||||
|
) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(format!("{:?}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return an error if a url cannot be parsed.
|
// Return an error if a url cannot be parsed.
|
||||||
pub fn is_url(string: String) -> Result<(), String> {
|
pub fn is_url(string: String) -> Result<(), String> {
|
||||||
match url::Url::parse(&string) {
|
match url::Url::parse(&string) {
|
||||||
|
344
cli/src/cli.rs
344
cli/src/cli.rs
@ -1,5 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
cluster_query::*, display::println_name_value, stake::*, storage::*, validator_info::*, vote::*,
|
cluster_query::*,
|
||||||
|
display::{println_name_value, println_signers},
|
||||||
|
stake::*,
|
||||||
|
storage::*,
|
||||||
|
validator_info::*,
|
||||||
|
vote::*,
|
||||||
};
|
};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
@ -15,6 +20,7 @@ use solana_drone::drone::request_airdrop_transaction;
|
|||||||
use solana_drone::drone_mock::request_airdrop_transaction;
|
use solana_drone::drone_mock::request_airdrop_transaction;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
|
clock::Slot,
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
@ -74,6 +80,9 @@ pub enum CliCommand {
|
|||||||
},
|
},
|
||||||
ClusterVersion,
|
ClusterVersion,
|
||||||
Fees,
|
Fees,
|
||||||
|
GetBlockTime {
|
||||||
|
slot: Slot,
|
||||||
|
},
|
||||||
GetEpochInfo {
|
GetEpochInfo {
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
},
|
},
|
||||||
@ -105,8 +114,20 @@ pub enum CliCommand {
|
|||||||
lockup: Lockup,
|
lockup: Lockup,
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
},
|
},
|
||||||
DeactivateStake(Pubkey),
|
DeactivateStake {
|
||||||
DelegateStake(Pubkey, Pubkey, bool),
|
stake_account_pubkey: Pubkey,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
|
},
|
||||||
|
DelegateStake {
|
||||||
|
stake_account_pubkey: Pubkey,
|
||||||
|
vote_account_pubkey: Pubkey,
|
||||||
|
force: bool,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
|
},
|
||||||
RedeemVoteCredits(Pubkey, Pubkey),
|
RedeemVoteCredits(Pubkey, Pubkey),
|
||||||
ShowStakeHistory {
|
ShowStakeHistory {
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
@ -174,6 +195,9 @@ pub enum CliCommand {
|
|||||||
timestamp_pubkey: Option<Pubkey>,
|
timestamp_pubkey: Option<Pubkey>,
|
||||||
witnesses: Option<Vec<Pubkey>>,
|
witnesses: Option<Vec<Pubkey>>,
|
||||||
cancelable: bool,
|
cancelable: bool,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
},
|
},
|
||||||
ShowAccount {
|
ShowAccount {
|
||||||
pubkey: Pubkey,
|
pubkey: Pubkey,
|
||||||
@ -266,6 +290,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
|
|||||||
command: CliCommand::Fees,
|
command: CliCommand::Fees,
|
||||||
require_keypair: false,
|
require_keypair: false,
|
||||||
}),
|
}),
|
||||||
|
("get-block-time", Some(matches)) => parse_get_block_time(matches),
|
||||||
("get-epoch-info", Some(matches)) => parse_get_epoch_info(matches),
|
("get-epoch-info", Some(matches)) => parse_get_epoch_info(matches),
|
||||||
("get-genesis-hash", Some(_matches)) => Ok(CliCommandInfo {
|
("get-genesis-hash", Some(_matches)) => Ok(CliCommandInfo {
|
||||||
command: CliCommand::GetGenesisHash,
|
command: CliCommand::GetGenesisHash,
|
||||||
@ -413,6 +438,9 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
|
|||||||
let timestamp_pubkey = value_of(&matches, "timestamp_pubkey");
|
let timestamp_pubkey = value_of(&matches, "timestamp_pubkey");
|
||||||
let witnesses = values_of(&matches, "witness");
|
let witnesses = values_of(&matches, "witness");
|
||||||
let cancelable = matches.is_present("cancelable");
|
let cancelable = matches.is_present("cancelable");
|
||||||
|
let sign_only = matches.is_present("sign_only");
|
||||||
|
let signers = pubkeys_sigs_of(&matches, "signer");
|
||||||
|
let blockhash = value_of(&matches, "blockhash");
|
||||||
|
|
||||||
Ok(CliCommandInfo {
|
Ok(CliCommandInfo {
|
||||||
command: CliCommand::Pay {
|
command: CliCommand::Pay {
|
||||||
@ -422,8 +450,11 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
|
|||||||
timestamp_pubkey,
|
timestamp_pubkey,
|
||||||
witnesses,
|
witnesses,
|
||||||
cancelable,
|
cancelable,
|
||||||
|
sign_only,
|
||||||
|
signers,
|
||||||
|
blockhash,
|
||||||
},
|
},
|
||||||
require_keypair: true,
|
require_keypair: !sign_only,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
("show-account", Some(matches)) => {
|
("show-account", Some(matches)) => {
|
||||||
@ -522,6 +553,48 @@ pub fn check_unique_pubkeys(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_blockhash_fee_calculator(
|
||||||
|
rpc_client: &RpcClient,
|
||||||
|
sign_only: bool,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
|
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
|
||||||
|
Ok(if let Some(blockhash) = blockhash {
|
||||||
|
if sign_only {
|
||||||
|
(blockhash, FeeCalculator::default())
|
||||||
|
} else {
|
||||||
|
(blockhash, rpc_client.get_recent_blockhash()?.1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rpc_client.get_recent_blockhash()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn return_signers(tx: &Transaction) -> ProcessResult {
|
||||||
|
println_signers(tx);
|
||||||
|
let signers: Vec<_> = tx
|
||||||
|
.signatures
|
||||||
|
.iter()
|
||||||
|
.zip(tx.message.account_keys.clone())
|
||||||
|
.map(|(signature, pubkey)| format!("{}={}", pubkey, signature))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"blockhash": tx.message.recent_blockhash.to_string(),
|
||||||
|
"signers": &signers,
|
||||||
|
})
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_signatures(tx: &mut Transaction, signers: &[(Pubkey, Signature)]) -> ProcessResult {
|
||||||
|
tx.replace_signatures(signers).map_err(|_| {
|
||||||
|
CliError::BadParameter(
|
||||||
|
"Transaction construction failed, incorrect signature or public key provided"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn process_airdrop(
|
fn process_airdrop(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
@ -694,6 +767,7 @@ fn process_deploy(
|
|||||||
.to_string())
|
.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn process_pay(
|
fn process_pay(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
@ -703,12 +777,17 @@ fn process_pay(
|
|||||||
timestamp_pubkey: Option<Pubkey>,
|
timestamp_pubkey: Option<Pubkey>,
|
||||||
witnesses: &Option<Vec<Pubkey>>,
|
witnesses: &Option<Vec<Pubkey>>,
|
||||||
cancelable: bool,
|
cancelable: bool,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: &Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
check_unique_pubkeys(
|
check_unique_pubkeys(
|
||||||
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
||||||
(to, "to".to_string()),
|
(to, "to".to_string()),
|
||||||
)?;
|
)?;
|
||||||
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
||||||
|
let (blockhash, fee_calculator) =
|
||||||
|
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
|
||||||
|
|
||||||
let cancelable = if cancelable {
|
let cancelable = if cancelable {
|
||||||
Some(config.keypair.pubkey())
|
Some(config.keypair.pubkey())
|
||||||
@ -718,9 +797,17 @@ fn process_pay(
|
|||||||
|
|
||||||
if timestamp == None && *witnesses == None {
|
if timestamp == None && *witnesses == None {
|
||||||
let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash);
|
let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash);
|
||||||
|
if let Some(signers) = signers {
|
||||||
|
replace_signatures(&mut tx, &signers)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if sign_only {
|
||||||
|
return_signers(&tx)
|
||||||
|
} else {
|
||||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
||||||
log_instruction_custom_error::<SystemError>(result)
|
log_instruction_custom_error::<SystemError>(result)
|
||||||
|
}
|
||||||
} else if *witnesses == None {
|
} else if *witnesses == None {
|
||||||
let dt = timestamp.unwrap();
|
let dt = timestamp.unwrap();
|
||||||
let dt_pubkey = match timestamp_pubkey {
|
let dt_pubkey = match timestamp_pubkey {
|
||||||
@ -745,9 +832,15 @@ fn process_pay(
|
|||||||
ixs,
|
ixs,
|
||||||
blockhash,
|
blockhash,
|
||||||
);
|
);
|
||||||
|
if let Some(signers) = signers {
|
||||||
|
replace_signatures(&mut tx, &signers)?;
|
||||||
|
}
|
||||||
|
if sign_only {
|
||||||
|
return_signers(&tx)
|
||||||
|
} else {
|
||||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||||
let result =
|
let result = rpc_client
|
||||||
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]);
|
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]);
|
||||||
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@ -755,9 +848,8 @@ fn process_pay(
|
|||||||
"processId": format!("{}", contract_state.pubkey()),
|
"processId": format!("{}", contract_state.pubkey()),
|
||||||
})
|
})
|
||||||
.to_string())
|
.to_string())
|
||||||
|
}
|
||||||
} else if timestamp == None {
|
} else if timestamp == None {
|
||||||
let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
||||||
|
|
||||||
let witness = if let Some(ref witness_vec) = *witnesses {
|
let witness = if let Some(ref witness_vec) = *witnesses {
|
||||||
witness_vec[0]
|
witness_vec[0]
|
||||||
} else {
|
} else {
|
||||||
@ -783,8 +875,14 @@ fn process_pay(
|
|||||||
ixs,
|
ixs,
|
||||||
blockhash,
|
blockhash,
|
||||||
);
|
);
|
||||||
let result =
|
if let Some(signers) = signers {
|
||||||
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]);
|
replace_signatures(&mut tx, &signers)?;
|
||||||
|
}
|
||||||
|
if sign_only {
|
||||||
|
return_signers(&tx)
|
||||||
|
} else {
|
||||||
|
let result = rpc_client
|
||||||
|
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &contract_state]);
|
||||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||||
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
||||||
|
|
||||||
@ -793,6 +891,7 @@ fn process_pay(
|
|||||||
"processId": format!("{}", contract_state.pubkey()),
|
"processId": format!("{}", contract_state.pubkey()),
|
||||||
})
|
})
|
||||||
.to_string())
|
.to_string())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok("Combo transactions not yet handled".to_string())
|
Ok("Combo transactions not yet handled".to_string())
|
||||||
}
|
}
|
||||||
@ -870,6 +969,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||||||
CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey),
|
CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey),
|
||||||
CliCommand::ClusterVersion => process_cluster_version(&rpc_client),
|
CliCommand::ClusterVersion => process_cluster_version(&rpc_client),
|
||||||
CliCommand::Fees => process_fees(&rpc_client),
|
CliCommand::Fees => process_fees(&rpc_client),
|
||||||
|
CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, *slot),
|
||||||
CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
||||||
CliCommand::GetEpochInfo { commitment_config } => {
|
CliCommand::GetEpochInfo { commitment_config } => {
|
||||||
process_get_epoch_info(&rpc_client, commitment_config)
|
process_get_epoch_info(&rpc_client, commitment_config)
|
||||||
@ -926,18 +1026,36 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||||||
*lamports,
|
*lamports,
|
||||||
),
|
),
|
||||||
// Deactivate stake account
|
// Deactivate stake account
|
||||||
CliCommand::DeactivateStake(stake_account_pubkey) => {
|
CliCommand::DeactivateStake {
|
||||||
process_deactivate_stake_account(&rpc_client, config, &stake_account_pubkey)
|
stake_account_pubkey,
|
||||||
}
|
sign_only,
|
||||||
CliCommand::DelegateStake(stake_account_pubkey, vote_account_pubkey, force) => {
|
ref signers,
|
||||||
process_delegate_stake(
|
blockhash,
|
||||||
|
} => process_deactivate_stake_account(
|
||||||
|
&rpc_client,
|
||||||
|
config,
|
||||||
|
&stake_account_pubkey,
|
||||||
|
*sign_only,
|
||||||
|
signers,
|
||||||
|
*blockhash,
|
||||||
|
),
|
||||||
|
CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force,
|
||||||
|
sign_only,
|
||||||
|
ref signers,
|
||||||
|
blockhash,
|
||||||
|
} => process_delegate_stake(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
&stake_account_pubkey,
|
&stake_account_pubkey,
|
||||||
&vote_account_pubkey,
|
&vote_account_pubkey,
|
||||||
*force,
|
*force,
|
||||||
)
|
*sign_only,
|
||||||
}
|
signers,
|
||||||
|
*blockhash,
|
||||||
|
),
|
||||||
CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey) => {
|
CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey) => {
|
||||||
process_redeem_vote_credits(
|
process_redeem_vote_credits(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
@ -1118,6 +1236,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||||||
timestamp_pubkey,
|
timestamp_pubkey,
|
||||||
ref witnesses,
|
ref witnesses,
|
||||||
cancelable,
|
cancelable,
|
||||||
|
sign_only,
|
||||||
|
ref signers,
|
||||||
|
blockhash,
|
||||||
} => process_pay(
|
} => process_pay(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
@ -1127,6 +1248,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||||||
*timestamp_pubkey,
|
*timestamp_pubkey,
|
||||||
witnesses,
|
witnesses,
|
||||||
*cancelable,
|
*cancelable,
|
||||||
|
*sign_only,
|
||||||
|
signers,
|
||||||
|
*blockhash,
|
||||||
),
|
),
|
||||||
CliCommand::ShowAccount {
|
CliCommand::ShowAccount {
|
||||||
pubkey,
|
pubkey,
|
||||||
@ -1410,6 +1534,29 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||||||
Arg::with_name("cancelable")
|
Arg::with_name("cancelable")
|
||||||
.long("cancelable")
|
.long("cancelable")
|
||||||
.takes_value(false),
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("sign_only")
|
||||||
|
.long("sign-only")
|
||||||
|
.takes_value(false)
|
||||||
|
.help("Sign the transaction offline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("signer")
|
||||||
|
.long("signer")
|
||||||
|
.value_name("PUBKEY=BASE58_SIG")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_pubkey_sig)
|
||||||
|
.multiple(true)
|
||||||
|
.help("Provide a public-key/signature pair for the transaction"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("blockhash")
|
||||||
|
.long("blockhash")
|
||||||
|
.value_name("BLOCKHASH")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_hash)
|
||||||
|
.help("Use the supplied blockhash"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@ -1667,6 +1814,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
},
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
@ -1694,6 +1844,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![witness0, witness1]),
|
witnesses: Some(vec![witness0, witness1]),
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
},
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
@ -1717,6 +1870,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![witness0]),
|
witnesses: Some(vec![witness0]),
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
},
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
@ -1744,6 +1900,130 @@ mod tests {
|
|||||||
timestamp_pubkey: Some(witness0),
|
timestamp_pubkey: Some(witness0),
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Pay Subcommand w/ sign-only
|
||||||
|
let test_pay = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"pay",
|
||||||
|
&pubkey_string,
|
||||||
|
"50",
|
||||||
|
"lamports",
|
||||||
|
"--sign-only",
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_pay).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::Pay {
|
||||||
|
lamports: 50,
|
||||||
|
to: pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: true,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
|
},
|
||||||
|
require_keypair: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Pay Subcommand w/ signer
|
||||||
|
let key1 = Pubkey::new_rand();
|
||||||
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let signer1 = format!("{}={}", key1, sig1);
|
||||||
|
let test_pay = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"pay",
|
||||||
|
&pubkey_string,
|
||||||
|
"50",
|
||||||
|
"lamports",
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_pay).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::Pay {
|
||||||
|
lamports: 50,
|
||||||
|
to: pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1)]),
|
||||||
|
blockhash: None,
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Pay Subcommand w/ signers
|
||||||
|
let key2 = Pubkey::new_rand();
|
||||||
|
let sig2 = Keypair::new().sign_message(&[1u8]);
|
||||||
|
let signer2 = format!("{}={}", key2, sig2);
|
||||||
|
let test_pay = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"pay",
|
||||||
|
&pubkey_string,
|
||||||
|
"50",
|
||||||
|
"lamports",
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
"--signer",
|
||||||
|
&signer2,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_pay).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::Pay {
|
||||||
|
lamports: 50,
|
||||||
|
to: pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1), (key2, sig2)]),
|
||||||
|
blockhash: None,
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Pay Subcommand w/ Blockhash
|
||||||
|
let blockhash = Hash::default();
|
||||||
|
let blockhash_string = format!("{}", blockhash);
|
||||||
|
let test_pay = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"pay",
|
||||||
|
&pubkey_string,
|
||||||
|
"50",
|
||||||
|
"lamports",
|
||||||
|
"--blockhash",
|
||||||
|
&blockhash_string,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_pay).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::Pay {
|
||||||
|
lamports: 50,
|
||||||
|
to: pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: Some(blockhash),
|
||||||
},
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
@ -1788,6 +2068,9 @@ mod tests {
|
|||||||
timestamp_pubkey: Some(witness0),
|
timestamp_pubkey: Some(witness0),
|
||||||
witnesses: Some(vec![witness0, witness1]),
|
witnesses: Some(vec![witness0, witness1]),
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
},
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
@ -1894,7 +2177,12 @@ mod tests {
|
|||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||||
|
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let stake_pubkey = Pubkey::new_rand();
|
||||||
config.command = CliCommand::DeactivateStake(stake_pubkey);
|
config.command = CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey: stake_pubkey,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
|
};
|
||||||
let signature = process_command(&config);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||||
|
|
||||||
@ -1915,6 +2203,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let signature = process_command(&config);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||||
@ -1928,6 +2219,9 @@ mod tests {
|
|||||||
timestamp_pubkey: Some(config.keypair.pubkey()),
|
timestamp_pubkey: Some(config.keypair.pubkey()),
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
||||||
@ -1949,6 +2243,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![witness]),
|
witnesses: Some(vec![witness]),
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
||||||
@ -2056,6 +2353,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
assert!(process_command(&config).is_err());
|
assert!(process_command(&config).is_err());
|
||||||
|
|
||||||
@ -2066,6 +2366,9 @@ mod tests {
|
|||||||
timestamp_pubkey: Some(config.keypair.pubkey()),
|
timestamp_pubkey: Some(config.keypair.pubkey()),
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
assert!(process_command(&config).is_err());
|
assert!(process_command(&config).is_err());
|
||||||
|
|
||||||
@ -2076,6 +2379,9 @@ mod tests {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![witness]),
|
witnesses: Some(vec![witness]),
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
assert!(process_command(&config).is_err());
|
assert!(process_command(&config).is_err());
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use indicatif::{ProgressBar, ProgressStyle};
|
|||||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||||
use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
|
use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock,
|
clock::{self, Slot},
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
@ -53,6 +53,17 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
|||||||
.about("Get the version of the cluster entrypoint"),
|
.about("Get the version of the cluster entrypoint"),
|
||||||
)
|
)
|
||||||
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
|
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
|
||||||
|
.subcommand(SubCommand::with_name("get-block-time")
|
||||||
|
.about("Get estimated production time of a block")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("slot")
|
||||||
|
.index(1)
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("SLOT")
|
||||||
|
.required(true)
|
||||||
|
.help("Slot number of the block to query")
|
||||||
|
)
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("get-epoch-info")
|
SubCommand::with_name("get-epoch-info")
|
||||||
.about("Get information about the current epoch")
|
.about("Get information about the current epoch")
|
||||||
@ -187,6 +198,14 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||||
|
let slot = value_t_or_exit!(matches, "slot", u64);
|
||||||
|
Ok(CliCommandInfo {
|
||||||
|
command: CliCommand::GetBlockTime { slot },
|
||||||
|
require_keypair: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||||
let commitment_config = if matches.is_present("confirmed") {
|
let commitment_config = if matches.is_present("confirmed") {
|
||||||
CommitmentConfig::default()
|
CommitmentConfig::default()
|
||||||
@ -313,6 +332,11 @@ pub fn process_fees(rpc_client: &RpcClient) -> ProcessResult {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResult {
|
||||||
|
let timestamp = rpc_client.get_block_time(slot)?;
|
||||||
|
Ok(timestamp.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_get_epoch_info(
|
pub fn process_get_epoch_info(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
commitment_config: &CommitmentConfig,
|
commitment_config: &CommitmentConfig,
|
||||||
@ -442,8 +466,7 @@ pub fn process_ping(
|
|||||||
// Sleep for half a slot
|
// Sleep for half a slot
|
||||||
if signal_receiver
|
if signal_receiver
|
||||||
.recv_timeout(Duration::from_millis(
|
.recv_timeout(Duration::from_millis(
|
||||||
500 * solana_sdk::clock::DEFAULT_TICKS_PER_SLOT
|
500 * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||||
/ solana_sdk::clock::DEFAULT_TICKS_PER_SECOND,
|
|
||||||
))
|
))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@ -656,6 +679,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let slot = 100;
|
||||||
|
let test_get_block_time = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"get-block-time",
|
||||||
|
&slot.to_string(),
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_get_block_time).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::GetBlockTime { slot },
|
||||||
|
require_keypair: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let test_get_epoch_info = test_commands
|
let test_get_epoch_info = test_commands
|
||||||
.clone()
|
.clone()
|
||||||
.get_matches_from(vec!["test", "get-epoch-info"]);
|
.get_matches_from(vec!["test", "get-epoch-info"]);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use console::style;
|
use console::style;
|
||||||
|
use solana_sdk::transaction::Transaction;
|
||||||
|
|
||||||
// Pretty print a "name value"
|
// Pretty print a "name value"
|
||||||
pub fn println_name_value(name: &str, value: &str) {
|
pub fn println_name_value(name: &str, value: &str) {
|
||||||
@ -22,3 +23,14 @@ pub fn println_name_value_or(name: &str, value: &str, default_value: &str) {
|
|||||||
println!("{} {}", style(name).bold(), style(value));
|
println!("{} {}", style(name).bold(), style(value));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn println_signers(tx: &Transaction) {
|
||||||
|
println!();
|
||||||
|
println!("Blockhash: {}", tx.message.recent_blockhash);
|
||||||
|
println!("Signers (Pubkey=Signature):");
|
||||||
|
tx.signatures
|
||||||
|
.iter()
|
||||||
|
.zip(tx.message.account_keys.clone())
|
||||||
|
.for_each(|(signature, pubkey)| println!(" {:?}={:?}", pubkey, signature));
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
339
cli/src/stake.rs
339
cli/src/stake.rs
@ -1,14 +1,16 @@
|
|||||||
use crate::cli::{
|
use crate::cli::{
|
||||||
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
||||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
get_blockhash_fee_calculator, log_instruction_custom_error, replace_signatures, return_signers,
|
||||||
|
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||||
};
|
};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use console::style;
|
use console::style;
|
||||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::{Keypair, Signature};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account_utils::State,
|
account_utils::State,
|
||||||
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::KeypairUtil,
|
signature::KeypairUtil,
|
||||||
system_instruction::SystemError,
|
system_instruction::SystemError,
|
||||||
@ -120,6 +122,29 @@ impl StakeSubCommands for App<'_, '_> {
|
|||||||
.validator(is_pubkey_or_keypair)
|
.validator(is_pubkey_or_keypair)
|
||||||
.help("The vote account to which the stake will be delegated")
|
.help("The vote account to which the stake will be delegated")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("sign_only")
|
||||||
|
.long("sign-only")
|
||||||
|
.takes_value(false)
|
||||||
|
.help("Sign the transaction offline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("signer")
|
||||||
|
.long("signer")
|
||||||
|
.value_name("PUBKEY=BASE58_SIG")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_pubkey_sig)
|
||||||
|
.multiple(true)
|
||||||
|
.help("Provide a public-key/signature pair for the transaction"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("blockhash")
|
||||||
|
.long("blockhash")
|
||||||
|
.value_name("BLOCKHASH")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_hash)
|
||||||
|
.help("Use the supplied blockhash"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("stake-authorize-staker")
|
SubCommand::with_name("stake-authorize-staker")
|
||||||
@ -176,6 +201,29 @@ impl StakeSubCommands for App<'_, '_> {
|
|||||||
.required(true)
|
.required(true)
|
||||||
.help("Stake account to be deactivated.")
|
.help("Stake account to be deactivated.")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("sign_only")
|
||||||
|
.long("sign-only")
|
||||||
|
.takes_value(false)
|
||||||
|
.help("Sign the transaction offline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("signer")
|
||||||
|
.long("signer")
|
||||||
|
.value_name("PUBKEY=BASE58_SIG")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_pubkey_sig)
|
||||||
|
.multiple(true)
|
||||||
|
.help("Provide a public-key/signature pair for the transaction"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("blockhash")
|
||||||
|
.long("blockhash")
|
||||||
|
.value_name("BLOCKHASH")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_hash)
|
||||||
|
.help("Use the supplied blockhash"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("withdraw-stake")
|
SubCommand::with_name("withdraw-stake")
|
||||||
@ -293,10 +341,20 @@ pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result<CliCommand
|
|||||||
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
||||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||||
let force = matches.is_present("force");
|
let force = matches.is_present("force");
|
||||||
|
let sign_only = matches.is_present("sign_only");
|
||||||
|
let signers = pubkeys_sigs_of(&matches, "signer");
|
||||||
|
let blockhash = value_of(matches, "blockhash");
|
||||||
|
|
||||||
Ok(CliCommandInfo {
|
Ok(CliCommandInfo {
|
||||||
command: CliCommand::DelegateStake(stake_account_pubkey, vote_account_pubkey, force),
|
command: CliCommand::DelegateStake {
|
||||||
require_keypair: true,
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force,
|
||||||
|
sign_only,
|
||||||
|
signers,
|
||||||
|
blockhash,
|
||||||
|
},
|
||||||
|
require_keypair: !sign_only,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +386,17 @@ pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result<CliCommandI
|
|||||||
|
|
||||||
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||||
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
||||||
|
let sign_only = matches.is_present("sign_only");
|
||||||
|
let signers = pubkeys_sigs_of(&matches, "signer");
|
||||||
|
let blockhash = value_of(matches, "blockhash");
|
||||||
Ok(CliCommandInfo {
|
Ok(CliCommandInfo {
|
||||||
command: CliCommand::DeactivateStake(stake_account_pubkey),
|
command: CliCommand::DeactivateStake {
|
||||||
require_keypair: true,
|
stake_account_pubkey,
|
||||||
|
sign_only,
|
||||||
|
signers,
|
||||||
|
blockhash,
|
||||||
|
},
|
||||||
|
require_keypair: !sign_only,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,8 +529,12 @@ pub fn process_deactivate_stake_account(
|
|||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
stake_account_pubkey: &Pubkey,
|
stake_account_pubkey: &Pubkey,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: &Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
let (recent_blockhash, fee_calculator) =
|
||||||
|
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
|
||||||
let ixs = vec![stake_instruction::deactivate_stake(
|
let ixs = vec![stake_instruction::deactivate_stake(
|
||||||
stake_account_pubkey,
|
stake_account_pubkey,
|
||||||
&config.keypair.pubkey(),
|
&config.keypair.pubkey(),
|
||||||
@ -475,9 +545,16 @@ pub fn process_deactivate_stake_account(
|
|||||||
&[&config.keypair],
|
&[&config.keypair],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
if let Some(signers) = signers {
|
||||||
|
replace_signatures(&mut tx, &signers)?;
|
||||||
|
}
|
||||||
|
if sign_only {
|
||||||
|
return_signers(&tx)
|
||||||
|
} else {
|
||||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
||||||
log_instruction_custom_error::<StakeError>(result)
|
log_instruction_custom_error::<StakeError>(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_withdraw_stake(
|
pub fn process_withdraw_stake(
|
||||||
@ -644,6 +721,9 @@ pub fn process_delegate_stake(
|
|||||||
stake_account_pubkey: &Pubkey,
|
stake_account_pubkey: &Pubkey,
|
||||||
vote_account_pubkey: &Pubkey,
|
vote_account_pubkey: &Pubkey,
|
||||||
force: bool,
|
force: bool,
|
||||||
|
sign_only: bool,
|
||||||
|
signers: &Option<Vec<(Pubkey, Signature)>>,
|
||||||
|
blockhash: Option<Hash>,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
check_unique_pubkeys(
|
check_unique_pubkeys(
|
||||||
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
||||||
@ -690,7 +770,8 @@ pub fn process_delegate_stake(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
let (recent_blockhash, fee_calculator) =
|
||||||
|
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
|
||||||
|
|
||||||
let ixs = vec![stake_instruction::delegate_stake(
|
let ixs = vec![stake_instruction::delegate_stake(
|
||||||
stake_account_pubkey,
|
stake_account_pubkey,
|
||||||
@ -704,9 +785,16 @@ pub fn process_delegate_stake(
|
|||||||
&[&config.keypair],
|
&[&config.keypair],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
if let Some(signers) = signers {
|
||||||
|
replace_signatures(&mut tx, &signers)?;
|
||||||
|
}
|
||||||
|
if sign_only {
|
||||||
|
return_signers(&tx)
|
||||||
|
} else {
|
||||||
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
||||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
||||||
log_instruction_custom_error::<StakeError>(result)
|
log_instruction_custom_error::<StakeError>(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -831,18 +919,25 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Test DelegateStake Subcommand
|
// Test DelegateStake Subcommand
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let vote_account_pubkey = Pubkey::new_rand();
|
||||||
let stake_pubkey_string = stake_pubkey.to_string();
|
let vote_account_string = vote_account_pubkey.to_string();
|
||||||
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
"test",
|
"test",
|
||||||
"delegate-stake",
|
"delegate-stake",
|
||||||
&stake_pubkey_string,
|
|
||||||
&stake_account_string,
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
]);
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&test_delegate_stake).unwrap(),
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
CliCommandInfo {
|
CliCommandInfo {
|
||||||
command: CliCommand::DelegateStake(stake_pubkey, stake_account_pubkey, false),
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -851,13 +946,124 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
"delegate-stake",
|
"delegate-stake",
|
||||||
"--force",
|
"--force",
|
||||||
&stake_pubkey_string,
|
|
||||||
&stake_account_string,
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
]);
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&test_delegate_stake).unwrap(),
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
CliCommandInfo {
|
CliCommandInfo {
|
||||||
command: CliCommand::DelegateStake(stake_pubkey, stake_account_pubkey, true),
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: true,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Delegate Subcommand w/ Blockhash
|
||||||
|
let blockhash = Hash::default();
|
||||||
|
let blockhash_string = format!("{}", blockhash);
|
||||||
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"delegate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
|
"--blockhash",
|
||||||
|
&blockhash_string,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: Some(blockhash)
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"delegate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
|
"--sign-only",
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: false,
|
||||||
|
sign_only: true,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Delegate Subcommand w/ signer
|
||||||
|
let key1 = Pubkey::new_rand();
|
||||||
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let signer1 = format!("{}={}", key1, sig1);
|
||||||
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"delegate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1)]),
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Delegate Subcommand w/ signers
|
||||||
|
let key2 = Pubkey::new_rand();
|
||||||
|
let sig2 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let signer2 = format!("{}={}", key2, sig2);
|
||||||
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"delegate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
&vote_account_string,
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
"--signer",
|
||||||
|
&signer2,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_delegate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DelegateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
vote_account_pubkey,
|
||||||
|
force: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1), (key2, sig2)]),
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -866,7 +1072,7 @@ mod tests {
|
|||||||
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
||||||
"test",
|
"test",
|
||||||
"withdraw-stake",
|
"withdraw-stake",
|
||||||
&stake_pubkey_string,
|
&stake_account_string,
|
||||||
&stake_account_string,
|
&stake_account_string,
|
||||||
"42",
|
"42",
|
||||||
"lamports",
|
"lamports",
|
||||||
@ -875,7 +1081,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&test_withdraw_stake).unwrap(),
|
parse_command(&test_withdraw_stake).unwrap(),
|
||||||
CliCommandInfo {
|
CliCommandInfo {
|
||||||
command: CliCommand::WithdrawStake(stake_pubkey, stake_account_pubkey, 42),
|
command: CliCommand::WithdrawStake(stake_account_pubkey, stake_account_pubkey, 42),
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -884,12 +1090,109 @@ mod tests {
|
|||||||
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
"test",
|
"test",
|
||||||
"deactivate-stake",
|
"deactivate-stake",
|
||||||
&stake_pubkey_string,
|
&stake_account_string,
|
||||||
]);
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command(&test_deactivate_stake).unwrap(),
|
parse_command(&test_deactivate_stake).unwrap(),
|
||||||
CliCommandInfo {
|
CliCommandInfo {
|
||||||
command: CliCommand::DeactivateStake(stake_pubkey),
|
command: CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Deactivate Subcommand w/ Blockhash
|
||||||
|
let blockhash = Hash::default();
|
||||||
|
let blockhash_string = format!("{}", blockhash);
|
||||||
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"deactivate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
"--blockhash",
|
||||||
|
&blockhash_string,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_deactivate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: Some(blockhash)
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"deactivate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
"--sign-only",
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_deactivate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
sign_only: true,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Deactivate Subcommand w/ signers
|
||||||
|
let key1 = Pubkey::new_rand();
|
||||||
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let signer1 = format!("{}={}", key1, sig1);
|
||||||
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"deactivate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_deactivate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1)]),
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
|
require_keypair: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Deactivate Subcommand w/ signers
|
||||||
|
let key2 = Pubkey::new_rand();
|
||||||
|
let sig2 = Keypair::new().sign_message(&[0u8]);
|
||||||
|
let signer2 = format!("{}={}", key2, sig2);
|
||||||
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"deactivate-stake",
|
||||||
|
&stake_account_string,
|
||||||
|
"--signer",
|
||||||
|
&signer1,
|
||||||
|
"--signer",
|
||||||
|
&signer2,
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_deactivate_stake).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::DeactivateStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(vec![(key1, sig1), (key2, sig2)]),
|
||||||
|
blockhash: None
|
||||||
|
},
|
||||||
require_keypair: true
|
require_keypair: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
104
cli/tests/pay.rs
104
cli/tests/pay.rs
@ -3,9 +3,9 @@ use serde_json::Value;
|
|||||||
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
|
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_drone::drone::run_local_drone;
|
use solana_drone::drone::run_local_drone;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::{hash::Hash, pubkey::Pubkey, signature::KeypairUtil, signature::Signature};
|
||||||
use solana_sdk::signature::KeypairUtil;
|
|
||||||
use std::fs::remove_dir_all;
|
use std::fs::remove_dir_all;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -71,6 +71,9 @@ fn test_cli_timestamp_tx() {
|
|||||||
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
|
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
|
||||||
witnesses: None,
|
witnesses: None,
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let sig_response = process_command(&config_payer);
|
let sig_response = process_command(&config_payer);
|
||||||
|
|
||||||
@ -138,6 +141,9 @@ fn test_cli_witness_tx() {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![config_witness.keypair.pubkey()]),
|
witnesses: Some(vec![config_witness.keypair.pubkey()]),
|
||||||
cancelable: false,
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let sig_response = process_command(&config_payer);
|
let sig_response = process_command(&config_payer);
|
||||||
|
|
||||||
@ -198,6 +204,9 @@ fn test_cli_cancel_tx() {
|
|||||||
timestamp_pubkey: None,
|
timestamp_pubkey: None,
|
||||||
witnesses: Some(vec![config_witness.keypair.pubkey()]),
|
witnesses: Some(vec![config_witness.keypair.pubkey()]),
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
|
sign_only: false,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
};
|
};
|
||||||
let sig_response = process_command(&config_payer).unwrap();
|
let sig_response = process_command(&config_payer).unwrap();
|
||||||
|
|
||||||
@ -223,3 +232,94 @@ fn test_cli_cancel_tx() {
|
|||||||
server.close().unwrap();
|
server.close().unwrap();
|
||||||
remove_dir_all(ledger_path).unwrap();
|
remove_dir_all(ledger_path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_offline_pay_tx() {
|
||||||
|
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
|
||||||
|
let bob_pubkey = Pubkey::new_rand();
|
||||||
|
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
run_local_drone(alice, sender, None);
|
||||||
|
let drone_addr = receiver.recv().unwrap();
|
||||||
|
|
||||||
|
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||||
|
|
||||||
|
let mut config_offline = CliConfig::default();
|
||||||
|
config_offline.json_rpc_url =
|
||||||
|
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||||
|
let mut config_online = CliConfig::default();
|
||||||
|
config_online.json_rpc_url =
|
||||||
|
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||||
|
assert_ne!(
|
||||||
|
config_offline.keypair.pubkey(),
|
||||||
|
config_online.keypair.pubkey()
|
||||||
|
);
|
||||||
|
|
||||||
|
request_and_confirm_airdrop(
|
||||||
|
&rpc_client,
|
||||||
|
&drone_addr,
|
||||||
|
&config_offline.keypair.pubkey(),
|
||||||
|
50,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
request_and_confirm_airdrop(
|
||||||
|
&rpc_client,
|
||||||
|
&drone_addr,
|
||||||
|
&config_online.keypair.pubkey(),
|
||||||
|
50,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
check_balance(50, &rpc_client, &config_offline.keypair.pubkey());
|
||||||
|
check_balance(50, &rpc_client, &config_online.keypair.pubkey());
|
||||||
|
|
||||||
|
config_offline.command = CliCommand::Pay {
|
||||||
|
lamports: 10,
|
||||||
|
to: bob_pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: true,
|
||||||
|
signers: None,
|
||||||
|
blockhash: None,
|
||||||
|
};
|
||||||
|
let sig_response = process_command(&config_offline).unwrap();
|
||||||
|
|
||||||
|
check_balance(50, &rpc_client, &config_offline.keypair.pubkey());
|
||||||
|
check_balance(50, &rpc_client, &config_online.keypair.pubkey());
|
||||||
|
check_balance(0, &rpc_client, &bob_pubkey);
|
||||||
|
|
||||||
|
let object: Value = serde_json::from_str(&sig_response).unwrap();
|
||||||
|
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||||
|
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
|
||||||
|
let signers: Vec<_> = signer_strings
|
||||||
|
.iter()
|
||||||
|
.map(|signer_string| {
|
||||||
|
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||||
|
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||||
|
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||||
|
(key, sig)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
config_online.command = CliCommand::Pay {
|
||||||
|
lamports: 10,
|
||||||
|
to: bob_pubkey,
|
||||||
|
timestamp: None,
|
||||||
|
timestamp_pubkey: None,
|
||||||
|
witnesses: None,
|
||||||
|
cancelable: false,
|
||||||
|
sign_only: false,
|
||||||
|
signers: Some(signers),
|
||||||
|
blockhash: Some(blockhash_str.parse::<Hash>().unwrap()),
|
||||||
|
};
|
||||||
|
process_command(&config_online).unwrap();
|
||||||
|
|
||||||
|
check_balance(40, &rpc_client, &config_offline.keypair.pubkey());
|
||||||
|
check_balance(50, &rpc_client, &config_online.keypair.pubkey());
|
||||||
|
check_balance(10, &rpc_client, &bob_pubkey);
|
||||||
|
|
||||||
|
server.close().unwrap();
|
||||||
|
remove_dir_all(ledger_path).unwrap();
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ use log::*;
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
clock::{Slot, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT},
|
clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT},
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
@ -196,6 +196,32 @@ impl RpcClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_block_time(&self, slot: Slot) -> io::Result<UnixTimestamp> {
|
||||||
|
let params = json!(slot);
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.send(&RpcRequest::GetBlockTime, Some(params), 0, None);
|
||||||
|
|
||||||
|
response
|
||||||
|
.map(|result_json| {
|
||||||
|
if result_json.is_null() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("Block Not Found: slot={}", slot),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let result = serde_json::from_value(result_json)?;
|
||||||
|
trace!("Response block timestamp {:?} {:?}", slot, result);
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("GetBlockTime request failure: {:?}", err),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_epoch_info(&self) -> io::Result<RpcEpochInfo> {
|
pub fn get_epoch_info(&self) -> io::Result<RpcEpochInfo> {
|
||||||
self.get_epoch_info_with_commitment(CommitmentConfig::default())
|
self.get_epoch_info_with_commitment(CommitmentConfig::default())
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ pub enum RpcRequest {
|
|||||||
ValidatorExit,
|
ValidatorExit,
|
||||||
GetAccountInfo,
|
GetAccountInfo,
|
||||||
GetBalance,
|
GetBalance,
|
||||||
|
GetBlockTime,
|
||||||
GetClusterNodes,
|
GetClusterNodes,
|
||||||
GetEpochInfo,
|
GetEpochInfo,
|
||||||
GetEpochSchedule,
|
GetEpochSchedule,
|
||||||
@ -150,6 +151,7 @@ impl RpcRequest {
|
|||||||
RpcRequest::ValidatorExit => "validatorExit",
|
RpcRequest::ValidatorExit => "validatorExit",
|
||||||
RpcRequest::GetAccountInfo => "getAccountInfo",
|
RpcRequest::GetAccountInfo => "getAccountInfo",
|
||||||
RpcRequest::GetBalance => "getBalance",
|
RpcRequest::GetBalance => "getBalance",
|
||||||
|
RpcRequest::GetBlockTime => "getBlockTime",
|
||||||
RpcRequest::GetClusterNodes => "getClusterNodes",
|
RpcRequest::GetClusterNodes => "getClusterNodes",
|
||||||
RpcRequest::GetEpochInfo => "getEpochInfo",
|
RpcRequest::GetEpochInfo => "getEpochInfo",
|
||||||
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
||||||
|
@ -20,7 +20,7 @@ use solana_ledger::{bank_forks::BankForks, blocktree::Blocktree};
|
|||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
clock::Slot,
|
clock::{Slot, UnixTimestamp},
|
||||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
fee_calculator::FeeCalculator,
|
fee_calculator::FeeCalculator,
|
||||||
@ -28,6 +28,7 @@ use solana_sdk::{
|
|||||||
inflation::Inflation,
|
inflation::Inflation,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
|
timing::slot_duration_from_slots_per_year,
|
||||||
transaction::{self, Transaction},
|
transaction::{self, Transaction},
|
||||||
};
|
};
|
||||||
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
||||||
@ -304,6 +305,21 @@ impl JsonRpcRequestProcessor {
|
|||||||
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> {
|
pub fn get_confirmed_block(&self, slot: Slot) -> Result<Option<RpcConfirmedBlock>> {
|
||||||
Ok(self.blocktree.get_confirmed_block(slot).ok())
|
Ok(self.blocktree.get_confirmed_block(slot).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The `get_block_time` method is not fully implemented. It currently returns `slot` *
|
||||||
|
// DEFAULT_MS_PER_SLOT offset from 0 for all requests, and null for any values that would
|
||||||
|
// overflow.
|
||||||
|
pub fn get_block_time(&self, slot: Slot) -> Result<Option<UnixTimestamp>> {
|
||||||
|
// This calculation currently assumes that bank.ticks_per_slot and bank.slots_per_year will
|
||||||
|
// remain unchanged after genesis. If these values will be variable in the future, those
|
||||||
|
// timing parameters will need to be stored persistently, and this calculation will likely
|
||||||
|
// need to be moved upstream into blocktree. Also, an explicit commitment level will need
|
||||||
|
// to be set.
|
||||||
|
let bank = self.bank(None);
|
||||||
|
let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year());
|
||||||
|
|
||||||
|
Ok(self.blocktree.get_block_time(slot, slot_duration))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
|
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
|
||||||
@ -513,6 +529,9 @@ pub trait RpcSol {
|
|||||||
meta: Self::Metadata,
|
meta: Self::Metadata,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
) -> Result<Option<RpcConfirmedBlock>>;
|
) -> Result<Option<RpcConfirmedBlock>>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "getBlockTime")]
|
||||||
|
fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result<Option<UnixTimestamp>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RpcSolImpl;
|
pub struct RpcSolImpl;
|
||||||
@ -967,6 +986,10 @@ impl RpcSol for RpcSolImpl {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.get_confirmed_block(slot)
|
.get_confirmed_block(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result<Option<UnixTimestamp>> {
|
||||||
|
meta.request_processor.read().unwrap().get_block_time(slot)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -1818,4 +1841,57 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_block_time() {
|
||||||
|
let bob_pubkey = Pubkey::new_rand();
|
||||||
|
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
|
|
||||||
|
let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year());
|
||||||
|
|
||||||
|
let slot = 100;
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#,
|
||||||
|
slot
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#,
|
||||||
|
(slot * slot_duration).as_secs()
|
||||||
|
);
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_str(&expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
let slot = 12345;
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#,
|
||||||
|
slot
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#,
|
||||||
|
(slot * slot_duration).as_secs()
|
||||||
|
);
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_str(&expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
let slot = 123450000000000000u64;
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getBlockTime","params":[{}]}}"#,
|
||||||
|
slot
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta);
|
||||||
|
let expected = format!(r#"{{"jsonrpc":"2.0","result":null,"id":1}}"#);
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_str(&expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
let default_target_tick_duration =
|
let default_target_tick_duration =
|
||||||
&timing::duration_as_ms(&PohConfig::default().target_tick_duration).to_string();
|
timing::duration_as_us(&PohConfig::default().target_tick_duration);
|
||||||
let default_ticks_per_slot = &clock::DEFAULT_TICKS_PER_SLOT.to_string();
|
let default_ticks_per_slot = &clock::DEFAULT_TICKS_PER_SLOT.to_string();
|
||||||
let default_operating_mode = "softlaunch";
|
let default_operating_mode = "softlaunch";
|
||||||
|
|
||||||
@ -261,7 +261,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
.long("target-tick-duration")
|
.long("target-tick-duration")
|
||||||
.value_name("MILLIS")
|
.value_name("MILLIS")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.default_value(default_target_tick_duration)
|
|
||||||
.help("The target tick rate of the cluster in milliseconds"),
|
.help("The target tick rate of the cluster in milliseconds"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -370,8 +369,11 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut poh_config = PohConfig::default();
|
let mut poh_config = PohConfig::default();
|
||||||
poh_config.target_tick_duration =
|
poh_config.target_tick_duration = if matches.is_present("target_tick_duration") {
|
||||||
Duration::from_millis(value_t_or_exit!(matches, "target_tick_duration", u64));
|
Duration::from_micros(value_t_or_exit!(matches, "target_tick_duration", u64))
|
||||||
|
} else {
|
||||||
|
Duration::from_micros(default_target_tick_duration)
|
||||||
|
};
|
||||||
|
|
||||||
let operating_mode = if matches.value_of("operating_mode").unwrap() == "development" {
|
let operating_mode = if matches.value_of("operating_mode").unwrap() == "development" {
|
||||||
OperatingMode::Development
|
OperatingMode::Development
|
||||||
|
@ -4,7 +4,9 @@ use clap::{
|
|||||||
crate_description, crate_name, values_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand,
|
crate_description, crate_name, values_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand,
|
||||||
};
|
};
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
use solana_clap_utils::keypair::{keypair_from_seed_phrase, SKIP_SEED_PHRASE_VALIDATION_ARG};
|
use solana_clap_utils::keypair::{
|
||||||
|
keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||||
|
};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
pubkey::write_pubkey_file,
|
pubkey::write_pubkey_file,
|
||||||
signature::{
|
signature::{
|
||||||
@ -78,6 +80,12 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
.long("no-passphrase")
|
.long("no-passphrase")
|
||||||
.help("Do not prompt for a passphrase"),
|
.help("Do not prompt for a passphrase"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("no_outfile")
|
||||||
|
.long("no-outfile")
|
||||||
|
.conflicts_with_all(&["outfile", "silent"])
|
||||||
|
.help("Only print a seed phrase and pubkey. Do not output a keypair file"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("silent")
|
Arg::with_name("silent")
|
||||||
.short("s")
|
.short("s")
|
||||||
@ -132,6 +140,11 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Path to keypair file"),
|
.help("Path to keypair file"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||||
|
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||||
|
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("outfile")
|
Arg::with_name("outfile")
|
||||||
.short("o")
|
.short("o")
|
||||||
@ -186,6 +199,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
let keypair = if infile == "-" {
|
let keypair = if infile == "-" {
|
||||||
let mut stdin = std::io::stdin();
|
let mut stdin = std::io::stdin();
|
||||||
read_keypair(&mut stdin)?
|
read_keypair(&mut stdin)?
|
||||||
|
} else if infile == ASK_KEYWORD {
|
||||||
|
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||||
|
keypair_from_seed_phrase("pubkey recovery", skip_validation)?
|
||||||
} else {
|
} else {
|
||||||
read_keypair_file(infile)?
|
read_keypair_file(infile)?
|
||||||
};
|
};
|
||||||
@ -201,14 +217,18 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
("new", Some(matches)) => {
|
("new", Some(matches)) => {
|
||||||
let mut path = dirs::home_dir().expect("home directory");
|
let mut path = dirs::home_dir().expect("home directory");
|
||||||
let outfile = if matches.is_present("outfile") {
|
let outfile = if matches.is_present("outfile") {
|
||||||
matches.value_of("outfile").unwrap()
|
matches.value_of("outfile")
|
||||||
|
} else if matches.is_present("no_outfile") {
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
path.extend(&[".config", "solana", "id.json"]);
|
path.extend(&[".config", "solana", "id.json"]);
|
||||||
path.to_str().unwrap()
|
Some(path.to_str().unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
if outfile != "-" {
|
match outfile {
|
||||||
check_for_overwrite(&outfile, &matches);
|
Some("-") => (),
|
||||||
|
Some(outfile) => check_for_overwrite(&outfile, &matches),
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
|
let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
|
||||||
@ -223,7 +243,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||||||
let seed = Seed::new(&mnemonic, &passphrase);
|
let seed = Seed::new(&mnemonic, &passphrase);
|
||||||
let keypair = keypair_from_seed(seed.as_bytes())?;
|
let keypair = keypair_from_seed(seed.as_bytes())?;
|
||||||
|
|
||||||
|
if let Some(outfile) = outfile {
|
||||||
output_keypair(&keypair, &outfile, "new")?;
|
output_keypair(&keypair, &outfile, "new")?;
|
||||||
|
}
|
||||||
|
|
||||||
let silent = matches.is_present("silent");
|
let silent = matches.is_present("silent");
|
||||||
if !silent {
|
if !silent {
|
||||||
|
@ -16,6 +16,7 @@ pub use crate::{
|
|||||||
blocktree_meta::SlotMeta,
|
blocktree_meta::SlotMeta,
|
||||||
};
|
};
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
|
use chrono::{offset::TimeZone, Duration as ChronoDuration, Utc};
|
||||||
use log::*;
|
use log::*;
|
||||||
use rayon::{
|
use rayon::{
|
||||||
iter::{IntoParallelRefIterator, ParallelIterator},
|
iter::{IntoParallelRefIterator, ParallelIterator},
|
||||||
@ -27,17 +28,18 @@ use solana_measure::measure::Measure;
|
|||||||
use solana_metrics::{datapoint_debug, datapoint_error};
|
use solana_metrics::{datapoint_debug, datapoint_error};
|
||||||
use solana_rayon_threadlimit::get_thread_count;
|
use solana_rayon_threadlimit::get_thread_count;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
clock::{Slot, DEFAULT_TICKS_PER_SECOND},
|
clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND},
|
||||||
genesis_config::GenesisConfig,
|
genesis_config::GenesisConfig,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
signature::{Keypair, KeypairUtil, Signature},
|
signature::{Keypair, KeypairUtil, Signature},
|
||||||
timing::timestamp,
|
timing::{duration_as_ms, timestamp},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp,
|
cmp,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@ -45,6 +47,7 @@ use std::{
|
|||||||
mpsc::{sync_channel, Receiver, SyncSender, TrySendError},
|
mpsc::{sync_channel, Receiver, SyncSender, TrySendError},
|
||||||
Arc, Mutex, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
},
|
},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const BLOCKTREE_DIRECTORY: &str = "rocksdb";
|
pub const BLOCKTREE_DIRECTORY: &str = "rocksdb";
|
||||||
@ -154,7 +157,7 @@ impl Blocktree {
|
|||||||
adjust_ulimit_nofile();
|
adjust_ulimit_nofile();
|
||||||
|
|
||||||
// Open the database
|
// Open the database
|
||||||
let measure = Measure::start("open");
|
let mut measure = Measure::start("open");
|
||||||
let db = Database::open(&blocktree_path)?;
|
let db = Database::open(&blocktree_path)?;
|
||||||
|
|
||||||
// Create the metadata column family
|
// Create the metadata column family
|
||||||
@ -185,6 +188,7 @@ impl Blocktree {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let last_root = Arc::new(RwLock::new(max_root));
|
let last_root = Arc::new(RwLock::new(max_root));
|
||||||
|
|
||||||
|
measure.stop();
|
||||||
info!("{:?} {}", blocktree_path, measure);
|
info!("{:?} {}", blocktree_path, measure);
|
||||||
Ok(Blocktree {
|
Ok(Blocktree {
|
||||||
db,
|
db,
|
||||||
@ -1129,6 +1133,25 @@ impl Blocktree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The `get_block_time` method is not fully implemented (depends on validator timestamp
|
||||||
|
// transactions). It currently returns Some(`slot` * DEFAULT_MS_PER_SLOT) offset from 0 for all
|
||||||
|
// transactions, and None for any values that would overflow any step.
|
||||||
|
pub fn get_block_time(&self, slot: Slot, slot_duration: Duration) -> Option<UnixTimestamp> {
|
||||||
|
let ms_per_slot = duration_as_ms(&slot_duration);
|
||||||
|
let (offset_millis, overflow) = slot.overflowing_mul(ms_per_slot);
|
||||||
|
if !overflow {
|
||||||
|
i64::try_from(offset_millis)
|
||||||
|
.ok()
|
||||||
|
.and_then(|millis| {
|
||||||
|
let median_datetime = Utc.timestamp(0, 0);
|
||||||
|
median_datetime.checked_add_signed(ChronoDuration::milliseconds(millis))
|
||||||
|
})
|
||||||
|
.map(|dt| dt.timestamp())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_confirmed_block(&self, slot: Slot) -> Result<RpcConfirmedBlock> {
|
pub fn get_confirmed_block(&self, slot: Slot) -> Result<RpcConfirmedBlock> {
|
||||||
if self.is_root(slot) {
|
if self.is_root(slot) {
|
||||||
let slot_meta_cf = self.db.column::<cf::SlotMeta>();
|
let slot_meta_cf = self.db.column::<cf::SlotMeta>();
|
||||||
|
@ -1472,6 +1472,11 @@ impl Bank {
|
|||||||
self.ticks_per_slot
|
self.ticks_per_slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the number of slots per year
|
||||||
|
pub fn slots_per_year(&self) -> f64 {
|
||||||
|
self.slots_per_year
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of slots per segment
|
/// Return the number of slots per segment
|
||||||
pub fn slots_per_segment(&self) -> u64 {
|
pub fn slots_per_segment(&self) -> u64 {
|
||||||
self.slots_per_segment
|
self.slots_per_segment
|
||||||
@ -1944,23 +1949,40 @@ mod tests {
|
|||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
keypairs: &mut Vec<Keypair>,
|
keypairs: &mut Vec<Keypair>,
|
||||||
mock_program_id: Pubkey,
|
mock_program_id: Pubkey,
|
||||||
|
generic_rent_due_for_system_account: u64,
|
||||||
) {
|
) {
|
||||||
let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1);
|
let mut account_pairs: Vec<(Pubkey, Account)> = Vec::with_capacity(keypairs.len() - 1);
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[0].pubkey(),
|
keypairs[0].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[1].pubkey(),
|
keypairs[1].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[2].pubkey(),
|
keypairs[2].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[3].pubkey(),
|
keypairs[3].pubkey(),
|
||||||
Account::new(51224, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[4].pubkey(),
|
keypairs[4].pubkey(),
|
||||||
@ -1972,12 +1994,20 @@ mod tests {
|
|||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[6].pubkey(),
|
keypairs[6].pubkey(),
|
||||||
Account::new(102_468, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
(2 * generic_rent_due_for_system_account) + 24,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[8].pubkey(),
|
keypairs[8].pubkey(),
|
||||||
Account::new(52153, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 2 + 929,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[9].pubkey(),
|
keypairs[9].pubkey(),
|
||||||
@ -1987,15 +2017,19 @@ mod tests {
|
|||||||
// Feeding to MockProgram to test read only rent behaviour
|
// Feeding to MockProgram to test read only rent behaviour
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[10].pubkey(),
|
keypairs[10].pubkey(),
|
||||||
Account::new(51225, 1, &Pubkey::default()),
|
Account::new(
|
||||||
|
generic_rent_due_for_system_account + 3,
|
||||||
|
1,
|
||||||
|
&Pubkey::default(),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[11].pubkey(),
|
keypairs[11].pubkey(),
|
||||||
Account::new(51225, 1, &mock_program_id),
|
Account::new(generic_rent_due_for_system_account + 3, 1, &mock_program_id),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[12].pubkey(),
|
keypairs[12].pubkey(),
|
||||||
Account::new(51225, 1, &mock_program_id),
|
Account::new(generic_rent_due_for_system_account + 3, 1, &mock_program_id),
|
||||||
));
|
));
|
||||||
account_pairs.push((
|
account_pairs.push((
|
||||||
keypairs[13].pubkey(),
|
keypairs[13].pubkey(),
|
||||||
@ -2055,7 +2089,25 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(bank.last_blockhash(), genesis_config.hash());
|
assert_eq!(bank.last_blockhash(), genesis_config.hash());
|
||||||
|
|
||||||
store_accounts_for_rent_test(&bank, &mut keypairs, mock_program_id);
|
let slots_elapsed: u64 = (0..=bank.epoch)
|
||||||
|
.map(|epoch| {
|
||||||
|
bank.rent_collector
|
||||||
|
.epoch_schedule
|
||||||
|
.get_slots_in_epoch(epoch + 1)
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let (generic_rent_due_for_system_account, _) = bank.rent_collector.rent.due(
|
||||||
|
bank.get_minimum_balance_for_rent_exemption(1) - 1,
|
||||||
|
1,
|
||||||
|
slots_elapsed as f64 / bank.rent_collector.slots_per_year,
|
||||||
|
);
|
||||||
|
|
||||||
|
store_accounts_for_rent_test(
|
||||||
|
&bank,
|
||||||
|
&mut keypairs,
|
||||||
|
mock_program_id,
|
||||||
|
generic_rent_due_for_system_account,
|
||||||
|
);
|
||||||
|
|
||||||
let t1 = system_transaction::transfer(
|
let t1 = system_transaction::transfer(
|
||||||
&keypairs[0],
|
&keypairs[0],
|
||||||
@ -2078,7 +2130,7 @@ mod tests {
|
|||||||
let t4 = system_transaction::transfer(
|
let t4 = system_transaction::transfer(
|
||||||
&keypairs[6],
|
&keypairs[6],
|
||||||
&keypairs[7].pubkey(),
|
&keypairs[7].pubkey(),
|
||||||
51223,
|
49373,
|
||||||
genesis_config.hash(),
|
genesis_config.hash(),
|
||||||
);
|
);
|
||||||
let t5 = system_transaction::transfer(
|
let t5 = system_transaction::transfer(
|
||||||
@ -2118,32 +2170,35 @@ mod tests {
|
|||||||
|
|
||||||
let mut rent_collected = 0;
|
let mut rent_collected = 0;
|
||||||
|
|
||||||
// 51224 - 51222(Rent) - 1(transfer)
|
// 49374 - 49372(Rent) - 1(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[0].pubkey()), 1);
|
assert_eq!(bank.get_balance(&keypairs[0].pubkey()), 1);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 51224 - 51222(Rent) - 1(transfer)
|
// 49374 - 49372(Rent) - 1(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[1].pubkey()), 3);
|
assert_eq!(bank.get_balance(&keypairs[1].pubkey()), 3);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 51224 - 51222(Rent) - 1(transfer)
|
// 49374 - 49372(Rent) - 1(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[2].pubkey()), 1);
|
assert_eq!(bank.get_balance(&keypairs[2].pubkey()), 1);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 51224 - 51222(Rent) - 1(transfer)
|
// 49374 - 49372(Rent) - 1(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[3].pubkey()), 3);
|
assert_eq!(bank.get_balance(&keypairs[3].pubkey()), 3);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// No rent deducted
|
// No rent deducted
|
||||||
assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10);
|
assert_eq!(bank.get_balance(&keypairs[4].pubkey()), 10);
|
||||||
assert_eq!(bank.get_balance(&keypairs[5].pubkey()), 10);
|
assert_eq!(bank.get_balance(&keypairs[5].pubkey()), 10);
|
||||||
|
|
||||||
// 102_468 - 51222(Rent) - 51223(transfer)
|
// 98768 - 49372(Rent) - 49373(transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[6].pubkey()), 23);
|
assert_eq!(bank.get_balance(&keypairs[6].pubkey()), 23);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 0 + 51223(transfer) - 917(Rent)
|
// 0 + 49373(transfer) - 917(Rent)
|
||||||
assert_eq!(bank.get_balance(&keypairs[7].pubkey()), 50306);
|
assert_eq!(
|
||||||
|
bank.get_balance(&keypairs[7].pubkey()),
|
||||||
|
generic_rent_due_for_system_account + 1 - 917
|
||||||
|
);
|
||||||
// Epoch should be updated
|
// Epoch should be updated
|
||||||
// Rent deducted on store side
|
// Rent deducted on store side
|
||||||
let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap();
|
let account8 = bank.get_account(&keypairs[7].pubkey()).unwrap();
|
||||||
@ -2151,9 +2206,9 @@ mod tests {
|
|||||||
assert_eq!(account8.rent_epoch, bank.epoch + 1);
|
assert_eq!(account8.rent_epoch, bank.epoch + 1);
|
||||||
rent_collected += 917;
|
rent_collected += 917;
|
||||||
|
|
||||||
// 52153 - 51222(Rent) - 929(Transfer)
|
// 50303 - 49372(Rent) - 929(Transfer)
|
||||||
assert_eq!(bank.get_balance(&keypairs[8].pubkey()), 2);
|
assert_eq!(bank.get_balance(&keypairs[8].pubkey()), 2);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
let account10 = bank.get_account(&keypairs[9].pubkey()).unwrap();
|
let account10 = bank.get_account(&keypairs[9].pubkey()).unwrap();
|
||||||
// Account was overwritten at load time, since it didn't have sufficient balance to pay rent
|
// Account was overwritten at load time, since it didn't have sufficient balance to pay rent
|
||||||
@ -2165,17 +2220,17 @@ mod tests {
|
|||||||
assert_eq!(account10.lamports, 12);
|
assert_eq!(account10.lamports, 12);
|
||||||
rent_collected += 927;
|
rent_collected += 927;
|
||||||
|
|
||||||
// 51225 - 51222(Rent)
|
// 49375 - 49372(Rent)
|
||||||
assert_eq!(bank.get_balance(&keypairs[10].pubkey()), 3);
|
assert_eq!(bank.get_balance(&keypairs[10].pubkey()), 3);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 51225 - 51222(Rent) + 1(Addition by program)
|
// 49375 - 49372(Rent) + 1(Addition by program)
|
||||||
assert_eq!(bank.get_balance(&keypairs[11].pubkey()), 4);
|
assert_eq!(bank.get_balance(&keypairs[11].pubkey()), 4);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// 51225 - 51222(Rent) - 1(Deduction by program)
|
// 49375 - 49372(Rent) - 1(Deduction by program)
|
||||||
assert_eq!(bank.get_balance(&keypairs[12].pubkey()), 2);
|
assert_eq!(bank.get_balance(&keypairs[12].pubkey()), 2);
|
||||||
rent_collected += 51222;
|
rent_collected += generic_rent_due_for_system_account;
|
||||||
|
|
||||||
// No rent for read-only account
|
// No rent for read-only account
|
||||||
assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14);
|
assert_eq!(bank.get_balance(&keypairs[13].pubkey()), 14);
|
||||||
|
@ -85,6 +85,10 @@ pub type Segment = u64;
|
|||||||
/// some number of Slots.
|
/// some number of Slots.
|
||||||
pub type Epoch = u64;
|
pub type Epoch = u64;
|
||||||
|
|
||||||
|
/// UnixTimestamp is an approximate measure of real-world time,
|
||||||
|
/// expressed as Unix time (ie. seconds since the Unix epoch)
|
||||||
|
pub type UnixTimestamp = i64;
|
||||||
|
|
||||||
/// Clock represents network time. Members of Clock start from 0 upon
|
/// Clock represents network time. Members of Clock start from 0 upon
|
||||||
/// network boot. The best way to map Clock to wallclock time is to use
|
/// network boot. The best way to map Clock to wallclock time is to use
|
||||||
/// current Slot, as Epochs vary in duration (they start short and grow
|
/// current Slot, as Epochs vary in duration (they start short and grow
|
||||||
|
@ -117,7 +117,7 @@ impl Instruction {
|
|||||||
pub struct AccountMeta {
|
pub struct AccountMeta {
|
||||||
/// An account's public key
|
/// An account's public key
|
||||||
pub pubkey: Pubkey,
|
pub pubkey: Pubkey,
|
||||||
/// True if an Instruciton requires a Transaction signature matching `pubkey`.
|
/// True if an Instruction requires a Transaction signature matching `pubkey`.
|
||||||
pub is_signer: bool,
|
pub is_signer: bool,
|
||||||
/// True if the `pubkey` can be loaded as a read-write account.
|
/// True if the `pubkey` can be loaded as a read-write account.
|
||||||
pub is_writable: bool,
|
pub is_writable: bool,
|
||||||
|
@ -28,6 +28,8 @@ impl PohConfig {
|
|||||||
|
|
||||||
impl Default for PohConfig {
|
impl Default for PohConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_sleep(Duration::from_millis(1000 / DEFAULT_TICKS_PER_SECOND))
|
Self::new_sleep(Duration::from_micros(
|
||||||
|
1000 * 1000 / DEFAULT_TICKS_PER_SECOND,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,27 +38,58 @@ pub fn years_as_slots(years: f64, tick_duration: &Duration, ticks_per_slot: u64)
|
|||||||
/ ticks_per_slot as f64
|
/ ticks_per_slot as f64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// From slots per year to tick_duration
|
||||||
|
pub fn slot_duration_from_slots_per_year(slots_per_year: f64) -> Duration {
|
||||||
|
// Regarding division by zero potential below: for some reason, if Rust stores an `inf` f64 and
|
||||||
|
// then converts it to a u64 on use, it always returns 0, as opposed to std::u64::MAX or any
|
||||||
|
// other huge value
|
||||||
|
let slot_in_ns = (SECONDS_PER_YEAR * 1_000_000_000.0) / slots_per_year;
|
||||||
|
Duration::from_nanos(slot_in_ns as u64)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_years_as_slots() {
|
fn test_years_as_slots() {
|
||||||
let tick_duration = Duration::from_millis(1000 / 160);
|
let tick_duration = Duration::from_micros(1000 * 1000 / 160);
|
||||||
|
|
||||||
// interestingly large numbers with 160 ticks/second
|
// interestingly large numbers with 160 ticks/second
|
||||||
assert_eq!(years_as_slots(0.0, &tick_duration, 4) as u64, 0);
|
assert_eq!(years_as_slots(0.0, &tick_duration, 4) as u64, 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
years_as_slots(1.0 / 12f64, &tick_duration, 4) as u64,
|
years_as_slots(1.0 / 12f64, &tick_duration, 4) as u64,
|
||||||
109_572_659
|
105_189_753
|
||||||
);
|
);
|
||||||
assert_eq!(years_as_slots(1.0, &tick_duration, 4) as u64, 1_314_871_916);
|
assert_eq!(years_as_slots(1.0, &tick_duration, 4) as u64, 1_262_277_039);
|
||||||
|
|
||||||
let tick_duration = Duration::from_millis(1000);
|
let tick_duration = Duration::from_micros(1000 * 1000);
|
||||||
// one second in years with one tick per second + one tick per slot
|
// one second in years with one tick per second + one tick per slot
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
years_as_slots(1.0 / SECONDS_PER_YEAR, &tick_duration, 1),
|
years_as_slots(1.0 / SECONDS_PER_YEAR, &tick_duration, 1),
|
||||||
1.0
|
1.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slot_duration_from_slots_per_year() {
|
||||||
|
let slots_per_year = 1_262_277_039.0;
|
||||||
|
let ticks_per_slot = 4;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
slot_duration_from_slots_per_year(slots_per_year),
|
||||||
|
Duration::from_micros(1000 * 1000 / 160) * ticks_per_slot
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
slot_duration_from_slots_per_year(0.0),
|
||||||
|
Duration::from_micros(0) * ticks_per_slot
|
||||||
|
);
|
||||||
|
|
||||||
|
let slots_per_year = SECONDS_PER_YEAR;
|
||||||
|
let ticks_per_slot = 1;
|
||||||
|
assert_eq!(
|
||||||
|
slot_duration_from_slots_per_year(slots_per_year),
|
||||||
|
Duration::from_millis(1000) * ticks_per_slot
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,9 @@ pub enum TransactionError {
|
|||||||
|
|
||||||
/// Transaction contains an invalid account reference
|
/// Transaction contains an invalid account reference
|
||||||
InvalidAccountIndex,
|
InvalidAccountIndex,
|
||||||
|
|
||||||
|
/// Transaction did not pass signature verification
|
||||||
|
SignatureFailure,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, TransactionError>;
|
pub type Result<T> = result::Result<T, TransactionError>;
|
||||||
@ -225,6 +228,21 @@ impl Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify the transaction
|
||||||
|
pub fn verify(&self) -> Result<()> {
|
||||||
|
if !self
|
||||||
|
.signatures
|
||||||
|
.iter()
|
||||||
|
.zip(&self.message.account_keys)
|
||||||
|
.map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &self.message_data()))
|
||||||
|
.all(|verify_result| verify_result)
|
||||||
|
{
|
||||||
|
Err(TransactionError::SignatureFailure)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
|
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
|
||||||
pub fn get_signing_keypair_positions<T: KeypairUtil>(
|
pub fn get_signing_keypair_positions<T: KeypairUtil>(
|
||||||
&self,
|
&self,
|
||||||
@ -246,6 +264,27 @@ impl Transaction {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace all the signatures and pubkeys
|
||||||
|
pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
|
||||||
|
let num_required_signatures = self.message.header.num_required_signatures as usize;
|
||||||
|
if signers.len() != num_required_signatures
|
||||||
|
|| self.signatures.len() != num_required_signatures
|
||||||
|
|| self.message.account_keys.len() < num_required_signatures
|
||||||
|
{
|
||||||
|
return Err(TransactionError::InvalidAccountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
signers
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, (pubkey, signature))| {
|
||||||
|
self.signatures[i] = *signature;
|
||||||
|
self.message.account_keys[i] = *pubkey;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.verify()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_signed(&self) -> bool {
|
pub fn is_signed(&self) -> bool {
|
||||||
self.signatures
|
self.signatures
|
||||||
.iter()
|
.iter()
|
||||||
|
Reference in New Issue
Block a user